Jul 15, 2015

TreeView, data binding and multiple templates

The WPF TreeView supports data binding, like pretty much all other WPF controls does, but because the TreeView is hierarchical in nature, a normal DataTemplate often won't suffice. Instead, we use the HierarchicalDataTemplate, which allows us to template both the tree node itself, while controlling which property to use as a source for child items of the node.

A basic data bound TreeView

In the following example, I'll show you just how easy it is to get started with the HierarchicalDataTemplate: 

<Window x:Class="WpfTutorialSamples.TreeView_control.TreeViewDataBindingSample"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                xmlns:self="clr-namespace:WpfTutorialSamples.TreeView_control"
        Title="TreeViewDataBindingSample" Height="150" Width="200">
    <Grid Margin="10">
                <TreeView Name="trvMenu">
                        <TreeView.ItemTemplate>
                                <HierarchicalDataTemplate DataType="{x:Type self:MenuItem}" ItemsSource="{Binding Items}">
                                        <TextBlock Text="{Binding Title}" />
                                </HierarchicalDataTemplate>
                        </TreeView.ItemTemplate>
                </TreeView>
        </Grid>
</Window>
 
 
 
using System;
using System.Collections.Generic;
using System.Windows;
using System.IO;
using System.Collections.ObjectModel;
namespace WpfTutorialSamples.TreeView_control
{
        public partial class TreeViewDataBindingSample : Window
        {
                public TreeViewDataBindingSample()
                {
                        InitializeComponent();
                        MenuItem root = new MenuItem() { Title = "Menu" };
                        MenuItem childItem1 = new MenuItem() { Title = "Child item #1" };
                        childItem1.Items.Add(new MenuItem() { Title = "Child item #1.1" });
                        childItem1.Items.Add(new MenuItem() { Title = "Child item #1.2" });
                        root.Items.Add(childItem1);
                        root.Items.Add(new MenuItem() { Title = "Child item #2" });
                        trvMenu.Items.Add(root);
                }
        }

        public class MenuItem
        {
                public MenuItem()
                {
                        this.Items = new ObservableCollection<MenuItem>();
                }

                public string Title { get; set; }

                public ObservableCollection<MenuItem> Items { get; set; }
        }
}
 
  
i n the XAML markup, I have specified a HierarchicalDataTemplate for the ItemTemplate of the TreeView. I instruct it to use the Items property for finding child items, by setting the ItemsSource property of the template, and inside of it I define the actual template, which for now just consists of a TextBlock bound to the Title property.
This first example was very simple, in fact so simple that we might as well have just added the TreeView items manually, instead of generating a set of objects and then binding to them. However, as soon as things get a bit more complicated, the advantages of using data bindings gets more obvious.

Multiple templates for different types

In the next example, I've taken a slightly more complex case, where I want to show a tree of families and their members. A family should be represented in one way, while each of its members should be shown in another way. I achieve this by creating two different templates and specifying them as resources of the tree (or the Window or the Application - that's really up to you), and then allowing the TreeView to pick the correct template based on the underlying type of data.
Here's the code - the explanation of it will follow right after: 



<Window x:Class="WpfTutorialSamples.TreeView_control.TreeViewMultipleTemplatesSample"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                xmlns:self="clr-namespace:WpfTutorialSamples.TreeView_control"
        Title="TreeViewMultipleTemplatesSample" Height="200" Width="250">
        <Grid Margin="10">
                <TreeView Name="trvFamilies">
                        <TreeView.Resources>
                                <HierarchicalDataTemplate DataType="{x:Type self:Family}" ItemsSource="{Binding Members}">
                                        <StackPanel Orientation="Horizontal">
                                                <Image Source="/WpfTutorialSamples;component/Images/group.png" Margin="0,0,5,0" />
                                                <TextBlock Text="{Binding Name}" />
                                                <TextBlock Text=" [" Foreground="Blue" />
                                                <TextBlock Text="{Binding Members.Count}" Foreground="Blue" />
                                                <TextBlock Text="]" Foreground="Blue" />
                                        </StackPanel>
                                </HierarchicalDataTemplate>
                                <DataTemplate DataType="{x:Type self:FamilyMember}">
                                        <StackPanel Orientation="Horizontal">
                                                <Image Source="/WpfTutorialSamples;component/Images/user.png" Margin="0,0,5,0" />
                                                <TextBlock Text="{Binding Name}" />
                                                <TextBlock Text=" (" Foreground="Green" />
                                                <TextBlock Text="{Binding Age}" Foreground="Green" />
                                                <TextBlock Text=" years)" Foreground="Green" />
                                        </StackPanel>
                                </DataTemplate>
                        </TreeView.Resources>
                </TreeView>
        </Grid>
</Window>
 
using System;
using System.Collections.Generic;
using System.Windows;
using System.Collections.ObjectModel;
namespace WpfTutorialSamples.TreeView_control
{
        public partial class TreeViewMultipleTemplatesSample : Window
        {
                public TreeViewMultipleTemplatesSample()
                {
                        InitializeComponent();

                        List<Family> families = new List<Family>();

                        Family family1 = new Family() { Name = "The Doe's" };
                        family1.Members.Add(new FamilyMember() { Name = "John Doe", Age = 42 });
                        family1.Members.Add(new FamilyMember() { Name = "Jane Doe", Age = 39 });
                        family1.Members.Add(new FamilyMember() { Name = "Sammy Doe", Age = 13 });
                        families.Add(family1);

                        Family family2 = new Family() { Name = "The Moe's" };
                        family2.Members.Add(new FamilyMember() { Name = "Mark Moe", Age = 31 });
                        family2.Members.Add(new FamilyMember() { Name = "Norma Moe", Age = 28 });
                        families.Add(family2);

                        trvFamilies.ItemsSource = families;
                }
        }

        public class Family
        {
                public Family()
                {
                        this.Members = new ObservableCollection<FamilyMember>();
                }

                public string Name { get; set; }

                public ObservableCollection<FamilyMember> Members { get; set; }
        }

        public class FamilyMember
        {
                public string Name { get; set; }

                public int Age { get; set; }
        }
}
 
As mentioned, the two templates are declared as a part of the TreeView resources, allowing the TreeView to select the appropriate template based on the data type that it's about to show. The template defined for the Family type is a hierarchical template, using the Members property to show its family members.
The template defined for the FamilyMember type is a regular DataTemplate, since this type doesn't have any child members. However, if we had wanted each FamilyMember to keep a collection of their children and perhaps their children's children, then we would have used a hierarchical template instead.
In both templates, we use an image representing either a family or a family member, and then we show some interesting data about it as well, like the amount of family members or the person's age.
In the code-behind, we simply create two Family instances, fill each of them with a set of members, and then add each of the families to a list, which is then used as the items source for the TreeView.
  



No comments:

Post a Comment