【问题标题】:How to correctly bind a ViewModel (which Include Separators) to WPF's Menu?如何正确地将 ViewModel(包括分隔符)绑定到 WPF 的菜单?
【发布时间】:2015-07-03 20:46:31
【问题描述】:

我正在使用 MVVM,我想将我的 MenuViewModels 列表数据绑定到我的主菜单。它由一组菜单项和分隔符组成。

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

【问题讨论】:

    标签: c# wpf mvvm menuitem


    【解决方案1】:

    解决了我自己的问题

    在网上搜索了几个小时后,我发现了很多违反 WPF 的自然意图的示例,但没有一个适用于它。

    下面是如何使用Menu 控件而不是反对它...

    一点背景

    WPF 的Menu 控件通常会在绑定到 POCO 集合时自动为您创建 MenuItem 对象,使用 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" />
    

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

    像这样:

        <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 对象。因此,我们需要将此值设置为true,然后我们的ItemContainerTemplateSelector 才能按预期工作。

    编码愉快!

    【讨论】:

    • 我在 parentItemsControl.FindResource() 上收到“无法在调度程序处理暂停时执行此操作”的分隔符。没有分隔符一切正常。有谁知道为什么?
    • 对于使用此解决方案和子菜单的任何人,请确保在您的MenuItem 中设置ItemsSource="{Binding Children}"。只需将其设置为 HierarchialDataTemplate 并不能解决问题...这里有很好的提示,但谢谢!
    【解决方案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:我不得不手动重新应用样式

    【讨论】:

      【解决方案3】:

      另一种方法是:

      • 在您的菜单项 ViewModel 上有一个布尔属性,用于指示某项是否为分隔符
      • 使用基于此属性的触发器来更改MenuItemControlTemplate,使其改用Separator 控件

      像这样:

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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2011-03-01
        • 1970-01-01
        • 2010-10-14
        • 1970-01-01
        • 2011-01-13
        • 2017-12-04
        • 1970-01-01
        相关资源
        最近更新 更多