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

问题描述

在默认 TreeView 样式中选中时,TreeViewItem 左侧有一个边距:

enter image description here

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

enter image description here

我知道 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 解决方案风格(保持缩进并且不要在开头留下背景颜色的空间)?

解决方法

一个非常快速和简单的解决方案需要添加一个自定义高亮边框并给它一个负 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"
                    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);
}

相关问答

依赖报错 idea导入项目后依赖报错,解决方案:https://blog....
错误1:代码生成器依赖和mybatis依赖冲突 启动项目时报错如下...
错误1:gradle项目控制台输出为乱码 # 解决方案:https://bl...
错误还原:在查询的过程中,传入的workType为0时,该条件不起...
报错如下,gcc版本太低 ^ server.c:5346:31: 错误:‘struct...