8

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?

Dave Clemmer
  • 3,741
  • 12
  • 49
  • 72
Joseph Devlin
  • 1,754
  • 1
  • 26
  • 37

2 Answers2

12

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.

Vogel612
  • 5,620
  • 5
  • 48
  • 73
Viv
  • 17,170
  • 4
  • 51
  • 71
  • I will attempt your solution on Monday when I return to work. Thanks for the answer – Joseph Devlin May 11 '13 at 13:09
  • Your solution places a border around each individual TreeViewItem. What I need is a border around the entire groups of TreeviewItems – Joseph Devlin May 13 '13 at 09:18
  • @Kazuo ye before you posting this image, I understood your question for having a border around each item. I've updated my answer to replicate your posted image. Hope that helps – Viv May 13 '13 at 10:43
  • Sorry, I am finding your answer confusing to follow. When looking at the last code snippet I do not know where the border is. Is that the border inside the HierarchicalDataTemplate from your first code snippet? Do I add your new code the ItemContainerStyle? Basically I do not know what to write and where. – Joseph Devlin May 13 '13 at 11:32
  • @Kazuo ill add the sample project I put together with a download link to my answer in a bit. This is just an ItemContainerStyle ehich is applied to the treeview xaml element directly. – Viv May 13 '13 at 12:21
  • @Kazuo I've added a download link at the bottom of the post you can get and have a look at for the implementation. It has 2 files MainWindow.xaml and Window1.xaml each with it's codebehind showing a slightly different usage using either just Resources or `` – Viv May 13 '13 at 13:17
  • I am still attempting to get your solution to work. To suit my needs all the xaml styling needs to be contained inside of the HierarchicalDataTemplate. I cannot be making any changes to the tree view itself. When get your solution working it had the same issue in that it put a border around each child. I had to read your download in notepad++ as I only have visual studio 2010. Cannot open your project to see your styling in action – Joseph Devlin May 15 '13 at 16:15
  • @Kazuo ok I've pretty much just completely trimmed down my answer to make it easier for you. I've attached a new download link(removed the old one) which has everything to do with this `Style` and it's Dependencies inside `HierarchicalDataTemplate`. This sln can be opened from VS2010 and should show the same output as the image above. Everything happens in MainWindow.xaml and it's code-behind. Hope that clarifies some stuff for you – Viv May 15 '13 at 22:24
  • @Kazuo Also you don't have to open stuff in NotePad++ if you only have VS2010 all it takes is a search with "convert vs2012 sln to vs2010" and you land http://stackoverflow.com/questions/12143383/converting-vs2012-solution-to-vs2010 which is exactly what I've done on the new download to make it VS2010 firendly. – Viv May 15 '13 at 22:26
  • I could not get this to work. When I open your example the border does indeed surround the lowest level child items but when it came to applying that style to my own I failed. The code from your example had to be changed as it was overwriting the style from my style library that is applied to all TreeViews. In the end I failed to take your example and change it successfully to make it fit my needs. All I needed a border around the children that did not involve changing any other aspect of the style. Ill list your answer as the accepted answer anyway as someone else my gain something from it. – Joseph Devlin May 20 '13 at 10:40
  • Well am sorry to hear that but the bottom line is to get `Border` around the children we need to edit the `Template`. If your having a base `Style`, you could try using the `BasedOn` property of the Style to derive from the base `Style`. We would still need to over-ride the template somewhere to get the behaviour your looking for. It's hard to give you an example without knowing what your base `Style` provides as functionalities. – Viv May 20 '13 at 10:55
  • when over-riding the template what is the bare minimum that needs to be applied to my HierarchicalDataTemplate to get the border if we use the basedon. I have over-ridden a template successfully before with a context menu and preserved its style but I am having a lot of trouble with this TreeViewItem for some reason. – Joseph Devlin May 20 '13 at 10:59
  • Unfortunately the "bare minimum" in this case is everything you found in the `TreeView`'s `ItemContainerStyle`. Just think of it as this. TreeViewItem's by default are "rendered" in the view a particular way. Your requirements needs us to add something that which is not available by default. WPF is look-less thus we have over-riden the entire render to be 99% similar to what WPF has by default and added that `Border` around the "ItemsHost". There isn't a solution to just add a Border without replicating the default 99% of the Template which we don't want to modify – Viv May 20 '13 at 11:05
  • @Kazuo what are the things you have in the base Style for a `TreeViewItem`? is it just Triggers and Setter's to modify inbuilt DP's of the `TreeViewitem`? If so just adding a `BasedOn` in the `ItemContainerStyle` should give you a perfectly concatenated Style. if your however giving a modified template in the base Style for TreeViewItem's then this would not work. – Viv May 20 '13 at 11:08
  • From what I can see the TreeView styling in my app is a complete control template change. This may explain why I could not get this to work. I would have to actually change the base style which is not appropriate in this case as my question above applied to only one tree in the app. – Joseph Devlin May 20 '13 at 11:11
  • Well if you can't change the base Style, you need to copy your base template to this TreeView and add the Border just in this one(If your deriving a Style you would not need to copy common dependencies, you would need to only provide the TreeViewItem's template since everything else should be found through the parent scope's resources), or convince whoever you have to convince to modify the base Style and render a transparent Border everywhere but for this TreeView. – Viv May 20 '13 at 11:23
  • let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/30252/discussion-between-kazuo-and-viv) – Joseph Devlin May 20 '13 at 11:25
3

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>();
        }
    }
}
Community
  • 1
  • 1
Stipo
  • 4,566
  • 1
  • 21
  • 37
  • 1
    Thank you for the answer but I do not wish to have my classes contain any data relating to how they should be presented in UI. The best solution in my case would be a xaml only one. – Joseph Devlin May 20 '13 at 10:42
  • Property HasChildrenBorder was used just as an example to show that concept works. You can put there any other property for which TreeViewItem children should be bordered. Then you should just change DataTrigger.Binding in TreeViewItem's ControlTemplate. – Stipo May 20 '13 at 14:19
  • Ah, my mistake. Thank you for the answer. I have found a solution to my problem with the help from another poster. – Joseph Devlin May 20 '13 at 14:23