Heterogeneous hierarchies in the WPF Elements MulticolumnTreeView

The MulticolumnTreeView (aka TreeListView) in WPF Elements combines the functionality of a TreeView and a ListView, supporting the hierarchy and expansion features of a TreeView with the multicolumn display of a ListView.

Most of the time, when you use a MulticolumnTreeView, you’ll be using a homogeneous hierarchy — that is, with the same type of object at each level. This is because the set of columns is set at the control level. If the objects at each level are too different, it doesn’t make much sense to try to present them under the same set of columns.

Sometimes, however, you may have a heterogeneous hierarchy where, although the objects in the hierarchy have different types, there’s enough commonality that a column approach makes sense.

For example, suppose we’re displaying a taxonomic hierarchy, such as the biological classification of organisms into kingdoms, phyla, species and so on. We’ll imagine that the entries in the taxonomy are represented by classes such as Kingdom, Phylum and Species. These classes have some properties in common (say, Description and LatinName), but each may have additional class-specific properties (for example, Species might have a flag indicating the species’ conservation status, or the URL of a picture of an example of the species). We’ll also assume that each class has a different name for its collection of children: the Order class has a Families collection, the Family class a Genera collection and the Genus class a Species collection.

We’ll first tackle the problem of creating the tree. With a homogeneous hierarchy, you can just tell the MulticolumnTreeView what the “children” property is, and all will be well:

<ms:MulticolumnTreeView ItemTemplate="{ms:ChildPath Children}" />

But each of our classification types has a different name for its “children” collection. So this won’t work for our hierarchy.

To address this, we need to go a bit more deeply into the workings of the MulticolumnTreeView. A MulticolumnTreeView is a TreeView, so it builds its hierarchy from a HierarchicalDataTemplate (it just ignores the body of the template in favour of the column-based display). The {ms:ChildPath} syntax is a shortcut for defining a HierarchicalDataTemplate, but we don’t have to take the shortcut. As in a normal TreeView, we can explicitly create a HierarchicalDataTemplate. And, also as in a normal TreeView, as part of that HierarchicalDataTemplate we can, if we want, specify a different ItemsSource to get us to the next level of the hierarchy. So we can tell the MulticolumnTreeView how to navigate our heterogeneous hierarchy as follows:

<Window.Resources>
  <HierarchicalDataTemplate x:Key="SpeciesTemplate" ItemsSource="{x:Null}" />
  <HierarchicalDataTemplate x:Key="GenusTemplate" ItemsSource="{Binding Species}" 
                            ItemTemplate="{StaticResource SpeciesTemplate}" />
  <HierarchicalDataTemplate x:Key="FamilyTemplate" ItemsSource="{Binding Genera}" 
                            ItemTemplate="{StaticResource GenusTemplate}" />
  <HierarchicalDataTemplate x:Key="OrderTemplate" ItemsSource="{Binding Families}" 
                            ItemTemplate="{StaticResource FamilyTemplate}" />
  <HierarchicalDataTemplate x:Key="ClassTemplate" ItemsSource="{Binding Orders}" 
                            ItemTemplate="{StaticResource OrderTemplate}" />
</Window.Resources>

(The reason for the explicit SpeciesTemplate is that, if we didn’t have this, the TreeView would keep trying to apply the nearest template at the Species level. That would be the GenusTemplate, and that would cause binding errors when WPF tried to find the Species property, specified in GenusTemplate’s ItemsSource, on the Species class. These errors are benign, but there’s no harm in keeping things clean.)

Now that we can display all the levels of our hierarchy, what about tailoring the display of specific types? This depends on how much tailoring you need to do. For now, let’s put a little colour indicator next to the names of endangered species.

We can do this by setting the CellTemplateSelector property of the GridViewColumn (instead of the DisplayMemberBinding or CellTemplate). Our selector will examine the type of the object being templated, and return a special template if the object is a Species. Then it’s just a matter of defining the “normal” and “species” templates:

<DataTemplate x:Key="DefaultEnglishNameTemplate">
  <TextBlock Text="{Binding EnglishName}" />
</DataTemplate>
 
<DataTemplate x:Key="SpeciesEnglishNameTemplate">
  <StackPanel Orientation="Horizontal">
    <Rectangle Name="StatusRect" Stroke="Black" StrokeThickness="1" 
               Fill="Orange"
               Height="10" Width="10" Margin="-15,0,4,0" />
    <TextBlock Text="{Binding EnglishName}" />
  </StackPanel>
  <DataTemplate.Triggers>
    <DataTrigger Binding="{Binding ConservationStatus}" Value="NotThreatened">
      <Setter TargetName="StatusRect" Property="Visibility" Value="Hidden" />
    </DataTrigger>
    <DataTrigger Binding="{Binding ConservationStatus}" Value="Endangered">
      <Setter TargetName="StatusRect" Property="Fill" Value="Red" />
    </DataTrigger>
  </DataTemplate.Triggers>
</DataTemplate>
 
<local:ClassificationTypeSelector x:Key="ByClassificationLevel"
                                  DefaultTemplate="{StaticResource DefaultEnglishNameTemplate}"
                                  SpeciesTemplate="{StaticResource SpeciesEnglishNameTemplate}" />

Here’s the result:

MulticolumnTreeView showing custom cell template

What we’ve described here is limited to working within a cell (that is, a single column of the item row). If you need to create some UI that spans cells, breaks out of the cellular structure or replaces the cellular structure, then you need to start templating the MulticolumnTreeViewItems. To get started with this, check out the MulticolumnTreeViewStyling.xaml page in the Elements sample project included with the WPF Elements package. The difference in the case of heteregeneous hierarchies is that — depending on how radical your level-specific customisations are — you’ll probably need to use ItemContainerStyleSelector instead of ItemContainerStyle to determine the template.

In our example, let’s extend the conservation status colour to be the background to the row instead of just a little indicator, and add a message expanding on this. (I’ve also kept some other chrome from the Elements sample.)

First, we need a StyleSelector. This will have exactly the same logic as the DataTemplateSelector from the previous iteration, but now it’s returning Styles instead of DataTemplates.

Now we define the styles. I’m going to base these closely on the Elements sample. For my default style, in fact, I’m just going to use the Elements sample style, with some application-specific bits taken out. For my species style, I’m going to add a coloured rectangle and a TextBlock, with appropriate data bindings and triggers. Here’s the species style — I’ve left out the default style because it’s similar but simpler — and the selector declaration:

<Style x:Key="SpeciesMulticolumnTreeViewItem" TargetType="ms:MulticolumnTreeViewItem">
  <Setter Property="Template">
    <Setter.Value>
      <ControlTemplate TargetType="ms:MulticolumnTreeViewItem">
        <Border Margin="0,2,5,2" Background="Transparent" BorderThickness="0">
          <Grid>
            <Rectangle Fill="Orange" RadiusX="6" RadiusY="6" Name="StatusRect"
                       Margin="{Binding Level, Converter={StaticResource LevelToIndentConverter}, RelativeSource={RelativeSource AncestorType={x:Type ms:MulticolumnTreeViewItem}}}" />
            <StackPanel>
              <GridViewRowPresenter Name="PART_Header" Content="{TemplateBinding Header}"
                                    Columns="{Binding WrappedColumns, RelativeSource={RelativeSource AncestorType={x:Type ms:MulticolumnTreeView}}}" />
              <Border Margin="{Binding Level, Converter={StaticResource LevelToIndentConverter}, RelativeSource={RelativeSource AncestorType={x:Type ms:MulticolumnTreeViewItem}}}"
                      Padding="20,3,3,3">
                <TextBlock Name="StatusText" Text="{Binding ConservationDetails}" 
                           FontFamily="Times New Roman" FontStyle="Italic" />
              </Border>
              <ItemsPresenter Name="ItemsHost" />
            </StackPanel>
          </Grid>
        </Border>
        <ControlTemplate.Triggers>
          <Trigger Property="IsExpanded" Value="False">
            <Setter TargetName="ItemsHost" Property="Visibility" Value="Collapsed"/>
          </Trigger>
          <DataTrigger Binding="{Binding ConservationStatus}" Value="NotThreatened">
            <Setter TargetName="StatusRect" Property="Visibility" Value="Hidden" />
            <Setter TargetName="StatusText" Property="Visibility" Value="Hidden" />
          </DataTrigger>
          <DataTrigger Binding="{Binding ConservationStatus}" Value="Endangered">
            <Setter TargetName="StatusRect" Property="Fill" Value="Red" />
          </DataTrigger>
        </ControlTemplate.Triggers>
      </ControlTemplate>
    </Setter.Value>
  </Setter>
</Style>
 
<local:ClassificationStyleSelector x:Key="StyleByClassificationLevel"
                                   DefaultStyle="{StaticResource CustomMulticolumnTreeViewItem}"
                                   SpeciesStyle="{StaticResource SpeciesMulticolumnTreeViewItem}" />

And here’s the result:

MulticolumnTreeView showing type-specific item styles

So, to summarise:

  • If everything in your hierarchy is the same (or at least the bits you’re interested in are the same), you can use a single ItemTemplate and the DisplayMemberBinding or CellTemplate properties.
  • If the “children” collections have different names at different levels, use multiple HierarchicalDataTemplates and specify the ItemsSource property on each.
  • To vary the appearance or content of a cell, use CellTemplateSelector.
  • To vary the appearance or content of an entire row, use ItemContainerStyleSelector.
  • You can mix and match these techniques as required to customise your user interface precisely to the kind of data you need to present.

kick it on DotNetKicks.com

Tagged as WPF, WPF Elements

6 Responses to “Heterogeneous hierarchies in the WPF Elements MulticolumnTreeView”

Archives

Join our mailer

You should join our newsletter! Sent monthly:

Back to Top