2009-03-19 6 views
17

Mam problem z powiązaniem polecenia w menu kontekstowym na kontrolerze użytkownika, które znajdują się na stronie karty. Przy pierwszym użyciu menu (kliknięcie prawym przyciskiem myszy na zakładce) działa świetnie, ale jeśli przełączyłem kartę, polecenie użyje instancji databound, która została użyta za pierwszym razem.Menu kontekstowe WPF nie wiąże się z właściwym obiektem danych pozycji

Jeśli mogę umieścić przycisk, który jest związany z polecenia w usercontrol to działa zgodnie z oczekiwaniami ...

Czy ktoś mógłby mi powiedzieć co robię źle ??

Jest to projekt testu, który odsłania problem:

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; } 
} 

Odpowiedz

22

Kluczową rzeczą do zapamiętania tutaj jest kontekstowo mnie nus nie są częścią drzewa wizualnego.

Dlatego nie odziedziczą tego samego źródła, do którego należą, do którego wiążą się. Sposobem na poradzenie sobie z tym jest powiązanie z celem umieszczenia w samym ContextMenu.

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

Hi Cameron. Czy sądzisz, że twoja technika jest w jakiś sposób powiązana z problemem, który opisałem tutaj: http://stackoverflow.com/questions/833607/wpf-why-do-contextmenu-items-work-for-listbox-but-not- itemscontrol ... Nie wiążę polecenia, ale podejrzewam, że to problem. –

+2

Nie jestem przekonany przez tę odpowiedź. Powiązania poleceń działają dla elementu menu (wiadomo, że musi on wiązać model widoku) ... Problem polega na tym, że menuitems nie rebinduje się, gdy zmienia się format danych z powodu przełączania tabulatora. Jeśli to nie jest częścią wizualnego drzewa, to dlaczego to działa po raz pierwszy? – Schneider

+0

@Schneider: Nie powiedziałem, że powiązania w menu nie działają, tylko że nie dziedziczą swojego datacontextu od swojego rodzica, jak można się spodziewać. Powiedziałbym, że mechanizm wiążący WPF ustawia kontekst, gdy menu jest otwierane po raz pierwszy, a następnie nie aktualizuje go po zmianie karty. –

8

Najczystszy sposób, w jaki znalazłem wiązanie poleceń z elementami menu kontekstowego, wymaga użycia klasy o nazwie CommandReference. Można go znaleźć w zestawie narzędzi MVVM na Codeplex pod numerem WPF Futures.

XAML może wyglądać następująco:

<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 jest RelayCommand na ViewModel. W tym przykładzie ViewModel został dołączony do datacontext widoku w kodzie z tyłu.

Uwaga: ten XAML został skopiowany z działającego projektu i uproszczony w celach ilustracyjnych. Mogą występować literówki lub inne drobne błędy.

+1

Czy próbowałeś tego z RelayCommand z delegatem CanExecute, CyberMonk? Odkryłem, że CommandReference zostaje przekazane null do parametru do CanExecute, chociaż metoda Execute dostaje prawidłową wartość. To powstrzymuje mnie przed używaniem tego teraz. –

+0

OK, to może zadziałać, ale czy ktoś może wyjaśnić, dlaczego jest potrzebny? Dlaczego wiązania na ContextMenus działają tylko raz? – Schneider

+0

Mogę zweryfikować te prace ... wyjaśnienia są mile widziane :) – Schneider

0

znalazłem tę metodę przy użyciu właściwości Tag bardzo przydatna podczas wiązania z menu kontekstowego głęboko wewnątrz szablonu sterowania:

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

To sprawia, że ​​możliwe do powiązania do dowolnego formatu danych dostępnego dla formantu, z którego otwierano menu kontekstowe. Menu kontekstowe może uzyskać dostęp do klikniętego elementu sterującego poprzez "PlacementTarget". Jeśli właściwość Tag kontrolki klikniętej jest powiązana z żądanym formatem danych, powiązanie z "menu docelowym" z poziomu menu kontekstowego przerzuci użytkownika bezpośrednio na ten format danych.

1

Preferuję inne rozwiązanie. Dodaj wydarzenie w menu kontekstowym.

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

Przypisz kontekst danych w ramach wydarzenia.

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

Ostatnio miałem ten sam problem z ContextMenu znajdującym się w ListBox. Próbowałem powiązać polecenie z metodą MVVM bez żadnego kodu z tyłu. W końcu się poddałem i poprosiłem o pomoc przyjaciela. Znalazł lekko skręcone, ale zwięzłe rozwiązanie. Przekazuje ListBox w DataContext w ContextMenu, a następnie znaleźć polecenie w modelu widoku, uzyskując dostęp do DataContext z ListBox. To najprostsze rozwiązanie, jakie dotychczas widziałem. Brak własnego kodu, tagu, tylko czysty XAML i MVVM.

Wysłałem w pełni działającą próbkę na Github. Oto fragment 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

Wiem, że to już stary post, ale chciałbym dodać kolejne rozwiązanie dla tych, którzy szukają różnych sposobów, aby to zrobić.

Nie mogłem zrobić tego samego rozwiązania do pracy w moim przypadku, ponieważ próbowałem zrobić coś innego: otwórz menu kontekstowe za pomocą kliknięcia myszką (podobnie jak pasek narzędzi z podmenu do niego dołączonego), a także powiązać polecenia do mojego modelu. Ponieważ korzystałem z wyzwalacza zdarzeń, obiekt PlacementTarget miał wartość zerową.

To rozwiązanie znalazłem aby pracować tylko przy użyciu 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> 
Powiązane problemy