c# - Usando MVVM, ¿cómo puede un ContextMenu ViewModel encontrar el ViewModel que abrió el ContextMenu?
wpf (2)
Estoy usando MVVM para vincular vistas a objetos en un árbol. Tengo una clase base que implementa los elementos en mi árbol, y esa clase base tiene una propiedad ContextMenu:
public IEnumerable<IMenuItem> ContextMenu
{
get
{
return m_ContextMenu;
}
protected set
{
if (m_ContextMenu != value)
{
m_ContextMenu = value;
NotifyPropertyChanged(m_ContextMenuArgs);
}
}
}
private IEnumerable<IMenuItem> m_ContextMenu = null;
static readonly PropertyChangedEventArgs m_ContextMenuArgs =
NotifyPropertyChangedHelper.CreateArgs<AbstractSolutionItem>(o => o.ContextMenu);
La Vista que se une a la clase base (y todas las clases derivadas) implementa un ContextMenu que se une a esa propiedad:
<ContextMenu x:Name="contextMenu" ItemsSource="{Binding Path=(local:AbstractSolutionItem.ContextMenu)}"
IsEnabled="{Binding Path=(local:AbstractSolutionItem.ContextMenuEnabled)}"
ItemContainerStyle="{StaticResource contextMenuStyle}"/>
Cada elemento del menú está vinculado a un objeto IMenuItem (un ViewModel para los elementos del menú). Cuando hace clic en el elemento de menú, usa Comandos para ejecutar un comando en el objeto base. Todo esto funciona genial
Sin embargo, una vez que el comando se ejecuta en la clase IMenuItem, a veces necesita obtener una referencia al objeto que el usuario hizo clic derecho para mostrar el menú contextual (o el ViewModel de ese objeto, al menos). Ese es el objetivo de un menú contextual. ¿Cómo debo pasar la referencia del elemento de árbol ViewModel al MenuItem ViewModel? Tenga en cuenta que algunos menús contextuales son compartidos por muchos objetos en el árbol.
Lo resolví manejando el evento ContextMenuOpening en el control padre (el que poseía el ContextMenu en la Vista). También agregué una propiedad de contexto a IMenuItem. El controlador se ve así:
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<IMenuItem> items =
solutionItem.ContextMenu as IEnumerable<IMenuItem>;
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;
}
}
Esto aprovecha el hecho de que solo puede haber un ContextMenu abierto a la vez.
Hay un DP en el objeto ContextMenu llamado "PlacementTarget" - esto se configurará en el elemento UI al que está conectado el menú contextual - incluso puede usarlo como fuente de enlace, por lo que podría pasarlo a su comando a través de CommandParameter:
http://msdn.microsoft.com/en-us/library/system.windows.controls.contextmenu.placementtarget.aspx
editar: en su caso, querría la VM del PlacementTarget, por lo que su enlace probablemente se parecería más a:
{Binding Path=PlacementTarget.DataContext, RelativeSource={RelativeSource Self}}