Home > WPF, WPF TreeView > Organizing Heterogeneous Data on a WPF TreeView

Organizing Heterogeneous Data on a WPF TreeView

December 11th, 2008

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

  <!-- bind the different data sources -->
  <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:

 

<!-- data template for FolderItem instances -->
<HierarchicalDataTemplate DataType="{x:Type vm:FolderItem}"
                          ItemsSource="{Binding Path=Items}">

  <TextBlock Text="{Binding Path=Name}" />

</HierarchicalDataTemplate>

 

This finally produces our desired output:

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

    <!-- data template for Farm instances -->
    <HierarchicalDataTemplate DataType="{x:Type local:Farm}">

      <!-- bind the different data sources -->
      <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>


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

  </Window.Resources>

  <!-- the treeview control -->
  <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


Author: Categories: WPF, WPF TreeView Tags: ,
  1. Meho
    May 13th, 2009 at 16:04 | #1

    Hi nice article!

    What if you have children under Animals? Animal A -> Child1,…?

  2. May 13th, 2009 at 16:10 | #2

    It’s the same game, just one level below…

  3. MEho
    May 13th, 2009 at 18:22 | #3

    @Philipp Sumi
    Hi
    yes it is one level bellow but how do you do it, any code example? :)
    Thanks

  4. May 13th, 2009 at 18:34 | #4

    I won’t write a sample for you – you will have to figure that out yourself. I’d say the sample shows how to go from level 1 to 2 and from 2 to 3. I’m pretty sure you’ll manage 3->4 ;)

  5. schue
    May 18th, 2009 at 11:10 | #5

    Hello, really helpful article, so thanks a lot.

    But one problem I have in a mvvm-scenario: if I remove or add an item from/into underlaying collections this change will not be visible within view. The FolderItem.Items-collection are not aware these changes, because the changes are made within the original collections and not within the FolderItem.Items-collection. Actually I cannot see a clear way to access this collections from within viewmodel.
    Do you have an idea how to solve this problem?

  6. May 18th, 2009 at 11:17 | #6

    If your underlying collections implement INotifyCollectionChanged, the tree will automatically update itself because the converter will be invoked every time the underlying data changes (typical WPF behavior).
    You can easily reproduce this with the farms sample: Add a button to the form, and in the button’s click handler, assign a new Animal to one of the farms:

    private void AddAnimal_Click(object sender, RoutedEventArgs e)
    {
    //change the contents of the view model collection
    farms[0].Animals.Add(new Animal("Test-Animal"));
    }

    This will add a new entry to your tree.

  7. schue
    May 19th, 2009 at 12:53 | #7

    @Philipp Sumi
    Yes, you are right. The notification-chain was broken at one point. I corrected this and it works. Thanks for the food for thought.

    But regards the collections within FolderItem there is another issue. Imagine the collections (e.g. animals) are again subject for changes and all members have been removed. It would be nice if after removing the last child the associated FolderItem-parent will be removed from the items-collection, too. But there is no access from outer.
    Do you have an idea how to achieve this behaviour? Ok, its not an very severe issue, but nice to have.

    Actually I only can imagine to solve this via an special collection-class (instead List), thats used within SimpleFolderConverter.Convert(…). The instance subscribes CollectionChanged-events from child-collections and acts if necessary by removing the FolderItem that has no further childs.
    Im not sure if its the smartest way, perhaps you have another idea.

  8. May 19th, 2009 at 13:02 | #8

    I’d say you are leaning towards a ViewModel – this solution is explicitly thought as a quick and simple alternative. Before doing a lot of work with the converter, you should consider going the MVVM route:
    http://www.codeproject.com/KB/WPF/TreeViewWithViewModel.aspx

    As an alternative, you can take parts of Josh’s solution (the TreeViewItem style) and bind the visibility to the underlying FolderItem/Collection along with a converter that checks whether the collection is empty or not. Depending on the collection, this converter would return Visible or Collapsed.

  9. urza
    June 2nd, 2009 at 20:20 | #9

    I like the incons in the tree :) are they somewhere available?

  10. June 3rd, 2009 at 08:25 | #10

    urza – it’s just a mockup, so there’s no custom icons. Have a look at http://www.balsamiq.com.

  11. Arielr
    October 7th, 2009 at 23:15 | #11

    Good article. Thanks.

  12. nockawa
    March 23rd, 2010 at 00:55 | #12

    You’re my god and my savior!
    This post rocks in every way possible: technique, writing, screens!
    Thank you!

  13. SmallDog
    July 6th, 2010 at 04:23 | #13

    Hi Philipp, great little example you have here.

    Like meho’s comment earlier, I also want to implement items under the “animal” items. In your “FolderItem” class you have:
    private IEnumerable items;

    Would you change this to: ‘private IEnumerable items’ to do this? I’m abit stuck on how to implement 4 layers, so any suggestions/help would be appreciated.
    thx

  14. SmallDog
    July 6th, 2010 at 04:25 | #14

    EDIT
    Sorry the post above took out my brackets. it was supposed to say:

    Would you change this to: ‘private IEnumerable(FolderItem) items

    *replace () with angle brackets

  15. Murali Krishna
    September 7th, 2010 at 08:38 | #15

    Hi Philipp,

    I have got clarified lots of doubts after reading your article. But i have one assignment in my project can you please explain me how should i solve this using XmlDataProvider. I want to solve this problem only with XAML i don’t want to use any obserable collections.

    My XML File looks like below.

    I want to display the Tree view in the below way using Hierarchical Data Templates.

    CEE
    murali.natukula
    durga.karri
    swapna.tatiraju
    ali.sharik
    Shobanadri.A
    SLC
    phani.kotikalapudi
    sylvester
    srikanth.pokala
    sravanti
    venkatesu.punugupati
    Workbench_Charlie
    Workbench_Delta

    Thanks & Regards,
    N.Murali Krishna.

  16. September 16th, 2010 at 09:36 | #16

    Good article!
    Content are explained in a very simple way :)

  17. Minstrel
    July 20th, 2011 at 15:06 | #17

    Fantastic article, exactly what I needed. It even works when you have a collection of child farms. The template is recursively applied to the child farms with no extra work. Overjoyed with the simplicitly of the solution. Thankyou!

  18. Christian
    April 3rd, 2013 at 12:24 | #18

    Nice article!
    Still working with VS2012 and WPF 4.5.

  1. October 5th, 2009 at 09:54 | #1
  2. September 19th, 2010 at 12:29 | #2