Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Add a border around all children of a TreeViewItem

I have a TreeView and I am trying to implement a style that will allow me to place a border around all the children of a particular node using the HierarchicalDataTemplate. An example of what I want is shown below:

enter image description here

The following code is what I have so far.

<HierarchicalDataTemplate DataType="{x:Type model:Node}" ItemsSource="{Binding Children, Mode=OneWay}">
     <StackPanel>
          <TextBlock Text="{Binding Name}"/>
     </StackPanel>
     <HierarchicalDataTemplate.ItemContainerStyle>
          <Style TargetType="{x:Type TreeViewItem}">
              //what goes in here???
          </Style>
     </HierarchicalDataTemplate.ItemContainerStyle>     
</HierarchicalDataTemplate>

What do I need to add to implement my border the way I want?

like image 553
Joseph Devlin Avatar asked May 10 '13 16:05

Joseph Devlin


2 Answers

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.

like image 136
Viv Avatar answered Oct 21 '22 12:10

Viv


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>();
        }
    }
}
like image 36
Stipo Avatar answered Oct 21 '22 12:10

Stipo