在 ContextMenu 中与 HierarchicalDataTemplate 作斗争

Struggling with HierarchicalDataTemplate in ContextMenu

我希望能够在我的视图模型中将 ContextMenuItemsSource 绑定到 Collection,在 [= 中显示 Separators 14=],ItemsSource 必须是分层的(层次结构的每一层看起来都一样)。

中,我设法能够在数据绑定 ContextMenu 中显示菜单项和分隔符,但现在我很难使 ItemsSource 分层。

现在不知道怎么回事,能不能指教一下?

这是我的代码(简化为简短,但有效):

MenuItemViewModel.vb

Public Class MenuItemViewModel
    Implements ICommand

    Public Event CanExecuteChanged As EventHandler Implements ICommand.CanExecuteChanged

    Public Property IsSeparator As Boolean
    Public Property Caption As String
    Private ReadOnly _subItems As List(Of MenuItemViewModel)

    Public Sub New(createItems As Boolean, level As Byte)
        _subItems = New List(Of MenuItemViewModel)

        If createItems Then
            _subItems.Add(New MenuItemViewModel(level < 4, level + 1) With {.Caption = "SubItem 1"})
            _subItems.Add(New MenuItemViewModel(False, level + 1) With {.IsSeparator = True, .Caption = "SubSep 1"})
            _subItems.Add(New MenuItemViewModel(level < 4, level + 1) With {.Caption = "SubItem 2"})
        End If
    End Sub

    Public ReadOnly Property SubItems As List(Of MenuItemViewModel)
        Get
            Return _subItems
        End Get
    End Property

    Public ReadOnly Property Command As ICommand
        Get
            Return Me
        End Get
    End Property

    Public Sub Execute(ByVal parameter As Object) Implements ICommand.Execute
        MessageBox.Show(Me.Caption)
    End Sub

    Public Function CanExecute(ByVal parameter As Object) As Boolean Implements ICommand.CanExecute
        Return True
    End Function

End Class

每个级别上每个菜单项的视图模型都有一个 Caption 显示在上下文菜单中,一个 IsSeparator 标志指示它是分隔符还是功能菜单项,一个 Command 作为功能菜单项时要绑定,当然还有 SubItems collection 包含功能菜单项和分隔符到某个层次结构级别。

MainViewModel.vb

Public Class MainViewModel
    Private ReadOnly _items As List(Of MenuItemViewModel)

    Public Sub New()
        _items = New List(Of MenuItemViewModel)
        _items.Add(New MenuItemViewModel(True, 0) With {.Caption = "Item 1"})
        _items.Add(New MenuItemViewModel(False, 0) With {.IsSeparator = True, .Caption = "Sep 1"})
        _items.Add(New MenuItemViewModel(True, 0) With {.Caption = "Item 2"})
        _items.Add(New MenuItemViewModel(True, 0) With {.Caption = "Item 3"})
        _items.Add(New MenuItemViewModel(False, 0) With {.IsSeparator = True, .Caption = "Sep 2"})
        _items.Add(New MenuItemViewModel(True, 0) With {.Caption = "Item 4"})
    End Sub

    Public ReadOnly Property Items As List(Of MenuItemViewModel)
        Get
            Return _items
        End Get
    End Property

End Class

主视图模型只有 Items collection 包含功能菜单项和分隔符。

MainWindow.xaml

<Window x:Class="MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:WpfApp3"
        mc:Ignorable="d"
        d:DataContext="{d:DesignInstance local:MainViewModel, IsDesignTimeCreatable=True}"
        Title="MainWindow" Height="450" Width="800">

    <Window.DataContext>
        <local:MainViewModel />
    </Window.DataContext>

    <Window.Resources>
        <ControlTemplate x:Key="mist" TargetType="{x:Type MenuItem}">
            <Separator />
        </ControlTemplate>

        <ControlTemplate x:Key="mict" TargetType="{x:Type MenuItem}">
            <MenuItem Header="{Binding Caption}" Command="{Binding Command}" ItemsSource="{Binding SubItems}" />
        </ControlTemplate>

        <Style x:Key="cmics" TargetType="{x:Type MenuItem}">
            <Setter Property="Template" Value="{StaticResource mict}" />
            <Style.Triggers>
                <DataTrigger Binding="{Binding IsSeparator}" Value="True">
                    <Setter Property="Template" Value="{StaticResource mist}" />
                </DataTrigger>
            </Style.Triggers>
        </Style>
    </Window.Resources>

    <Grid>
        <TextBox HorizontalAlignment="Center" VerticalAlignment="Center" Text="Right click me">
            <TextBox.ContextMenu>
                <ContextMenu ItemsSource="{Binding Items}" ItemContainerStyle="{StaticResource cmics}">
                    <ContextMenu.ItemTemplate>
                        <HierarchicalDataTemplate DataType="{x:Type local:MenuItemViewModel}" ItemsSource="{Binding SubItems}" />
                    </ContextMenu.ItemTemplate>
                </ContextMenu>
            </TextBox.ContextMenu>
        </TextBox>
    </Grid>
</Window>

window资源包含两个ControlTemplates "mist"和"mict"以及一个Style "cmics"在这两个ControlTemplates 取决于 IsSeparator 标志的值。只要 ItemsSource 不是分层的(参见 ),这就可以正常工作。

如果我的 Style "cmics" 仅附加到 ContextMenuItemContainerStyle (如我的示例代码中所示),那么它看起来像这样:

第一层有效,但其他层无效。将我的 Style "cmics" 附加到 HierarchicalDataTemplateItemContainerStyle 时也不会改变。

如果我只将我的 Style "cmics" 附加到 HierarchicalDataTemplate 那么它看起来像这样:

第一级不显示标题和分隔符,第二级有效,其他级别无效。

那么,我怎样才能说服 ContextMenu 使用我的 Style "cmics" 作为每个层级的 ItemContainerStyle

我刚刚在 Xaml 部分对您的 (TextBox) 进行了一些更改。看看这个,

<TextBox HorizontalAlignment="Center" VerticalAlignment="Center" Text="Right click me">
            <TextBox.Resources>
                <HierarchicalDataTemplate DataType="{x:Type local:MenuItemViewModel}" ItemsSource="{Binding SubItems}">
                    <Button Content="{Binding Caption}" Command="{Binding Command}" Background="Red"/>
                </HierarchicalDataTemplate>
            </TextBox.Resources>
            <TextBox.ContextMenu>
                <ContextMenu ItemsSource="{Binding Items}" ItemContainerStyle="{StaticResource cmics}"/>
            </TextBox.ContextMenu>
        </TextBox>

基本上我已经删除了您的 ContexttMenu ItemTemplate 并在 TextBox.Resources 下添加了一个分层数据模板。

在数据模板中,我刚刚添加了一个单选按钮。您可以根据需要更改内容。

如果这能解决您的问题或需要任何其他帮助,请告诉我。

我找到了答案

我不得不为分隔符创建一个空的视图模型和一个从 ItemContainerTemplateSelector 派生到 return 的 class 属于类型的 DataTemplate菜单项("MenuItemViewModel" 或 "SeparatorViewModel")。

链接的文章应该是不言自明的。