Add a border around all children of a TreeViewItem Add a border around all children of a TreeViewItem wpf wpf

Add a border around all children of a TreeViewItem


To render a Border around the collection of children for a TreeViewItem we need to modify the Style for ItemContainerStyle of the TreeView

TreeViewItem Style by default uses a <ItemsPresenter x:Name="ItemsHost" /> to render it's children's content.

Children's Content in the default ItemContainerStyle is given by

<ItemsPresenter x:Name="ItemsHost"                Grid.Row="1"                Grid.Column="1"                Grid.ColumnSpan="2" />

Now to test this I had a Collection with a bool named Type and just tried to render a Border when this bool was True

So I updated the ItemsPresenter to

<Border Grid.Row="1"        Grid.Column="1"        Grid.ColumnSpan="2"        BorderThickness="1">  <Border.Style>    <Style TargetType="{x:Type Border}">      <Setter Property="BorderBrush"              Value="Transparent" />      <Style.Triggers>        <DataTrigger Binding="{Binding RelativeSource={RelativeSource FindAncestor,                                       AncestorType={x:Type TreeViewItem}},                                       Path=DataContext.Type}"                      Value="True">          <Setter Property="BorderBrush"                  Value="Red" />        </DataTrigger>      </Style.Triggers>    </Style>  </Border.Style>  <ItemsPresenter x:Name="ItemsHost"  /></Border>

Which then rendered the following

enter image description here

You'll of course have to update the above Bindings to be based on your own cases of when you want the Border rendered.

In my case my Type variable was set to True for the Item with "1.1" as it's Header Content.


Looks like WPF team thought that nobody will need this functionality so they haven't included any border around ItemsPresenter in TreeViewItem template, so you are going to have to change TreeViewItem template and add border around ItemsPresenter.

You can view default TreeViewItem style/template definition by downloading WPF themes XAML dictionaries. Links are provided here.

Here is a complete XAML of a working solution:

<Window x:Class="WpfApplication.MainWindow"        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"        xmlns:model="clr-namespace:WpfApplication">    <Window.DataContext>        <x:ArrayExtension Type="{x:Type model:Node}">            <model:Node Name="Root">                <model:Node.Children>                    <model:Node Name="Child 1" HasChildrenBorder="True">                        <model:Node.Children>                            <model:Node Name="Child 1.1"/>                            <model:Node Name="Child 1.2"/>                            <model:Node Name="Child 1.3"/>                        </model:Node.Children>                    </model:Node>                    <model:Node Name="Child 2"/>                </model:Node.Children>            </model:Node>        </x:ArrayExtension>    </Window.DataContext>    <TreeView ItemsSource="{Binding}">        <TreeView.Resources>            <!--This part is extracted from Areo.NormalColor.xaml WPF Theme which you can download from locations explained here: https://stackoverflow.com/questions/4158678/where-can-i-download-microsofts-standard-wpf-themes-from/4158681#4158681-->            <PathGeometry x:Key="TreeArrow">                <PathGeometry.Figures>                    <PathFigureCollection>                        <PathFigure IsFilled="True"                                    StartPoint="0 0"                                    IsClosed="True">                            <PathFigure.Segments>                                <PathSegmentCollection>                                    <LineSegment Point="0 6"/>                                    <LineSegment Point="6 0"/>                                </PathSegmentCollection>                            </PathFigure.Segments>                        </PathFigure>                    </PathFigureCollection>                </PathGeometry.Figures>            </PathGeometry>            <Style x:Key="ExpandCollapseToggleStyle" TargetType="{x:Type ToggleButton}">                <Setter Property="Focusable" Value="False"/>                <Setter Property="Width" Value="16"/>                <Setter Property="Height" Value="16"/>                <Setter Property="Template">                    <Setter.Value>                        <ControlTemplate TargetType="{x:Type ToggleButton}">                            <Border Width="16"                                     Height="16"                                     Background="Transparent"                                     Padding="5,5,5,5">                                <Path x:Name="ExpandPath"                                       Fill="Transparent"                                       Stroke="#FF989898"                                       Data="{StaticResource TreeArrow}">                                    <Path.RenderTransform>                                        <RotateTransform                                             Angle="135"                                             CenterX="3"                                             CenterY="3"/>                                    </Path.RenderTransform>                                </Path>                            </Border>                            <ControlTemplate.Triggers>                                <Trigger Property="IsMouseOver" Value="True">                                    <Setter TargetName="ExpandPath" Property="Stroke" Value="#FF1BBBFA"/>                                    <Setter TargetName="ExpandPath" Property="Fill" Value="Transparent"/>                                </Trigger>                                <Trigger Property="IsChecked" Value="True">                                    <Setter TargetName="ExpandPath" Property="RenderTransform">                                        <Setter.Value>                                            <RotateTransform                                                 Angle="180"                                                 CenterX="3"                                                 CenterY="3"/>                                        </Setter.Value>                                    </Setter>                                    <Setter TargetName="ExpandPath" Property="Fill" Value="#FF595959"/>                                    <Setter TargetName="ExpandPath" Property="Stroke" Value="#FF262626"/>                                </Trigger>                            </ControlTemplate.Triggers>                        </ControlTemplate>                    </Setter.Value>                </Setter>            </Style>            <Style TargetType="{x:Type TreeViewItem}" BasedOn="{StaticResource {x:Type TreeViewItem}}">                <Setter Property="Template">                    <Setter.Value>                        <ControlTemplate TargetType="{x:Type TreeViewItem}">                            <Grid>                                <Grid.ColumnDefinitions>                                    <ColumnDefinition MinWidth="19" Width="Auto"/>                                    <ColumnDefinition Width="Auto"/>                                    <ColumnDefinition Width="Auto"/>                                </Grid.ColumnDefinitions>                                <Grid.RowDefinitions>                                    <RowDefinition Height="Auto"/>                                    <RowDefinition/>                                </Grid.RowDefinitions>                                <ToggleButton x:Name="Expander"                                              Style="{StaticResource ExpandCollapseToggleStyle}"                                              IsChecked="{Binding Path=IsExpanded, RelativeSource={RelativeSource TemplatedParent}}"                                              ClickMode="Press"/>                                <Border Name="Bd"                                        Grid.Column="1"                                        Background="{TemplateBinding Background}"                                        BorderBrush="{TemplateBinding BorderBrush}"                                        BorderThickness="{TemplateBinding BorderThickness}"                                        Padding="{TemplateBinding Padding}"                                        SnapsToDevicePixels="True">                                    <ContentPresenter x:Name="PART_Header"                                                      ContentSource="Header"                                                      HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"                                                      SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"/>                                </Border>                                <Border Name="ItemsHostBd"                                         Grid.Row="1"                                         Grid.Column="1"                                         Grid.ColumnSpan="2">                                    <ItemsPresenter x:Name="ItemsHost"/>                                </Border>                            </Grid>                            <ControlTemplate.Triggers>                                <Trigger Property="IsExpanded" Value="False">                                    <Setter TargetName="ItemsHostBd" Property="Visibility" Value="Collapsed"/>                                </Trigger>                                <Trigger Property="HasItems" Value="False">                                    <Setter TargetName="Expander" Property="Visibility" Value="Hidden"/>                                </Trigger>                                <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>                                <!-- This part is customized to work with HasChildrenBorder property from data-bound object. -->                                <DataTrigger Binding="{Binding HasChildrenBorder}" Value="True">                                    <Setter TargetName="ItemsHostBd" Property="BorderBrush" Value="Red"/>                                    <Setter TargetName="ItemsHostBd" Property="BorderThickness" Value="1"/>                                </DataTrigger>                            </ControlTemplate.Triggers>                        </ControlTemplate>                    </Setter.Value>                </Setter>            </Style>            <HierarchicalDataTemplate DataType="{x:Type model:Node}" ItemsSource="{Binding Children}">                <TextBlock Text="{Binding Name}"/>            </HierarchicalDataTemplate>        </TreeView.Resources>    </TreeView></Window>

Here is how Node class is defined:

using System.Collections.ObjectModel;namespace WpfApplication{    public class Node    {        public string Name { get; set; }        public ObservableCollection<Node> Children { get; set; }        public bool HasChildrenBorder { get; set; }        public Node()        {            this.Children = new ObservableCollection<Node>();        }    }}