Blog Archives

WPF: Displaying a Master-Detail Collection of In-Memory Objects In a TreeView

 Introduction
In this earlier blog I used an XML file containing nested data items as the data source of a TreeView. In this version I will use a collection of objects as the data source.
The details of the data are much the same as I used in the previous blog, with a couple of tiny changes to avoid any possible confusion caused by the use of Visual Basic keywords as field names:

These classes are saved in a VB file named SalesData.vb. Because it is fairly lengthy, filling about three screen display lengths, I've made it available from this link. (To a large extent, how the the data is created isn't really the topic of this blog – I'm more concerned with the Binding to a source and the templates used for displaying the data.)

Mapping to the local Assembly
In order to be able to access those SalesPerson, SalesOrder, etc classes and objects in the XAML file, it is necessary to create a mapping. The syntax for this is as follows:  

  xmlns:local="clr-namespace:HierarchicalDataTemplate" 

It isn't mandatory to use 'local' as the mapping alias, but it's a fairly traditional approach. In this case, of course 'HierarchicalDataTemplate' is the name of the project I am working on.

With the namespace mapping in place, any of the classes that currently exist in the code-behind files of the project become visible to the XAML file. If you view the code listing for the SalesData.vb file, you will see that the class which creates the demo data is called 'SalesPersonList'. It's now possible to create a new instance of that class in the XAML file : 

    <local:SalesPersonList x:Key="SalesPersonList"/> 

I generally place this in the Window.Resources collection.

Creating a TreeView and Binding its Data Source
In the markup for the Window itself, I'll create a TreeView which contains a single TreeViewItem.  

    <TreeView>

      <TreeViewItem ItemsSource="{Binding Source={StaticResource SalesPersonList}}"

          Header="Sales Figures" />

    </TreeView> 

The ItemsSource is the crucial property here. It identifies exactly where the TreeView should look for its data. In this case it looks into the SalesPersonList instance that I created a few moments ago. For the avoidance of doubt, the exact 'SalesPersonList' that is used is the StaticResource created earlier and identified by the Key. (I possibly should have used a different name for the Key and the underlying Class to avoid any confusion).

Because of the use of the Binding and the HierarchicalDataTemplates, it is only necessary to create this single TreeViewItem. The Binding engine will trawl through all the data and create as many TreeViewItem nodes as it needs to in order to display everything correctly. 

HierarchicalDataTemplates
This example uses three HierarchicalDataTemplates, plus a standard DataTemplate for the SalesItems. Here is the first one (which again I've placed in the Window.Resources collection):  

    <HierarchicalDataTemplate DataType="{x:Type local:SalesPerson}"

          ItemsSource="{Binding Path=Periods}">

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

    </HierarchicalDataTemplate> 

The DataType property identifies which type of object will be dealt with by this template. In the case of this first template, this will be the SalesPerson type. In other words, when the Binding that I placed on the TreeViewItem finds any instance of a SalesPerson inside that SalesPersonList, it will look at this template to discover how it should display SalesPerson details.
In this case, the Name property of the current SalesPerson will be shown in a TextBlock.
The ItemsSource property also uses a Binding, but it is only interested in knowing what needs to be shown as the child data of SalesPersons. As we know from the diagram above, this will be the Periods data.

If you are finding the DataType and the Bindings' Paths a bit tricky to grasp, it may help if you run the project as it currently stands. Here's the markup for the Window so far: 

<Window x:Class="OrdersListsDisplay"

   xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

   xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

   xmlns:local="clr-namespace:HierarchicalDataTemplate"

   Title="Sales List Display" Height="300" Width="300">

  <Window.Resources>

    <local:SalesPersonList x:Key="SalesPersonList"/>

 

    <!–  Data Templates –>

    <HierarchicalDataTemplate DataType="{x:Type local:SalesPerson}"

          ItemsSource="{Binding Path=Periods}">

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

    </HierarchicalDataTemplate>

 

  </Window.Resources>

    <Grid>

    <TreeView>

      <TreeViewItem ItemsSource="{Binding Source={StaticResource SalesPersonList}}"

          Header="Sales Figures" />

    </TreeView>

  </Grid>

</Window> 

When you run this, you will first see:

Then, when you expand the first node, you will have:

Finally, clicking on either or both the SalesPerson nodes, you will see that the application has tried to display the children of the SalesPersons for you. In the absence of any instruction about formatting the Periods, it simply reverts to displaying the default ToString rendering of the class.

Hopefully though you can now see why the template points to SalesPerson as its DataType, but identifies the next level down the tree as the ItemsSource. (You may have noticed that this wasn't necessary with the XML data example in the earlier blog, where simply assigning a Binding without a Path will work.)

Displaying Periods and SalesOrders
Exactly the same approach is used for the next two templates: 

    <HierarchicalDataTemplate DataType="{x:Type local:Period}"

          ItemsSource="{Binding Path=SalesOrders}">

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

    </HierarchicalDataTemplate>

 

    <HierarchicalDataTemplate DataType="{x:Type local:SalesOrder}"

         ItemsSource="{Binding Path=SalesItems}">

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

    </HierarchicalDataTemplate> 

The three HierarchicalDataTemplates in place so far will produce this result:

which should come as no surprise, based on what I explained earlier.

ItemDetail DataTemplate
So that just leaves the template for the SalesItems and ItemDetail data. This time a standard DataTemplate will do, because we know there is no further data below ItemDetail.  

    <DataTemplate DataType="{x:Type local:SalesItem}">

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

    </DataTemplate> 

Just for the record, a HierarchicalDataTemplate would work but isn't necessary. With the final DataTemplate added, every level of the Master-Detail data can be accessed:

Formatting
You have many options for formatting the final presentation of the data. In this example, you can change the color, font size and weight, indentation, etc via the individual TextBlock properties. If you wanted to insert additional graphical detail (such as an icon), you simply wrap the TextBlock in a StackPanel with its Orientation set to Horizontal. You can then insert the icon image as an additional child of this StackPanel.

You can build on this basic example shown here to create a much more complex display.

WP Like Button Plugin by Free WordPress Templates