如何使项目突出显示边框跨越完整的 TreeView 宽度(TreeViewItem 选择行为类似于 VS 解决方案资源管理器)?

How to make item highlight border span the complete TreeView width (TreeViewItem selection behavior like VS Solution Explorer)?

在默认 TreeView 样式中选择时,TreeViewItem 的左侧有空白:

所以我想编辑 TreeViewItem 的样式,使其看起来像 VS Solution 样式:

我知道 TreeViewItem 的默认 ControlTemplate 是:

<Grid>
    <Grid.ColumnDefinitions>
        <ColumnDefinition MinWidth="19" Width="Auto"/>
        <ColumnDefinition Width="Auto"/>
        <ColumnDefinition Width="*"/>
    </Grid.ColumnDefinitions>
    <Grid.RowDefinitions>
        <RowDefinition Height="Auto"/>
        <RowDefinition/>
    </Grid.RowDefinitions>
    <ToggleButton x:Name="Expander" ClickMode="Press" IsChecked="{Binding IsExpanded, RelativeSource={RelativeSource TemplatedParent}}" Style="{StaticResource ExpandCollapseToggleStyle}"/>
    <Border x:Name="Bd" Background="{TemplateBinding Background}" BorderThickness="{TemplateBinding BorderThickness}" BorderBrush="{TemplateBinding BorderBrush}" Grid.Column="1" Padding="{TemplateBinding Padding}" SnapsToDevicePixels="true">
        <ContentPresenter x:Name="PART_Header" ContentSource="Header" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"/>
    </Border>
    <ItemsPresenter x:Name="ItemsHost" Grid.ColumnSpan="2" Grid.Column="1" Grid.Row="1"/>
</Grid>

左边距来自Grid.ColumnSpan="2" Grid.Column="1" Grid.Row="1"

那么如何让它看起来像 VS Solution 风格(保持缩进并且不要在开头留下 space 背景色)?

一个非常快速和简单的解决方案需要添加一个自定义高亮边框并给它一个负 Margin

以下示例通过侦听 Selected 路由事件动态计算负左边距。要使其正常工作,突出显示 Border 必须跨越所有列并分配名称 "HighlightBorder"。主机 Grid 必须命名为 "RootGrid"。我们需要访问根网格以获取实际缩进。

TreeViewItem 风格:
请注意,样式缺少处理 ToggleButtonItemsPresenter.

可见性的基本触发器
<Style TargetType="TreeViewItem">
  <EventSetter Event="Selected" Handler="AdjustHighlightBorder_OnSelectedItemChanged" />
  <Setter Property="Template">
    <Setter.Value>
      <ControlTemplate TargetType="TreeViewItem">
        <Border>
          <Grid x:Name="RootGrid">
            <Grid.ColumnDefinitions>
              <ColumnDefinition MinWidth="19" Width="Auto" />
              <ColumnDefinition Width="*" />
            </Grid.ColumnDefinitions>
            <Grid.RowDefinitions>
              <RowDefinition Height="Auto" />
              <RowDefinition />
            </Grid.RowDefinitions>
            <Border x:Name="HighlightBorder" 
                    Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="2" 
                    Margin="0,0,0,0"
                    Background="DodgerBlue" 
                    Visibility="Hidden" />

            <ToggleButton x:Name="Expander" ClickMode="Press" Content="+"
                          IsChecked="{Binding IsExpanded, RelativeSource={RelativeSource TemplatedParent}}" />
            <Border x:Name="Bd" Background="{TemplateBinding Background}"
                    BorderThickness="{TemplateBinding BorderThickness}"
                    BorderBrush="{TemplateBinding BorderBrush}" Grid.Column="1"
                    Padding="{TemplateBinding Padding}" SnapsToDevicePixels="true">
              <ContentPresenter x:Name="PART_Header" ContentSource="Header"
                                HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
                                SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" />
            </Border>
            <ItemsPresenter x:Name="ItemsHost" Grid.Column="1" Grid.Row="1" />
          </Grid>

        </Border>
        <ControlTemplate.Triggers>
          <Trigger Property="IsSelected" Value="True">
            <Setter TargetName="HighlightBorder" Property="Visibility" Value="Visible" />
          </Trigger>
        </ControlTemplate.Triggers>
      </ControlTemplate>
    </Setter.Value>
  </Setter>
</Style>

Selected 路由事件的处理程序:
判断当前选中节点的层级,计算高亮边框的实际缩进量。

private void AdjustHighlightBorder_OnSelectedItemChanged(object sender, RoutedEventArgs routedEventArgs)
{
  if (!(routedEventArgs.Source is TreeViewItem treeViewItemContainer 
        && treeViewItemContainer.IsSelected))
  {
    return;
  }

  var highlightBorder = treeViewItemContainer.Template.FindName("HighlightBorder", treeViewItemContainer) as Border;
  var rootGrid = treeViewItemContainer.Template.FindName("RootGrid", treeViewItemContainer) as Grid;
  double indention = rootGrid.ColumnDefinitions.First().ActualWidth;

  // Determine the level of currently selected node
  int treeLevel = 0;
  DependencyObject parent = treeViewItemContainer;
  do
  {
    parent = VisualTreeHelper.GetParent(parent);
    switch (parent)
    {
      case TreeViewItem _:
        treeLevel++;
        break;
      case TreeView _:

        // Root node reached --> apply indention
        Thickness highlightBorderMargin = highlightBorder.Margin;
        highlightBorderMargin.Left = treeLevel * -indention;
        highlightBorder.Margin = highlightBorderMargin;
        return;
    }

  } while (parent != null);
}