wpftreeviewitem

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


There is a margin on the left of the TreeViewItem when selected in Default TreeView style:

enter image description here

So i want to edit the style of TreeViewItem to make it looks like VS Solution style:

enter image description here

I know the default ControlTemplate of TreeViewItem is:

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

and the left margin is from Grid.ColumnSpan="2" Grid.Column="1" Grid.Row="1"

So how can I make it look like VS Solution style(keep the indent and Not to leave space of background color at the beginning )?


Solution

  • A very quick and simple solution would require to add a custom highlight border and give it a negative Margin.

    The following example dynamically calculates the negative left margin by listening to the Selected routed event. To make it work the highlight Border must span all columns and have the name "HighlightBorder" assigned. The hosting Grid must be named "RootGrid". We need to access the root grid to get the actual indention.

    TreeViewItem style:
    Note that the Style is missing essential triggers to handle visibility of the ToggleButton and the ItemsPresenter.

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

    Handler for the Selected routed event:
    Determine the level of the currently selected node to calculate the actual indention of the highlight border.

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