2009-03-19 11 views
17

У меня возникла проблема при связывании команды в контекстном меню пользовательского элемента управления, который находится на закладке. В первый раз, когда я пользуюсь меню (щелкните правой кнопкой на вкладке), он отлично работает, но если я переключу вкладку, команда будет использовать экземпляр привязки данных, который был использован в первый раз.Контекстное меню WPF не привязывается к правильному значению базы данных

Если я ставлю кнопку, привязанный к команде в UserControl он работает, как ожидалось ...

Может кто-то пожалуйста, скажите мне, что я делаю неправильно ??

Это тестовый проект, который обнажает проблему:

App.xaml.cs:

public partial class App : Application 
{ 
    protected override void OnStartup(StartupEventArgs e) 
    { 
     base.OnStartup(e); 

     CompanyViewModel model = new CompanyViewModel(); 
     Window1 window = new Window1(); 
     window.DataContext = model; 
     window.Show(); 
    } 
} 

Window1.xaml:

<Window x:Class="WpfApplication1.Window1" 
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
    xmlns:vw="clr-namespace:WpfApplication1" 
Title="Window1" Height="300" Width="300"> 

    <Window.Resources> 
    <DataTemplate x:Key="HeaderTemplate"> 
     <StackPanel Orientation="Horizontal"> 
      <TextBlock Text="{Binding Path=Name}" /> 
     </StackPanel> 
    </DataTemplate> 
    <DataTemplate DataType="{x:Type vw:PersonViewModel}"> 
     <vw:UserControl1/> 
    </DataTemplate> 

</Window.Resources> 
<Grid> 
    <TabControl ItemsSource="{Binding Path=Persons}" 
       ItemTemplate="{StaticResource HeaderTemplate}" 
       IsSynchronizedWithCurrentItem="True" /> 
</Grid> 
</Window> 

UserControl1.xaml:

<UserControl x:Class="WpfApplication1.UserControl1" 
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
    MinWidth="200"> 
    <UserControl.ContextMenu> 
     <ContextMenu > 
      <MenuItem Header="Change" Command="{Binding Path=ChangeCommand}"/> 
     </ContextMenu> 
    </UserControl.ContextMenu> 
    <Grid> 
     <Grid.ColumnDefinitions> 
      <ColumnDefinition Width="100" /> 
      <ColumnDefinition Width="*" /> 
     </Grid.ColumnDefinitions> 
     <Label Grid.Column="0">The name:</Label> 
     <TextBox Grid.Column="1" Text="{Binding Path=Name, UpdateSourceTrigger=PropertyChanged}" /> 
    </Grid> 
</UserControl> 

Compa nyViewModel.cs:

public class CompanyViewModel 
{ 
    public ObservableCollection<PersonViewModel> Persons { get; set; } 
    public CompanyViewModel() 
    { 
     Persons = new ObservableCollection<PersonViewModel>(); 
     Persons.Add(new PersonViewModel(new Person { Name = "Kalle" })); 
     Persons.Add(new PersonViewModel(new Person { Name = "Nisse" })); 
     Persons.Add(new PersonViewModel(new Person { Name = "Jocke" })); 
    } 
} 

PersonViewModel.cs:

public class PersonViewModel : INotifyPropertyChanged 
{ 
    Person _person; 
    TestCommand _testCommand; 

    public PersonViewModel(Person person) 
    { 
     _person = person; 
     _testCommand = new TestCommand(this); 
    } 
    public ICommand ChangeCommand 
    { 
     get 
     { 
      return _testCommand; 
     } 
    } 
    public string Name 
    { 
     get 
     { 
      return _person.Name; 
     } 
     set 
     { 
      if (value == _person.Name) 
       return; 
      _person.Name = value; 
      OnPropertyChanged("Name"); 
     } 
    } 
    void OnPropertyChanged(string propertyName) 
    { 
     PropertyChangedEventHandler handler = this.PropertyChanged; 
     if (handler != null) 
     { 
      var e = new PropertyChangedEventArgs(propertyName); 
      handler(this, e); 
     } 
    } 
    public event PropertyChangedEventHandler PropertyChanged; 
} 

TestCommand.cs:

public class TestCommand : ICommand 
{ 
    PersonViewModel _person; 
    public event EventHandler CanExecuteChanged; 

    public TestCommand(PersonViewModel person) 
    { 
     _person = person; 
    } 
    public bool CanExecute(object parameter) 
    { 
     return true; 
    } 
    public void Execute(object parameter) 
    { 
     _person.Name = "Changed by command"; 
    } 
} 

Person.cs:

public class Person 
{ 
    public string Name { get; set; } 
} 

ответ

22

Главное, помнить, это nus не являются частью визуального дерева.

Поэтому они не наследуют тот же источник, что и элемент управления, к которому они принадлежат для привязки. Способ справиться с этим заключается в привязке к цели размещения самого ContextMenu.

<MenuItem Header="Change" Command="{Binding 
    Path=PlacementTarget.ChangeCommand, 
    RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ContextMenu}}}" 
/> 
+0

Привет Кэмерон. Как вы думаете, ваша техника здесь как-то связана с проблемой, которую я описал здесь: http://stackoverflow.com/questions/833607/wpf-why-do-contextmenu-items-work-for-listbox-but-not- itemscontrol ... Я не привязываюсь к команде, но у меня есть подозрение, что это связанная с этим проблема. –

+2

Я не уверен в этом ответе. Командные привязки DO работают для элемента меню (он знает, что он должен привязать модель представления) ... проблема в том, что элементы меню не перепроверяются, когда изменяется дата-текст из-за переключения. Если из-за того, что они не являются частью визуального дерева, как это работает в первый раз? – Schneider

+0

@Schneider: Я не говорил, что привязки в меню не работают, просто они не наследуют свой datacontext от своего родителя, как вы ожидали. Я бы сказал, что механизм привязки WPF устанавливает контекст при первом открытии меню, а затем не обновляет его при изменении вкладки. –

8

Самый чистый способ, с помощью которого я связывал команды с элементами контекстного меню, включает использование класса CommandReference. Вы можете найти его в наборе MVVM на Codeplex по адресу WPF Futures.

Часть XAML может выглядеть следующим образом:

<UserControl x:Class="View.MyView" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
       xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
       xmlns:vm="clr-namespace:ViewModel;assembly=MyViewModel" 
       xmlns:mvvm="clr-namespace:ViewModelHelper;assembly=ViewModelHelper" 
      <UserControl.Resources> 
       <mvvm:CommandReference x:Key="MyCustomCommandReference" Command="{Binding MyCustomCommand}" /> 

       <ContextMenu x:Key="ItemContextMenu"> 
        <MenuItem Header="Plate"> 
         <MenuItem Header="Inspect Now" Command="{StaticResource MyCustomCommandReference}" 
           CommandParameter="{Binding}"> 
         </MenuItem> 
        </MenuItem> 
       </ContextMenu> 
    </UserControl.Resources> 

MyCustomCommand является RelayCommand на ViewModel. В этом примере ViewModel был привязан к datacontext представления в коде.

Примечание: этот XAML был скопирован из рабочего проекта и упрощен для иллюстрации. Могут быть опечатки или другие незначительные ошибки.

+1

Вы пробовали это с помощью RelayCommand с делегатом CanExecute, CyberMonk? Я обнаружил, что CommandReference получает значение null для параметра CanExecute, хотя метод Execute получает правильное значение. Это мешает мне использовать его прямо сейчас. –

+0

Хорошо, это может сработать, но может ли кто-нибудь объяснить, почему это необходимо? Почему привязки в ContextMenus запускаются только один раз? – Schneider

+0

Я могу проверить это работает ... объяснения приветствуются :) – Schneider

0

Я нашел этот метод, используя свойство Tag очень полезно при связывании из контекстного меню глубоко внутри шаблона управления:

http://blog.jtango.net/binding-to-a-menuitem-in-a-wpf-context-menu

Это позволяет связать для любого доступного для элемента управления данным, из которого было открыто контекстное меню. Контекстное меню может получить доступ к элементу управления нажатием кнопки «PlacementTarget». Если свойство Tag элемента управления, связанного с щелчком, привязано к нужному файлу данных, привязка к «PlacementTarget.Tag» из контекстного меню приведет к тому, что вы сразу перейдете к этому файлу данных.

1

Я предпочитаю другое решение. Добавить событие загрузчика контекстного меню.

<ContextMenu Loaded="ContextMenu_Loaded"> 
    <MenuItem Header="Change" Command="{Binding Path=ChangeCommand}"/> 
</ContextMenu> 

Назначить контекст данных внутри события.

private void ContextMenu_Loaded(object sender, RoutedEventArgs e) 
{ 
    (sender as ContextMenu).DataContext = this; //assignment can be replaced with desired data context 
} 
4

У меня была такая же проблема недавно с ContextMenu, который находится в ListBox. Я попытался связать команду с MVVM без кода. Я, наконец, сдался, и я попросил друга помочь. Он нашел немного искривленное, но сжатое решение. Он передает ListBox в DataContext ContextMenu, а затем находит команду в модели представления, обращаясь к DataContext ListBox. Это самое простое решение, которое я видел до сих пор. Нет настраиваемого кода, без тегов, просто чистых XAML и MVVM.

Я разместил полностью рабочий образец на Github. Вот отрывок из XAML.

<Window x:Class="WpfListContextMenu.MainWindow" 
     xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
     xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
     Title="MainWindow" Height="350" Width="268"> 
    <Grid> 
    <DockPanel> 
     <ListBox x:Name="listBox" DockPanel.Dock="Top" ItemsSource="{Binding Items}" DisplayMemberPath="Name" 
       SelectionMode="Extended"> 
     <ListBox.ContextMenu> 
      <ContextMenu DataContext="{Binding Path=PlacementTarget, RelativeSource={RelativeSource Self}}"> 
      <MenuItem Header="Show Selected" Command="{Binding Path=DataContext.ShowSelectedCommand}" 
         CommandParameter="{Binding Path=SelectedItems}" /> 
      </ContextMenu> 
     </ListBox.ContextMenu> 
     </ListBox> 
    </DockPanel> 
    </Grid> 
</Window> 
0

Я знаю, что это уже старый пост, но я хотел бы добавить еще одно решение для тех, кто ищут различные способы, чтобы сделать это.

Я не мог сделать то же самое решение для работы в моем случае, так как я пытался сделать что-то еще: откройте контекстное меню с помощью мыши (как и панель инструментов с прикрепленным к нему подменю), а также свяжите команды к моей модели. Поскольку я использовал Event Trigger, объект PlacementTarget был нулевым.

Это решение, которое я нашел, чтобы заставить его работать только с помощью XAML:

<!-- This is an example with a button, but could be other control --> 
<Button> 
    <...> 

    <!-- This opens the context menu and binds the data context to it --> 
    <Button.Triggers> 
    <EventTrigger RoutedEvent="Button.Click"> 
     <EventTrigger.Actions> 
     <BeginStoryboard> 
      <Storyboard> 
      <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="ContextMenu.DataContext"> 
       <DiscreteObjectKeyFrame KeyTime="0:0:0" Value="{Binding}"/> 
      </ObjectAnimationUsingKeyFrames> 
      <BooleanAnimationUsingKeyFrames Storyboard.TargetProperty="ContextMenu.IsOpen"> 
       <DiscreteBooleanKeyFrame KeyTime="0:0:0" Value="True"/> 
      </BooleanAnimationUsingKeyFrames> 
      </Storyboard> 
     </BeginStoryboard> 
     </EventTrigger.Actions> 
    </EventTrigger> 
    </Button.Triggers> 

    <!-- Here it goes the context menu --> 
    <Button.ContextMenu> 
    <ContextMenu> 
     <MenuItem Header="Item 1" Command="{Binding MyCommand1}"/> 
     <MenuItem Header="Item 2" Command="{Binding MyCommand2}"/> 
    </ContextMenu> 
    </Button.ContextMenu> 

</Button> 

 Смежные вопросы

  • Нет связанных вопросов^_^