How to correctly bind a ViewModel (which Include Separators) to WPF's Menu?

谁都会走 提交于 2019-12-03 09:38:28

问题


I'm using MVVM and I want to data bind my list of MenuViewModels to my maim menu. Which consists of a set of menu items and separators.

Here's my MenuItemViewModel code:

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; }
}

And my Main window looks like this:

<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>

Should be very simple stuff. Unfortunately, either the menu item looks wrong or the separator is an empty menuItem (depending on what I've tried).

So, how do I get my Menu to find my two DataTemplates?


回答1:


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!




回答2:


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


来源:https://stackoverflow.com/questions/31214027/how-to-correctly-bind-a-viewmodel-which-include-separators-to-wpfs-menu

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!