Avec MVVM, comment un ViewModel ContextMenu peut-il trouver le ViewModel qui a ouvert le ContextMenu?

J’utilise MVVM pour lier des vues à des objects dans une arborescence. J’ai une classe de base qui implémente les éléments de mon arbre et cette classe de base a une propriété ContextMenu:

public IEnumerable ContextMenu { get { return m_ContextMenu; } protected set { if (m_ContextMenu != value) { m_ContextMenu = value; NotifyPropertyChanged(m_ContextMenuArgs); } } } private IEnumerable m_ContextMenu = null; static readonly PropertyChangedEventArgs m_ContextMenuArgs = NotifyPropertyChangedHelper.CreateArgs(o => o.ContextMenu); 

La vue qui se lie à la classe de base (et à toutes les classes dérivées) implémente un ContextMenu qui se lie à cette propriété:

  

Chaque élément du menu est lié à un object IMenuItem (un ViewModel pour les éléments de menu). Lorsque vous cliquez sur l’élément de menu, il utilise des commandes pour exécuter une commande sur l’object de base. Tout cela fonctionne très bien.

Cependant, une fois que la commande est exécutée sur la classe IMenuItem, elle doit parfois obtenir une référence à l’object sur lequel l’utilisateur a cliqué avec le bouton droit pour afficher le menu contextuel (ou au moins le ViewModel de cet object). C’est tout l’intérêt d’un menu contextuel. Comment dois-je passer pour passer la référence de l’élément d’arborescence ViewModel au MenuItem ViewModel? Notez que certains menus contextuels sont partagés par de nombreux objects dans l’arborescence.

Il y a un DP sur l’object ContextMenu appelé “PlacementTarget” – il sera défini sur l’élément d’interface utilisateur auquel le menu contextuel est associé – vous pouvez même l’utiliser comme source de liaison, afin que vous puissiez le transmettre à votre commande via CommandParameter:

http://msdn.microsoft.com/en-us/library/system.windows.controls.contextmenu.placementtarget.aspx

edit: dans votre cas, vous voudriez que la machine virtuelle du PlacementTarget, ainsi votre liaison ressemblerait probablement plus à:

 {Binding Path=PlacementTarget.DataContext, RelativeSource={RelativeSource Self}} 

J’ai résolu ce problème en gérant l’événement ContextMenuOpening sur le contrôle parent (celui qui possédait le ContextMenu dans la vue). J’ai également ajouté une propriété de contexte à IMenuItem. Le gestionnaire ressemble à ceci:

  private void stackPanel_ContextMenuOpening( object sender, ContextMenuEventArgs e) { StackPanel sp = sender as StackPanel; if (sp != null) { // solutionItem is the "context" ISolutionItem solutionItem = sp.DataContext as ISolutionItem; if (solutionItem != null) { IEnumerable items = solutionItem.ContextMenu as IEnumerable; if (items != null) { foreach (IMenuItem item in items) { // will automatically set all // child menu items' context as well item.Context = solutionItem; } } else { e.Handled = true; } } else { e.Handled = true; } } else { e.Handled = true; } } 

Cela tire parti du fait qu’il ne peut y avoir qu’un seul ContextMenu ouvert à la fois.