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