2009-03-19 12 views
17

Ho un problema durante l'associazione di un comando in un menu di scelta rapida su un usercontrol che si trova in una pagina di tabulazione. La prima volta che utilizzo il menu (tasto destro del mouse sulla scheda) funziona alla grande, ma se cambio scheda il comando utilizzerà l'istanza del database che è stata utilizzata la prima volta.Il menu di scelta rapida WPF non si associa alla voce di databound destra

Se metto un pulsante che è destinata al comando nella UserControl funziona come previsto ...

Qualcuno può dirmi che cosa sto facendo male ??

Si tratta di un progetto di test che espone il problema:

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

risposta

22

La cosa fondamentale da ricordare qui è contesto me nus non fanno parte dell'albero visivo.

Pertanto non ereditano la stessa fonte del controllo a cui appartengono per il binding. Il modo di affrontarlo consiste nel collegarsi al target di posizionamento dello stesso ContextMenu.

+0

Hi Cameron. Pensi che la tua tecnica qui sia in qualche modo correlata al problema che ho descritto qui: http://stackoverflow.com/questions/833607/wpf-why-do-contextmenu-items-work-for-listbox-but-not- itemscontrol ... Non sono vincolante per un comando, ma ho il sospetto che sia un problema correlato. –

+2

Non sono convinto da questa risposta. I bind di comando funzionano per la voce di menu (sa che deve associare il modello di vista) ... il problema è che i menu non si rebind quando cambia il datacontext a causa della tabulazione. Se è dovuto al fatto che non fanno parte dell'albero visivo, come mai funziona la prima volta? – Schneider

+0

@Schneider: Non ho detto che i collegamenti in un menu non funzionano, solo che non ereditano il loro datacontext dai loro genitori come ci si aspetterebbe. Direi che il motore di associazione WPF sta impostando il contesto quando il menu viene aperto per la prima volta e quindi non lo aggiorna quando la scheda cambia. –

8

Il modo più pulito che ho trovato per associare i comandi alle voci del menu di scelta rapida consiste nell'utilizzare una classe denominata CommandReference. Puoi trovarlo nel toolkit MVVM su Codeplex al WPF Futures.

Il XAML potrebbe assomigliare a questo:

<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 è un RelayCommand sul ViewModel. In questo esempio, ViewModel era collegato al datacontext della vista nel code-behind.

Nota: questo XAML è stato copiato da un progetto di lavoro e semplificato per illustrazione. Potrebbero esserci errori di battitura o altri errori minori.

+1

Hai provato questo con un RelayCommand con un delegato CanExecute, CyberMonk? Ho rilevato che CommandReference viene passato null al parametro su CanExecute, sebbene il metodo Execute ottenga il valore corretto. Mi impedisce di usarlo adesso. –

+0

OK, questo può funzionare ma qualcuno può spiegare perché è necessario? Perché i binding su ContextMenus vengono eseguiti una sola volta? – Schneider

+0

Posso verificare che funzioni ... le spiegazioni sono benvenute :) – Schneider

0

ho trovato questo metodo che utilizza la proprietà Tag molto utile quando vincolante da un menu contestuale in profondità all'interno di un modello di controllo:

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

In questo modo è possibile associare a qualsiasi datacontext disponibile per il controllo da cui è stato aperto il menu di scelta rapida. Il menu di scelta rapida può accedere al controllo cliccato tramite "PlacementTarget". Se la proprietà Tag del controllo su cui si fa clic è associata a un datacontext desiderato, il collegamento a "PlacementTarget.Tag" dall'interno del menu di scelta rapida ti porterà direttamente a quel datacontext.

1

Preferisco un'altra soluzione. Aggiungi evento caricatore menu contestuale.

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

Assegnare contesto dati all'interno dell'evento.

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

Ho avuto lo stesso problema di recente con un ContextMenu che si trova in un ListBox. Ho provato a collegare un comando in modo MVVM senza code-behind. Alla fine ho rinunciato e ho chiesto ad un amico il suo aiuto. Trovò una soluzione leggermente contorta ma concisa. Sta passando il ListBox nel DataContext del ContextMenu e quindi trova il comando nel modello di vista accedendo al DataContext del ListBox. Questa è la soluzione più semplice che ho visto finora. Nessun codice personalizzato, nessun tag, solo XAML e MVVM puri.

Ho inviato un campione completamente funzionante su Github. Ecco un estratto di 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

So che questo è già un vecchio post, ma vorrei aggiungere un'altra soluzione per coloro chi sono alla ricerca di modi diversi per farlo.

Non riuscivo a far funzionare la stessa soluzione nel mio caso, poiché stavo cercando di fare qualcos'altro: aprire il menu di scelta rapida con un clic del mouse (proprio come una barra degli strumenti con un sottomenu ad esso collegato) e anche i comandi di bind al mio modello. Dal momento che stavo usando un Event Trigger, l'oggetto PlacementTarget era nullo.

Questa è la soluzione che ho trovato per farlo funzionare solo con 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> 
Problemi correlati