Estoy usando MVVM y quiero vincular datos de mi lista de MenuViewModels a mi menú mutilado. Que consiste en un conjunto de elementos de menú y separadores.
Aquí está mi código MenuItemViewModel:
public interface IMenuItemViewModel { } [DebuggerDisplay("---")] public class SeparatorViewModel : IMenuItemViewModel { } [DebuggerDisplay("{Header}, Children={Children.Count}")] public class MenuItemViewModel : IMenuItemViewModel, INotifyPropertyChanged { public event PropertyChangedEventHandler PropertyChanged; public MenuItemViewModel(string header, ICommand command, ImageSource imageSource) { Header = header; Command = command; ImageSource = imageSource; Children = new List<IMenuItemViewModel>(); } public string Header { get; private set; } public ICommand Command { get; private set; } public ImageSource ImageSource { get; private set; } public IList<IMenuItemViewModel> Children { get; private set; } }Y mi ventana principal se ve así:
<Window.Resources> <HierarchicalDataTemplate DataType="{x:Type ViewModel:MenuItemViewModel}" ItemsSource="{Binding Children}"> <MenuItem Header="{Binding Header}" Command="{Binding Command}"/> </HierarchicalDataTemplate> <DataTemplate DataType="{x:Type ViewModel:SeparatorViewModel}"> <Separator /> </DataTemplate> </Window.Resources> <DockPanel> <Menu DockPanel.Dock="Top" ItemsSource="{Binding MenuItems}"> </Menu> </DockPanel> Debería ser algo muy simple. Desafortunadamente, el elemento del menú se ve mal o el separador es un elemento de menú menuItem (dependiendo de lo que haya intentado).
Entonces, ¿cómo hago para que mi Menu encuentre mis dos DataTemplates ?
Resolví mi propia pregunta
Después de pasar varias horas buscando en la web, encontré muchos ejemplos que funcionan en contra de las intenciones naturales de WPF, pero ninguno funcionó con él.
Aquí se explica cómo trabajar con el control Menu y no contra él...
un poco de fondo
El control de Menu de WPF normalmente creará automáticamente objetos MenuItem cuando esté enlazado a una colección POCO, usando la propiedad ItemsSource .
Sin embargo, este comportamiento predeterminado se puede anular. Así es cómo...
La solución
Primero, debe crear una clase que derive de ItemContainerTemplateSelector . O usa la clase simple que he creado:
public class MenuItemContainerTemplateSelector : ItemContainerTemplateSelector { public override DataTemplate SelectTemplate(object item, ItemsControl parentItemsControl) { var key = new DataTemplateKey(item.GetType()); return (DataTemplate) parentItemsControl.FindResource(key); } } En segundo lugar, debe agregar una referencia a la clase MenuItemContainerTemplateSelector a su objeto de resources de Windows, así:
<Window.Resources> <Selectors:MenuItemContainerTemplateSelector x:Key="_menuItemContainerTemplateSelector" /> En tercer lugar, debe establecer dos propiedades ( UsesItemContainerTemplate y ItemContainerTemplateSelector ) tanto en Menu como en MenuItem (que se define en HierarchicalDataTemplate ).
Al igual que:
<HierarchicalDataTemplate DataType="{x:Type ViewModel:MenuItemViewModel}" ItemsSource="{Binding Children}"> <MenuItem Header="{Binding Header}" Command="{Binding Command}" UsesItemContainerTemplate ="true" ItemContainerTemplateSelector= "{StaticResource _menuItemContainerTemplateSelector}"/> </HierarchicalDataTemplate> <Menu DockPanel.Dock="Top" ItemsSource="{Binding MenuItems}" UsesItemContainerTemplate="True" ItemContainerTemplateSelector= "{StaticResource _menuItemContainerTemplateSelector}"> </Menu>Por qué funciona
Con fines de optimización, el Menu usa el indicador UsesItemContainerTemplate (que tiene un valor predeterminado de false ) para omitir la búsqueda de DataTemplate y solo devuelve un objeto MenuItem normal. Por lo tanto, necesitábamos establecer este valor en true y luego nuestro ItemContainerTemplateSelector funciona como se esperaba.
¡Feliz codificación!
Una solución sin el TemplateSelector:
proporcione ItemContainerTemplates en lugar de DataTemplates:
<ContextMenu ItemsSource="{Binding Path=MenuItems}" UsesItemContainerTemplate="True"> <ContextMenu.Resources> <ResourceDictionary> <ItemContainerTemplate DataType="{x:Type ViewModel:MenuItemViewModel }"> <MenuItem Header="{Binding Path=Header}" Command="{Binding Path=Command}" UsesItemContainerTemplate="True"> <MenuItem.Icon> <Image Source="{Binding Path=ImageSource}"/> </MenuItem.Icon> </MenuItem> </ItemContainerTemplate> <ItemContainerTemplate DataType="{x:Type ViewModel:SeparatorViewModel}"> <Separator > <Separator.Style> <Style TargetType="{x:Type Separator}" BasedOn="{StaticResource ResourceKey={x:Static MenuItem.SeparatorStyleKey}}"/> </Separator.Style> </Separator> </ItemContainerTemplate> </ResourceDictionary> </ContextMenu.Resources> </ContextMenu>Notas:
Otro enfoque es:
ControlTemplate del MenuItem para que use un control Separator en su lugarAl igual que:
<Menu ItemsSource="{Binding MenuItems}"> <Menu.Resources> <Style TargetType="{x:Type MenuItem}"> <Setter Property="Header" Value="{Binding Header}" /> <Setter Property="Command" Value="{Binding Command}" /> <Style.Triggers> <DataTrigger Binding="{Binding IsSeparator}" Value="True"> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="{x:Type MenuItem}"> <Separator /> </ControlTemplate> </Setter.Value> </Setter> </DataTrigger> </Style.Triggers> </Style> <HierarchicalDataTemplate DataType="{x:Type ViewModel:MenuItemViewModel}" ItemsSource="{Binding Children}" /> </Menu.Resources> </Menu>