如何正确绑定ViewModel(包括分隔符)到WPF的菜单?

时间:2015-07-03 20:46:32

标签: c# wpf mvvm menuitem

我正在使用MVVM,我希望将MenuViewModels的{​​{1}}列表数据绑定到我的maim菜单。其中包含一组菜单项和分隔符。

这是我的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; }
}

我的主窗口看起来像这样:

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

应该是非常简单的东西。不幸的是,菜单项看起来不对,或者分隔符为空menuItem(取决于我尝试过的内容)。

那么,如何让Menu找到我的两个DataTemplates

3 个答案:

答案 0 :(得分:16)

解决了我自己的问题

在网上花了几个小时后,我发现很多例子都是反对 WPF的自然意图,但没有一个能用它。

以下是如何使用 Menu控件工作而不是反对...

一点背景

WPF&#39; Menu控件将通常自动创建MenuItem个对象,当它绑定到POCO集合时,使用ItemsSource属性

但是,可以覆盖此默认行为 !以下是......

解决方案

首先,您必须创建一个派生自ItemContainerTemplateSelector的类。或者使用我创建的简单类:

public class MenuItemContainerTemplateSelector : ItemContainerTemplateSelector
{
    public override DataTemplate SelectTemplate(object item, ItemsControl parentItemsControl)
    {
        var key = new DataTemplateKey(item.GetType());
        return (DataTemplate) parentItemsControl.FindResource(key);
    }
}

其次,您必须将MenuItemContainerTemplateSelector类的引用添加到Windows resources对象中,如下所示:

<Window.Resources>
    <Selectors:MenuItemContainerTemplateSelector x:Key="_menuItemContainerTemplateSelector" />

第三,您必须在UsesItemContainerTemplateItemContainerTemplateSelector上设置两个属性(MenuMenuItem)(在HierarchicalDataTemplate中定义)。

像这样:

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

为何有效

出于优化目的,Menu使用UsesItemContainerTemplate标记(默认值为false)跳过DataTemplate查找并返回正常MenuItem 1}}对象。因此,我们需要将此值设置为true,然后我们的ItemContainerTemplateSelector按预期工作。

快乐的编码!

答案 1 :(得分:2)

没有TemplateSelector的解决方案:

提供ItemContainerTemplates而不是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>

注意:

  • 我没有尝试过儿童
  • 分隔符styled wrong:我必须手动重新应用样式

答案 2 :(得分:1)

另一种方法是:

  • 菜单项ViewModel上具有布尔属性,该属性指示项是否为分隔符
  • 使用基于此属性的触发器来更改ControlTemplate的{​​{1}},以便它改用MenuItem控件

像这样:

Separator