How to correctly bind a ViewModel (which Include Separators) to WPF's Menu?
Solved my own question
After spending several hours searching the web, I found lots of examples that work against the WPF's natural intentions but none that worked with it.
Here's how to work with the Menu
control and not against it...
A little Background
WPF's Menu
control will normally auto create MenuItem
objects for you when it is binded to a POCO collection, using the ItemsSource
property.
However, this default behavior can be overridden! Here's how...
The Solution
First, you must create a class that derives from ItemContainerTemplateSelector
. Or use the simple class I've created:
public class MenuItemContainerTemplateSelector : ItemContainerTemplateSelector
{
public override DataTemplate SelectTemplate(object item, ItemsControl parentItemsControl)
{
var key = new DataTemplateKey(item.GetType());
return (DataTemplate) parentItemsControl.FindResource(key);
}
}
Second, you must add a reference to the MenuItemContainerTemplateSelector
class to your Windows resources
object, like so:
<Window.Resources>
<Selectors:MenuItemContainerTemplateSelector x:Key="_menuItemContainerTemplateSelector" />
Third, you must set two properties (UsesItemContainerTemplate
, and ItemContainerTemplateSelector
) on both the Menu
and the MenuItem
(which is defined in the HierarchicalDataTemplate
).
Like so:
<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>
Why it Works
For optimization purposes, the Menu
uses the UsesItemContainerTemplate
flag (which has a default value of false
) to skip the DataTemplate
lookup and just returns a normal MenuItem
object. Therefore, we needed to set this value to true
and then our ItemContainerTemplateSelector
works as expected.
Happy Coding!
A solution without the TemplateSelector:
provide ItemContainerTemplates instead of the 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>
Notes:
- I haven't tried Children
- the separator styled wrong: I had to manually re-apply the style