Introduction
This article discusses how to customize the item layout in a WPF TreeView
. The layout we will examine is quite similar to an "org chart", where each level of items is displayed in a horizontal row directly beneath their respective parent. Along the way we will see how the power of templates and styles in WPF can provide incredible flexibility for customizing an application's user interface.
This article is not for WPF beginners. It assumes that you already have knowledge of XAML, control templates, styles, triggers, hierarchical data templates, data binding, and other fundamentals of WPF.
I also posted another article regarding layout customization for the TreeView
control. If you are interested in seeing another way that the TreeView
can be customized, you might want to read Advanced Custom TreeView Layout in WPF.
Graphical overview
Before diving into the XAML which makes the magic happen, let's first take a look at what we are aiming to achieve. If I populate a TreeView
with some simple data and view it, by default it looks pretty plain. Here is the "before" picture:
What you see above is certainly not a breathtaking representation of the data. However, after we customize the way that TreeViewItem
s are rendered and how the TreeView
positions its items, the same TreeView
control can look like this:
How it works
The first step is to create a custom ControlTemplate
for the TreeViewItem
class. If you wrap that template in a typed Style
(i.e. a Style
with no Key
) then it will automatically be applied to every TreeViewItem
instance by default. The TreeViewItem
control template should have two things: a ContentPresenter
whose Name
is 'PART_Header' and an ItemsPresenter
. The ContentPresenter
is used to display the content of the item. The ItemsPresenter
is used to display it's child items.
In addition to customizing the TreeViewItem
control template, you also must modify the ItemsPanel
of TreeViewItem
. In order for the child items to be displayed in a horizontal row, I set the TreeViewItem.ItemsPanel
property to a StackPanel
with a horizontal orientation. That setting was also applied in the typed Style
mentioned previously.
Let's take a look at an abridged version of the typed Style
:
<Style TargetType="TreeViewItem">
<Style.Resources>
-->
</Style.Resources>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="TreeViewItem">
<Grid Margin="2">
<Grid.RowDefinitions>
-->
<RowDefinition Height="Auto" />
-->
<RowDefinition Height="*" />
</Grid.RowDefinitions>
-->
<Border Name="Bd"
Background="{StaticResource ItemAreaBrush}"
BorderBrush="{StaticResource ItemBorderBrush}"
BorderThickness="0.6"
CornerRadius="8"
Padding="6"
>
<ContentPresenter Name="PART_Header"
ContentSource="Header"
HorizontalAlignment="Center"
VerticalAlignment="Center" />
</Border>
-->
<ItemsPresenter Grid.Row="1"/>
</Grid>
<ControlTemplate.Triggers>
-->
<Trigger Property="IsSelected" Value="True">
<Setter
TargetName="Bd"
Property="Panel.Background"
Value="{StaticResource SelectedItemAreaBrush}" />
<Setter
TargetName="Bd"
Property="Border.BorderBrush"
Value="{StaticResource SelectedItemBorderBrush}" />
<Setter
TargetName="Bd"
Property="TextElement.Foreground"
Value="{DynamicResource
{x:Static SystemColors.HighlightTextBrushKey}}" />
<Setter
TargetName="Bd"
Property="Border.BitmapEffect"
Value="{StaticResource DropShadowEffect}" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
-->
<Setter Property="ItemsPanel">
<Setter.Value>
<ItemsPanelTemplate>
<StackPanel
HorizontalAlignment="Center"
IsItemsHost="True"
Margin="4,6"
Orientation="Horizontal" />
</ItemsPanelTemplate>
</Setter.Value>
</Setter>
</Style>
The final step is to make the TreeView
center the root item(s) horizontally. Doing so will provide symmetry between the items, as seen in the screenshot above. This step is a simple matter of setting the TreeView
's ItemsPanel
property to a Grid
whose HorizontalAlignment
is set to 'Center
'. Let's take a look at the XAML for a Window which contains our customized TreeView
:
<Window x:Class="CustomTreeViewLayout.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:CustomTreeViewLayout"
Title="Custom TreeView" Height="350" Width="780"
Loaded="OnLoaded"
WindowStartupLocation="CenterScreen"
FontSize="11"
>
<TreeView Name="tree">
<TreeView.Resources>
<ResourceDictionary>
-->
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="OrgChartTreeViewItemStyle.xaml" />
</ResourceDictionary.MergedDictionaries>
-->
<HierarchicalDataTemplate
DataType="{x:Type local:Node}"
ItemsSource="{Binding ChildNodes}"
>
<TextBlock Text="{Binding Text}" />
</HierarchicalDataTemplate>
</ResourceDictionary>
</TreeView.Resources>
-->
<TreeView.ItemsPanel>
<ItemsPanelTemplate>
<Grid
HorizontalAlignment="Center"
IsItemsHost="True" />
</ItemsPanelTemplate>
</TreeView.ItemsPanel>
</TreeView>
</Window>
I am not going to discuss the code which populates the TreeView
with dummy data. Feel free to peruse that code (and all the rest of it) in the source code download, which is available at the top of this article.
Tip
Customizing the ControlTemplate
for the TreeViewItem
class was easy once I discovered a little trick. I serialized the default TreeViewItem
control template to XAML and then modified that until I got the result I was looking for.
The Big Bummer
Unfortunately there is no supported way to programmatically set the selected item in a TreeView
. The TreeView
's SelectedItem
property does not have a setter. As a result, I could not customize the keyboard navigation for the TreeView
. The demo project prevents the TreeView
from responding to keyboard input altogether. If you enable keyboard navigation in the demo you will find that it is very unintuitive to navigate the items. Hopefully one day there will be a way to customize the keyboard navigation of a TreeView
, but until then...