Note: Kenneth Haugland has posted a better solution on this article: Examples of Attached Behaviors in WPF.
In case you load all TreeViewItem
s at one time to TreeView
control (that means you don't add or remove item after showing it), my article solution may be more lightweight.
Introduction
WPF default TreeView
is very good, but many people still want it to have lines join each of its child elements, like Windows Forms TreeView
, including me. I have searched on the internet and have some examples, but they were not designed well enough.
Now, I myself designed a TreeView
with style as WinForms. Hope this will help many people!
Table of Contents
All you need is an XAML file and a code behind.
First, you need draw Toggle Button: From Triangle button to Plus-Minus button: draw a rectangle with dark border, then draw two lines, one vertical line and one horizontal line. When TreeViewItem
is expanded, the vertical line will hide:
<Style x:Key="ExpandCollapseToggleStyle" TargetType="ToggleButton">
<Setter Property="Focusable" Value="False"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ToggleButton">
<Grid Width="15" Height="13" SnapsToDevicePixels="True">
<Rectangle Width="9" Height="9"
Stroke="#919191" SnapsToDevicePixels="true">
<Rectangle.Fill>
<LinearGradientBrush EndPoint="0.5,2" StartPoint="0.5,0">
<GradientStop Color="White" Offset="0"/>
<GradientStop Color="Silver" Offset="0.5"/>
<GradientStop Color="LightGray" Offset="1"/>
</LinearGradientBrush>
</Rectangle.Fill>
</Rectangle>
<Rectangle x:Name="ExpandPath" Width="1"
Height="5" Stroke="Black" SnapsToDevicePixels="true"/>
<Rectangle Width="5" Height="1"
Stroke="Black" SnapsToDevicePixels="true"/>
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="IsChecked" Value="True">
<Setter Property="Visibility"
TargetName="ExpandPath" Value="Collapsed"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
In the above code, you can see a trigger, it will make the vertical line inside toggle button hide if item is expanded, or show if its children collapsed.
Then, you need to draw vertical and horizontal connecting lines between nodes: You need to redesign TreeViewItem
control. Add these connecting lines:
<Rectangle x:Name="HorLn" Margin="9,1,0,0" Height="1"
Stroke="#DCDCDC" SnapsToDevicePixels="True"/>
<Rectangle x:Name="VerLn" Width="1" Stroke="#DCDCDC"
Margin="0,0,1,0" Grid.RowSpan="2" SnapsToDevicePixels="true"
Fill="White"/>
to your TreeViewItem
template like this:
<Style x:Key="{x:Type TreeViewItem}" TargetType="{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="*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition/>
</Grid.RowDefinitions>
<Rectangle x:Name="HorLn" Margin="9,1,0,0" Height="1"
Stroke="#DCDCDC" SnapsToDevicePixels="True"/>
<Rectangle x:Name="VerLn" Width="1"
Stroke="#DCDCDC" Margin="0,0,1,0" Grid.RowSpan="2"
SnapsToDevicePixels="true" Fill="White"/>
<ToggleButton Margin="-1,0,0,0" 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}"
MinWidth="20"/>
</Border>
<ItemsPresenter x:Name="ItemsHost" Grid.Row="1"
Grid.Column="1" Grid.ColumnSpan="2"/>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Then, you need put the class TreeViewLineConverter
to your namespace. This class will change the connecting lines if the item is the last in the list:
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
namespace TreeViewEx
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
}
class TreeViewLineConverter : IValueConverter
{
public object Convert(object value, Type targetType,
object parameter, System.Globalization.CultureInfo culture)
{
TreeViewItem item = (TreeViewItem)value;
ItemsControl ic = ItemsControl.ItemsControlFromItemContainer(item);
return ic.ItemContainerGenerator.IndexFromContainer(item) == ic.Items.Count - 1;
}
public object ConvertBack(object value, Type targetType,
object parameter, System.Globalization.CultureInfo culture)
{
return false;
}
}
}
Insert your namespace to your XAML, i.e.:
<Window x:Class="TreeViewEx.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:TreeViewEx"/>
Add this line to Window.Resources
:
<local:TreeViewLineConverter x:Key="LineConverter"/>
Add trigger to TreeViewItem
template, this trigger changes the connecting lines if the item is the last in the list:
<DataTrigger Binding="{Binding RelativeSource={RelativeSource Self},
Converter={StaticResource LineConverter}}" Value="true">
<Setter TargetName="VerLn" Property="Height" Value="9"/>
<Setter TargetName="VerLn" Property="VerticalAlignment" Value="Top"/>
</DataTrigger>
The TreeView
will have WinForms style now. You can add more trigger to control behavior of TreeView
if you want. The full trigger can be found in the attached file.
There is a problem with vertical line when you add a new TreeViewItem
into TreeView
, then the connecting line is not updated:
This problem also occurs when you change font size of treeview
item.
If you know how to fix this issue, please post it as a comment. Thank you!
This is the code I referenced before I wrote my own:
My rewrite code is almost the same as that, the differences are:
- Changed triangle toggle button to plus-minus button
- Adjusted better position/size of connecting lines
- Added some explanation and pointed out the issue
- 10/24/2013: First release
- 01/28/2015: Minor update