在 WPF 中的整个 TreeViewItem 行上显示 ContextMenu

Show ContextMenu on whole TreeViewItem line in WPF

我有一个具有异构节点类型的 TreeView。使用 HierarchicalDataTemplate 在 XAML 中配置每个节点类型。

一些节点有一个上下文菜单,这取决于节点的类型。 ContextMenus 在 XAML 中定义为静态资源,并附加到 HierarchicalDataTemplate 中的 DockPanel。

此外,我在以下 Whosebug 问题 中对 bendewey 描述的 TreeViewItem 使用了 ControlTemplate。此 ControlTemplate 已定义,以便在选择时突出显示完整的 TreeViewItem。 左键单击该行的任何部分,选择该项目。

相比之下,在 HierarchicalDataTemplate 中定义的上下文菜单仅适用于该行的右侧部分。

我正在寻找一种配置 ContextMenus 的方法,以便它们也可以在整行中使用。

这似乎需要将上下文菜单附加到 ControlTemplate 中的元素,并将 TemplateBinding 附加到 HierarchicalDataTemplate 中定义的内容,但我不知道该怎么做。

顺便说一句,Visual Studio 中的解决方案资源管理器正是具有这种行为。上下文菜单取决于节点类型,它在整个项目上可用,包括 expand/collapse 按钮的左侧。

(已编辑)您需要删除 TreeViewItemGrid 的两列样式:

<Window.Resources>
    <local:LeftMarginMultiplierConverter Length="19" x:Key="lengthConverter" />
    <SolidColorBrush x:Key="GlyphBrush" Color="#444" />
    <Style x:Key="ExpandCollapseToggleStyle" TargetType="ToggleButton">
        <Setter Property="Focusable" Value="False"/>
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="ToggleButton">
                    <Grid Width="15" Height="13"
      Background="Transparent">
                        <Path x:Name="ExpandPath"
        HorizontalAlignment="Left" 
        VerticalAlignment="Center" 
        Margin="1,1,1,1"
        Fill="{StaticResource GlyphBrush}"
        Data="M 4 0 L 8 4 L 4 8 Z"/>
                    </Grid>
                    <ControlTemplate.Triggers>
                        <Trigger Property="IsChecked"
           Value="True">
                            <Setter Property="Data"
            TargetName="ExpandPath"
            Value="M 0 4 L 8 4 L 4 8 Z"/>
                        </Trigger>
                    </ControlTemplate.Triggers>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>
    <Style x:Key="TreeViewItemFocusVisual">
        <Setter Property="Control.Template">
            <Setter.Value>
                <ControlTemplate>
                    <Border>
                        <Rectangle Margin="0,0,0,0"
             StrokeThickness="5"
             Stroke="Black"
             StrokeDashArray="1 2"
             Opacity="0"/>
                    </Border>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>
    <Style x:Key="{x:Type TreeViewItem}"
 TargetType="{x:Type TreeViewItem}">
        <Setter Property="Background"
  Value="Transparent"/>
        <Setter Property="HorizontalContentAlignment"
  Value="{Binding Path=HorizontalContentAlignment,
          RelativeSource={RelativeSource AncestorType={x:Type ItemsControl}}}"/>
        <Setter Property="VerticalContentAlignment"
  Value="{Binding Path=VerticalContentAlignment,
          RelativeSource={RelativeSource AncestorType={x:Type ItemsControl}}}"/>
        <Setter Property="Padding"
  Value="1,0,0,0"/>
        <Setter Property="Foreground"
  Value="{DynamicResource {x:Static SystemColors.ControlTextBrushKey}}"/>
        <Setter Property="FocusVisualStyle"
  Value="{StaticResource TreeViewItemFocusVisual}"/>
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="{x:Type TreeViewItem}">
                    <ControlTemplate.Resources>
                        <local:LeftMarginMultiplierConverter Length="19" x:Key="lengthConverter" /> 
                    </ControlTemplate.Resources>
                    <StackPanel>
                        <Border Name="Bd"
          Background="{TemplateBinding Background}"
          BorderBrush="{TemplateBinding BorderBrush}"
          BorderThickness="{TemplateBinding BorderThickness}"
          Padding="{TemplateBinding Padding}">
                            <Grid>
                                <ToggleButton Panel.ZIndex="2" x:Name="Expander"
                                              HorizontalAlignment="Left"
              Style="{StaticResource ExpandCollapseToggleStyle}"
              IsChecked="{Binding Path=IsExpanded,
                          RelativeSource={RelativeSource TemplatedParent}}"
              ClickMode="Press" 
                                              Margin="{Binding  Converter={StaticResource lengthConverter}, ConverterParameter=0,
                          RelativeSource={RelativeSource TemplatedParent}}"/>

                                <ContentPresenter x:Name="PART_Header" ContentSource="Header"
                  HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}">
                                </ContentPresenter>
                            </Grid>
                        </Border>
                        <ItemsPresenter x:Name="ItemsHost" />
                    </StackPanel>
                    <ControlTemplate.Triggers>
                        <Trigger Property="IsExpanded"
           Value="false">
                            <Setter TargetName="ItemsHost"
            Property="Visibility"
            Value="Collapsed"/>
                        </Trigger>
                        <Trigger Property="HasItems"
           Value="false">
                            <Setter TargetName="Expander"
            Property="Visibility"
            Value="Hidden"/>
                        </Trigger>
                        <MultiTrigger>
                            <MultiTrigger.Conditions>
                                <Condition Property="HasHeader"
                 Value="false"/>
                                <Condition Property="Width"
                 Value="Auto"/>
                            </MultiTrigger.Conditions>
                            <Setter TargetName="PART_Header"
            Property="MinWidth"
            Value="75"/>
                        </MultiTrigger>
                        <MultiTrigger>
                            <MultiTrigger.Conditions>
                                <Condition Property="HasHeader"
                 Value="false"/>
                                <Condition Property="Height"
                 Value="Auto"/>
                            </MultiTrigger.Conditions>
                            <Setter TargetName="PART_Header"
            Property="MinHeight"
            Value="19"/>
                        </MultiTrigger>
                        <Trigger Property="IsSelected"
           Value="true">
                            <Setter TargetName="Bd"
            Property="Background"
            Value="{DynamicResource {x:Static SystemColors.HighlightBrushKey}}"/>
                            <Setter Property="Foreground"
            Value="{DynamicResource {x:Static SystemColors.HighlightTextBrushKey}}"/>
                        </Trigger>
                        <MultiTrigger>
                            <MultiTrigger.Conditions>
                                <Condition Property="IsSelected"
                 Value="true"/>
                                <Condition Property="IsSelectionActive"
                 Value="false"/>
                            </MultiTrigger.Conditions>
                            <Setter TargetName="Bd"
            Property="Background"
            Value="{DynamicResource {x:Static SystemColors.ControlBrushKey}}"/>
                            <Setter Property="Foreground"
            Value="{DynamicResource {x:Static SystemColors.ControlTextBrushKey}}"/>
                        </MultiTrigger>
                        <Trigger Property="IsEnabled"
           Value="false">
                            <Setter Property="Foreground"
            Value="{DynamicResource {x:Static SystemColors.GrayTextBrushKey}}"/>
                        </Trigger>
                    </ControlTemplate.Triggers>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>
</Window.Resources>

但是,这会扭曲Header-Items的安排。因此,需要调整HierarchicalDataTemplate部分的Margin:

<TreeView Margin="50" HorizontalContentAlignment="Stretch" DataContext="{Binding}" ItemsSource="{Binding Models}">
    <TreeView.ItemTemplate>
        <HierarchicalDataTemplate DataType="{x:Type local:Model}" ItemsSource="{Binding Models}">
            <Border Background="Transparent">
                <TextBlock Margin="{Binding  Converter={StaticResource lengthConverter}, ConverterParameter=1,
                                                                 RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=TreeViewItem}}" 
                           Text="{Binding Name}" />
                <Border.ContextMenu>
                    <ContextMenu>
                        <MenuItem Header="dddd"/>
                    </ContextMenu>
                </Border.ContextMenu>
            </Border>
        </HierarchicalDataTemplate>
    </TreeView.ItemTemplate>
</TreeView>

请注意,您应该调整转换器以考虑所需的额外余量:

public class LeftMarginMultiplierConverter : IValueConverter
{
    public double Length { get; set; } 

    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        var item = value as TreeViewItem;
        if (item == null)
            return new Thickness(0);
        int extra = int.Parse(parameter.ToString());
        var t = item.GetDepth();
        return new Thickness(Length * (item.GetDepth() + extra), 0, 0, 0);
    }

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        throw new System.NotImplementedException();
    }
}

如果转换器参数为1,它会在项目的深度上加一分。我将它用于 Header 部分。

另一种方法可能是在 TreeViewItem StyleContentPresenter 中添加 DataTemplate。虽然我不知道如何绑定 ContextMenu,但我更喜欢这个。