如何将 ViewModel(包括分隔符)正确绑定到 WPF 的菜单?
How to correctly bind a ViewModel (which Include Separators) to WPF's Menu?
我正在使用 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 看起来像这样:
<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
?
解决了我自己的问题
在网上搜索了几个小时后,我发现很多例子反对 WPF 的自然意图,但none 与它一起工作。
以下是与 Menu
控制而不是对抗它的方法...
一点背景知识
WPF 的 Menu
控件在绑定到 POCO 集合时,通常 自动为您创建 MenuItem
个对象,使用 ItemsSource
属性.
但是,此默认行为可以被覆盖!方法如下...
解决方案
首先,您必须创建一个派生自 ItemContainerTemplateSelector
的 class。或者使用我创建的简单 class:
public class MenuItemContainerTemplateSelector : ItemContainerTemplateSelector
{
public override DataTemplate SelectTemplate(object item, ItemsControl parentItemsControl)
{
var key = new DataTemplateKey(item.GetType());
return (DataTemplate) parentItemsControl.FindResource(key);
}
}
其次,您必须将对 MenuItemContainerTemplateSelector
class 的引用添加到您的 Windows resources
对象,如下所示:
<Window.Resources>
<Selectors:MenuItemContainerTemplateSelector x:Key="_menuItemContainerTemplateSelector" />
第三,您必须在 Menu
和 MenuItem
上设置两个属性(UsesItemContainerTemplate
和 ItemContainerTemplateSelector
(在 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
查找,仅 returns 一个普通的 MenuItem
对象。因此,我们需要将此值设置为 true
,然后我们的 ItemContainerTemplateSelector
会按预期工作。
编码愉快!
没有 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:我不得不手动重新应用样式
另一种方法是:
- 在您的菜单项 ViewModel 上有一个布尔值 属性,指示某个项目是否为分隔符
- 使用基于此 属性 的触发器来更改
MenuItem
的 ControlTemplate
,以便它使用 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>
我正在使用 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 看起来像这样:
<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
?
解决了我自己的问题
在网上搜索了几个小时后,我发现很多例子反对 WPF 的自然意图,但none 与它一起工作。
以下是与 Menu
控制而不是对抗它的方法...
一点背景知识
WPF 的 Menu
控件在绑定到 POCO 集合时,通常 自动为您创建 MenuItem
个对象,使用 ItemsSource
属性.
但是,此默认行为可以被覆盖!方法如下...
解决方案
首先,您必须创建一个派生自 ItemContainerTemplateSelector
的 class。或者使用我创建的简单 class:
public class MenuItemContainerTemplateSelector : ItemContainerTemplateSelector
{
public override DataTemplate SelectTemplate(object item, ItemsControl parentItemsControl)
{
var key = new DataTemplateKey(item.GetType());
return (DataTemplate) parentItemsControl.FindResource(key);
}
}
其次,您必须将对 MenuItemContainerTemplateSelector
class 的引用添加到您的 Windows resources
对象,如下所示:
<Window.Resources>
<Selectors:MenuItemContainerTemplateSelector x:Key="_menuItemContainerTemplateSelector" />
第三,您必须在 Menu
和 MenuItem
上设置两个属性(UsesItemContainerTemplate
和 ItemContainerTemplateSelector
(在 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
查找,仅 returns 一个普通的 MenuItem
对象。因此,我们需要将此值设置为 true
,然后我们的 ItemContainerTemplateSelector
会按预期工作。
编码愉快!
没有 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:我不得不手动重新应用样式
另一种方法是:
- 在您的菜单项 ViewModel 上有一个布尔值 属性,指示某个项目是否为分隔符
- 使用基于此 属性 的触发器来更改
MenuItem
的ControlTemplate
,以便它使用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>