Most WPF TreeView
samples you see on the web are somewhat simplistic: While they may provide heterogeneous data, usually all childs of a given node are of the same type:
However, more often than not, you’re running into more complex scenarios where additional structuring is necessary. Imagine your Farm
class looks like this:
Accordingly, you might want to display that information according to the sketch below:
Note the difference: We want to organize the Animals
and Crops
collections within individual sub folders, while the "Farmer
" node is a direct child of the "Farm
" root node. From the control’s point of view, this means that the "Folder
" nodes and the "Farmer
" node are siblings.
Now, one solution to that very problem is ViewModel wrapper classes that optimize the business logic for your specific UI logic. This route does work very well for quite a few scenarios. However, sometimes, you just want to have a quick solution. I’ll try to provide one here…
My solution to that very problem requires the following ingredients:
- A
MultiBinding
that allows you to combine different bindings.
- A converter that helps us organizing the different bound collections into sub folders, where necessary.
- And of course: Data templates that provide a visual representation of your bound data.
Let’s start with the bindings, which are declared within a HiearchicalDataTemplate
. You can use a MultiBinding
to retrieve all necessary data of a given Farm
instance:
<HierarchicalDataTemplate DataType="{x:Type local:Farm}">
<HierarchicalDataTemplate.ItemsSource>
<MultiBinding>
<Binding Path="Farmer" />
<Binding Path="Animals" />
<Binding Path="Crops" />
</MultiBinding>
</HierarchicalDataTemplate.ItemsSource>
<TextBlock Text="{Binding Path=FarmName}" />
</HierarchicalDataTemplate>
A MultiBinding
always needs a converter of type IMultiValueConverter
. Our converter has to provide the following functionality:
- Allow binding of simple objects (
Farmer
), or collections (Animals
, Crops
).
- Where necessary, put a bound child item or collection into a virtual container object that can serve as a "folder" when it comes to rendering.
- Provide means to name (or even identify) a folder in order to simplify styling.
- Render specific child items directly under the parent node (no subfolder).
- Return everything as an object that can be bound to the
TreeView.ItemsSource
property.
I wrote a simple converter that performs these tasks. The initial declaration looks like this:
<MultiBinding Converter="{StaticResource folderConverter}">
<Binding Path="Farmer" />
<Binding Path="Animals" />
<Binding Path="Crops" />
</MultiBinding>
…and it produces the following output:
Obviously, the data is being parsed and assigned to the "Farm
" nodes, but we’re still lacking the desired structure (sub folders for animals and plants). However, this can easily be done by setting the ConverterParameter
property:
<MultiBinding Converter="{StaticResource folderConverter}"
ConverterParameter=", Animals, Cultivated Plants">
<Binding Path="Farmer" />
<Binding Path="Animals" />
<Binding Path="Crops" />
</MultiBinding>
The converter parameter allows you to define folders for any of the items that are bound within the MultiBinding
, while an empty string inserts a bound item directly under the root item. The converter parameter above produces the following output:
The tree now renders the farmers and four FolderItem
instances. FolderItem
is a very simple helper class that is used by the converter to store the bound Animals
and Crops
collections. It provides just two properties:
Name
(the string
that was defined through the converter parameter)
Items
(the folder’s contents)
Currently, the tree does not know yet how to render a FolderItem
class, which is why there’s just the name displayed. What’s missing here is an additional data template for FolderItem
:
<HierarchicalDataTemplate DataType="{x:Type vm:FolderItem}"
ItemsSource="{Binding Path=Items}">
<TextBlock Text="{Binding Path=Name}" />
</HierarchicalDataTemplate>
This finally produces the desired output which matches the originally sketched presentation:
Simply delegating data organization to the SimpleFolderConverter
allows us to individually structure heterogeneous data for our TreeView
control with a very simplistic approach. Below is the complete XAML for the sample:
<Window
x:Class="Hardcodet.Farms.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="clr-namespace:Hardcodet.Farms.ViewModel"
xmlns:local="clr-namespace:Hardcodet.Farms.Model"
Title="Window1"
Height="300"
Width="300">
<Window.Resources>
<vm:SimpleFolderConverter x:Key="folderConverter" />
<HierarchicalDataTemplate DataType="{x:Type local:Farm}">
<HierarchicalDataTemplate.ItemsSource>
<MultiBinding Converter="{StaticResource folderConverter}"
ConverterParameter=", Animals, Cultivated Plants">
<Binding Path="Farmer" />
<Binding Path="Animals" />
<Binding Path="Crops" />
</MultiBinding>
</HierarchicalDataTemplate.ItemsSource>
<TextBlock Text="{Binding Path=FarmName}" />
</HierarchicalDataTemplate>
<HierarchicalDataTemplate DataType="{x:Type vm:FolderItem}"
ItemsSource="{Binding Path=Items}">
<TextBlock Text="{Binding Path=Name}" />
</HierarchicalDataTemplate>
</Window.Resources>
<TreeView x:Name="farmsTree" />
</Window>
Of course, you can easily style any of the data templates to your liking. You can find the complete sample from the link at the top of this post. Enjoy!
codeproject