WPF 通过 MVVM 动态构建 ContextMenu
WPF Build ContextMenu dynamically via MVVM
我有一个 TreeView,我在其中显示通过 TreeViews HierarchicalDataTemplate.ItemsSource
绑定的项目。 TreeView 的上下文菜单根据选择的项目而变化。菜单项取决于所选项目。这意味着:上下文菜单是完全动态构建的。为此,我写了一个 MenuItemModel
class,作为菜单项的业务对象。像这样:
public class MenuItemModel : ViewModelBase
{
public string Header { get; set; }
public string Icon { get; set; }
public ObservableCollection<MenuItemModel> ChildItems { get; set; }
public UiCommand Command { get; set; }
}
到目前为止一切顺利。但是现在我有两个问题:
问题1如何在菜单中显示分隔符?我还有另一个 class SeparatorMenuItemModel
打算用于分隔符。但在那种情况下,我的上下文菜单需要包含 Separator
而不是 MenuItem
。我该怎么做?
问题 2 我尝试使用 DataTemplate
自定义菜单项的显示方式。但这并没有改变菜单本身,只是改变了内容部分。我必须为此使用 ControlTemplate
,但是我怎样才能让我的菜单更改 ControlTemplate
就像我可以用 DataTemplate
做的那样?
您似乎在此处使用 classic 组合模式。至于 SeparatorMenuItemModel,让 MenuItemModel 和 SeparatorMenuItemModel 都继承自一个公共 class 或接口,例如 IMenuItemModel(或 MenuItemModelBase)怎么样?那么你可以使用
public ObservableCollection<IMenuItemModel> ChildItems {get; set;}
包含两者。
不确定我是否完全理解问题 2,但了解 ControlTemplate 完全 替换控件的可视化树很重要;本质上,您能够(并且必须)从头开始完全重建控件。通常这比您真正想做的要多得多。
我找到了解决这两个问题的方法。
第一:我创造了两种风格。一个用于 MenuItemModel
类型,另一个用于 SeparatorMenuItemModel
:
类型
<Style x:Key="theMenuItemStyle" TargetType="{x:Type MenuItem}">
...
</Style>
<Style x:Key="theSeparatorStyle" TargetType="{x:Type MenuItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate> ... </ControlTemplate>
</Setter.Value>
</Setter>
</Style>
我还使用样式更改了我的控件模板(除了其他一些在这里不重要的东西)。
然后我使用了一个 StyleSelector,它根据显示的项目类型选择要应用的样式。像这样:
<TreeView.ContextMenu>
<ContextMenu ItemsSource="{Binding ContextMenuItemRoot.ChildItems}"
ItemContainerStyleSelector="{StaticResource MenuItemStyleSelector}" />
</TreeView.ContextMenu>
而 StyleSelector 本身是这样定义的:
public class MenuItemStyleSelector : StyleSelector
{
public Style MenuItemStyle { get; set; }
public Style SeparatorStyle { get; set; }
public override Style SelectStyle(object item, DependencyObject container)
{
if (item is SeparatorMenuItemModel)
return SeparatorItemStyle;
return MenuItemStyle;
}
}
我有一个 TreeView,我在其中显示通过 TreeViews HierarchicalDataTemplate.ItemsSource
绑定的项目。 TreeView 的上下文菜单根据选择的项目而变化。菜单项取决于所选项目。这意味着:上下文菜单是完全动态构建的。为此,我写了一个 MenuItemModel
class,作为菜单项的业务对象。像这样:
public class MenuItemModel : ViewModelBase
{
public string Header { get; set; }
public string Icon { get; set; }
public ObservableCollection<MenuItemModel> ChildItems { get; set; }
public UiCommand Command { get; set; }
}
到目前为止一切顺利。但是现在我有两个问题:
问题1如何在菜单中显示分隔符?我还有另一个 class SeparatorMenuItemModel
打算用于分隔符。但在那种情况下,我的上下文菜单需要包含 Separator
而不是 MenuItem
。我该怎么做?
问题 2 我尝试使用 DataTemplate
自定义菜单项的显示方式。但这并没有改变菜单本身,只是改变了内容部分。我必须为此使用 ControlTemplate
,但是我怎样才能让我的菜单更改 ControlTemplate
就像我可以用 DataTemplate
做的那样?
您似乎在此处使用 classic 组合模式。至于 SeparatorMenuItemModel,让 MenuItemModel 和 SeparatorMenuItemModel 都继承自一个公共 class 或接口,例如 IMenuItemModel(或 MenuItemModelBase)怎么样?那么你可以使用
public ObservableCollection<IMenuItemModel> ChildItems {get; set;}
包含两者。
不确定我是否完全理解问题 2,但了解 ControlTemplate 完全 替换控件的可视化树很重要;本质上,您能够(并且必须)从头开始完全重建控件。通常这比您真正想做的要多得多。
我找到了解决这两个问题的方法。
第一:我创造了两种风格。一个用于 MenuItemModel
类型,另一个用于 SeparatorMenuItemModel
:
<Style x:Key="theMenuItemStyle" TargetType="{x:Type MenuItem}">
...
</Style>
<Style x:Key="theSeparatorStyle" TargetType="{x:Type MenuItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate> ... </ControlTemplate>
</Setter.Value>
</Setter>
</Style>
我还使用样式更改了我的控件模板(除了其他一些在这里不重要的东西)。
然后我使用了一个 StyleSelector,它根据显示的项目类型选择要应用的样式。像这样:
<TreeView.ContextMenu>
<ContextMenu ItemsSource="{Binding ContextMenuItemRoot.ChildItems}"
ItemContainerStyleSelector="{StaticResource MenuItemStyleSelector}" />
</TreeView.ContextMenu>
而 StyleSelector 本身是这样定义的:
public class MenuItemStyleSelector : StyleSelector
{
public Style MenuItemStyle { get; set; }
public Style SeparatorStyle { get; set; }
public override Style SelectStyle(object item, DependencyObject container)
{
if (item is SeparatorMenuItemModel)
return SeparatorItemStyle;
return MenuItemStyle;
}
}