Click here to Skip to main content
Click here to Skip to main content

Organizing Heterogeneous Data on a WPF TreeView

By , 14 May 2009
Rate this:
Please Sign up or sign in to vote.

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:

simpletree

However, more often than not, you’re running into more complex scenarios where additional structuring is necessary. Image your Farm class looks like this:

image

Accordingly, you might want to display that information according to the sketch below:

complextree

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 are 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}">

  <!--<span class="code-comment"> bind the different data sources --></span>
  <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 a 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:

image

 

Obviously, the data is being parsed 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:

 image


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:

<!--<span class="code-comment"> data template for FolderItem instances --></span>
<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:

image

 

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" />

    <!--<span class="code-comment"> data template for Farm instances --></span>
    <HierarchicalDataTemplate DataType="{x:Type local:Farm}">

      <!--<span class="code-comment"> bind the different data sources --></span>
      <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>

    <!--<span class="code-comment"> data template for FolderItem instances --></span>
    <HierarchicalDataTemplate DataType="{x:Type vm:FolderItem}"
                              ItemsSource="{Binding Path=Items}">
      <TextBlock Text="{Binding Path=Name}" />
    </HierarchicalDataTemplate>

  </Window.Resources>

  <!--<span class="code-comment"> the treeview control --></span>
  <TreeView x:Name="farmsTree" />

</Window> 

 

Of course, you can easily style any of the data templates to your liking. You find the complete sample under the link below. Enjoy :)

Download Sample Project (VS2008): farmtree.zip

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Author

Philipp Sumi
Architect I'm a gun for hire
Switzerland Switzerland
Philipp is an independent software engineer with great love for all things .NET.
He lives in Winterthur, Switzerland and his home on the web is at http://www.hardcodet.net.

Comments and Discussions

 
QuestionGreat Article! Pinmembereduardolucioac24-Feb-12 4:45 
Generalexactly what i need PinmemberSeriousM18-Mar-10 21:42 
GeneralVery cool! Question on updates [modified] PinmemberJanene Mc1-Feb-10 6:11 
GeneralRe: Very cool! Question on updates PinmemberPhilipp Sumi1-Feb-10 6:43 
GeneralRe: Very cool! Question on updates [modified] PinmemberJanene Mc1-Feb-10 6:52 
GeneralRe: Very cool! Question on updates PinmemberPhilipp Sumi1-Feb-10 7:19 
GeneralRe: Very cool! Question on updates PinmemberJanene Mc1-Feb-10 7:25 
GeneralRe: Very cool! Question on updates PinmemberPhilipp Sumi1-Feb-10 7:30 
GeneralRe: Very cool! Question on updates PinmemberJanene Mc1-Feb-10 7:34 
GeneralRe: Very cool! Question on updates PinmemberJanene Mc1-Feb-10 8:04 
GeneralRe: Very cool! Question on updates PinmemberPhilipp Sumi1-Feb-10 9:04 
GeneralRe: Very cool! Question on updates PinmemberJanene Mc1-Feb-10 9:08 
GeneralRe: Very cool! Question on updates PinmemberPhilipp Sumi4-Feb-10 5:24 
GeneralRe: Very cool! Question on updates PinmemberJanene Mc4-Feb-10 5:40 
GeneralRe: Very cool! Question on updates PinmemberPhilipp Sumi7-Feb-10 13:17 
GeneralRe: Very cool! Question on updates PinmemberJanene Mc9-Feb-10 9:45 
GeneralRe: Very cool! Question on updates PinmemberPhilipp Sumi14-Feb-10 11:40 
Janene
 
Sorry for taking so much time - I'm currently so busy with stuff, I forgot - again. Sorry.
 
Ok, I really had a blackout on this one, because I started off the wrong premise. The thing is that the converter is bound to three properties (which are collections), and as such, it only refreshes itself if the collections themselves are changed, not their contents (listening to property change events, not collection change events).
You can easily see this with the working scenario (where you have an "Animals" subfolder) if you set a break point in the converter. Although the new animal appears on the tree, the break point is not hit - the fact that the new animal shows up is only due to the binding of the tree node, which is bound to the underlying collection.
 
Now, if there is no "Animals" folder, the original collection is not used, because its elements are taken out of it and assigned to a new list that does not exist on the model (the one that also contains the farmer). Accordingly, there's no binding to the collection you are being updating.
 
Now, there would be a possible workaround, but that would require the converter to create some kind of aggregating collection that monitors the underlying collections and accordingly refreshes itself. This, on the other hand, is rather nasty (you would have to use weak events in order not to cause memory leaks for starters) and goes beyond the simplicity of the converter solution. Accordingly, I'd suggest to do it right from the start, and create a view model you bind to. This is also the right place to throw your change events in order to have the tree refresh its contents.
 
Cheers,
Philipp
NetDrives - Open Source Network Share Management Awesomeness

GeneralDefinitely Useful PinmemberLee Humphries15-May-09 11:45 
GeneralRe: Definitely Useful PinmemberPhilipp Sumi15-May-09 12:39 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web02 | 2.8.140415.2 | Last Updated 15 May 2009
Article Copyright 2009 by Philipp Sumi
Everything else Copyright © CodeProject, 1999-2014
Terms of Use
Layout: fixed | fluid