Search code examples
wpfxamldatatemplate

Why are hotkeys not working when using DataTemplate for dynamic menu?


I'm able to bind a collection of my own type to a menu. But I'm struggling with the hotkeys. Either the hotkeys show up but don't work or the hotkeys don't show up but work.

Do you have any idea what goes wrong here?

Here is the stripped-down code:

    public class Category : DependencyObject
{
    public string Caption
    {
        get { return (string)GetValue(CaptionProperty); }
        set { SetValue(CaptionProperty, value); }
    }
    public static readonly DependencyProperty CaptionProperty =
        DependencyProperty.Register("Caption", typeof(string), typeof(Category), new PropertyMetadata(null));

    public ObservableCollection<Item> SubItems
    {
        get { return (ObservableCollection<Item>)GetValue(SubItemsProperty); }
        set { SetValue(SubItemsProperty, value); }
    }
    public static readonly DependencyProperty SubItemsProperty =
        DependencyProperty.Register("SubItems", typeof(ObservableCollection<Item>), typeof(Category), new PropertyMetadata(null));
}
public class Item : DependencyObject
{
    public string Header
    {
        get { return (string)GetValue(HeaderProperty); }
        set { SetValue(HeaderProperty, value); }
    }
    public static readonly DependencyProperty HeaderProperty =
        DependencyProperty.Register("Header", typeof(string), typeof(Item), new PropertyMetadata(null));
}
public class MenuViewModel : DependencyObject
{
    public ObservableCollection<Category> MenuCategories
    {
        get { return (ObservableCollection<Category>)GetValue(MenuCategoriesProperty); }
        set { SetValue(MenuCategoriesProperty, value); }
    }
    public static readonly DependencyProperty MenuCategoriesProperty =
        DependencyProperty.Register("MenuCategories", typeof(ObservableCollection<Category>), typeof(MenuViewModel), new PropertyMetadata(null));

    public MenuViewModel()
    {
        MenuCategories = new ObservableCollection<Category>()
        {
            new Category() {Caption = "_One", SubItems = new ObservableCollection<Item>() { new Item() { Header = "_one"}, new Item() { Header = "t_wo" }, new Item() { Header = "_three" } } },
            new Category() {Caption = "_Two", SubItems = new ObservableCollection<Item>() { new Item() { Header = "_one"}, new Item() { Header = "t_wo" }, new Item() { Header = "_three" } } },
        };
    }
}
public partial class Test1Window : Window
{
    public Test1Window()
    {
        InitializeComponent();
        DataContext = new MenuViewModel();
    }
}
public class MenuItemContainerTemplateSelector : ItemContainerTemplateSelector
{
    public override DataTemplate SelectTemplate(object item, ItemsControl parentItemsControl)
    {
        var key = new DataTemplateKey(item.GetType());
        return (DataTemplate)parentItemsControl.FindResource(key);
    }
}

And the xaml 1. try:

<Window x:Class="DynamicMenuTest.Test1Window"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:local="clr-namespace:DynamicMenuTest"
    mc:Ignorable="d"
    Title="Test1Window" Height="300" Width="300">
<Window.Resources>

    <local:MenuItemContainerTemplateSelector x:Key="MenuItemContainerTemplateSelector"/>

    <DataTemplate DataType="{x:Type local:Item}">
        <MenuItem Header="{Binding Header}">
            <MenuItem.HeaderTemplate>
                <DataTemplate>
                    <AccessText VerticalAlignment="Center" Text="{Binding}" />
                </DataTemplate>
            </MenuItem.HeaderTemplate>
        </MenuItem>
    </DataTemplate>

    <HierarchicalDataTemplate DataType="{x:Type local:Category}" ItemsSource="{Binding SubItems}">
        <MenuItem VerticalContentAlignment="Center">
            <!-- Hotkey works but doesn't show up -->
            <MenuItem.Header>
                <AccessText Text="{Binding Caption}"/>
            </MenuItem.Header>
        </MenuItem>
    </HierarchicalDataTemplate>

</Window.Resources>
<Grid>
    <Menu ItemsSource="{Binding MenuCategories}"
        UsesItemContainerTemplate ="true"
        ItemContainerTemplateSelector="{StaticResource MenuItemContainerTemplateSelector}"
    />
</Grid>

Xaml 2. try:

<Window x:Class="DynamicMenuTest.Test2Window"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:local="clr-namespace:DynamicMenuTest"
    mc:Ignorable="d"
    Title="Test2Window" Height="300" Width="300">
<Window.Resources>

    <local:MenuItemContainerTemplateSelector x:Key="MenuItemContainerTemplateSelector"/>

    <DataTemplate DataType="{x:Type local:Item}">
        <MenuItem Header="{Binding Header}">
            <MenuItem.HeaderTemplate>
                <DataTemplate>
                    <AccessText VerticalAlignment="Center" Text="{Binding}" />
                </DataTemplate>
            </MenuItem.HeaderTemplate>
        </MenuItem>
    </DataTemplate>

    <HierarchicalDataTemplate  DataType="{x:Type local:Category}" ItemsSource="{Binding SubItems}">
        <MenuItem 
            Header="{Binding Caption}"
            VerticalContentAlignment="Center">

            <!-- Hotkey shows up but menu not working -->
            <MenuItem.HeaderTemplate>
                <DataTemplate>
                    <ContentPresenter RecognizesAccessKey="True" Content="{Binding }" VerticalAlignment="Center" Margin="4,0"/>
                </DataTemplate>
            </MenuItem.HeaderTemplate>
        </MenuItem>
    </HierarchicalDataTemplate>

</Window.Resources>
<Grid>
    <Menu ItemsSource="{Binding MenuCategories}"
        UsesItemContainerTemplate ="true"
        ItemContainerTemplateSelector="{StaticResource MenuItemContainerTemplateSelector}"
    />
</Grid>

Xaml 3. try:

<Window x:Class="DynamicMenuTest.Test3Window"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:local="clr-namespace:DynamicMenuTest"
    mc:Ignorable="d"
    Title="Test3Window" Height="300" Width="300">
<Window.Resources>

    <local:MenuItemContainerTemplateSelector x:Key="MenuItemContainerTemplateSelector"/>

    <DataTemplate DataType="{x:Type local:Item}">
        <MenuItem Header="{Binding Header}">
            <MenuItem.HeaderTemplate>
                <DataTemplate>
                    <AccessText VerticalAlignment="Center" Text="{Binding}" />
                </DataTemplate>
            </MenuItem.HeaderTemplate>
        </MenuItem>
    </DataTemplate>

    <HierarchicalDataTemplate  DataType="{x:Type local:Category}" ItemsSource="{Binding SubItems}">
        <MenuItem 
            Header="{Binding Caption}"
            VerticalContentAlignment="Center">
            <!-- Hotkey shows up but menu doesn't work -->
            <MenuItem.HeaderTemplate>
                <DataTemplate>
                    <AccessText Text="{Binding}" VerticalAlignment="Center" Margin="4,0"/>
                </DataTemplate>
            </MenuItem.HeaderTemplate>
        </MenuItem>
    </HierarchicalDataTemplate>

</Window.Resources>
<Grid>
    <Menu ItemsSource="{Binding MenuCategories}"
        UsesItemContainerTemplate ="true"
        ItemContainerTemplateSelector="{StaticResource MenuItemContainerTemplateSelector}"
    />
</Grid>


Solution

  • Your DataTemplate and HierarchicalDataTemplate describe how the header content of your MenuItem should look like, so nesting another MenuItem there is a bad idea. Instead, keep it really simple and enjoy:

        <DataTemplate DataType="{x:Type local:Item}">
            <AccessText VerticalAlignment="Center" Text="{Binding Header}" />
        </DataTemplate>
    
        <HierarchicalDataTemplate DataType="{x:Type local:Category}" ItemsSource="{Binding SubItems}">
            <AccessText Text="{Binding Caption}"/>
        </HierarchicalDataTemplate>