Posted by: Zeeshan Amjad | July 31, 2012

Apply Data Template Conditionally in Hierarchical Grid


I came across a question how to apply the data template conditionally in hierarchical grid. We already saw an example of applying data template conditionally here. I picked the idea of creating hierarchical grid from David Sackstein’s blog and here is an article to discuss hierarchical grid.

First we are going to define a class that we wish to display in hierarchical grid. Here is our class.

Code Snippet
public class EmployeeInfo
{
    public EmployeeInfo(EmployeeInfo parent)
    {
        this.Parent = parent;
    }

    public int ID
    { get; set; }

    public string Name
    { get; set; }

    public string City
    { get; set; }

    public string State
    { get; set; }

    public EmployeeInfo Parent
    { get; set; }

    public ObservableCollection<EmployeeInfo> Employees
    { get; set; }

    public int Depth
    {
        get
        {
            int max;
            if (Employees == null || Employees.Count == 0)
                max = 0;
            else
                max = (int)Employees.Max(r => r.Depth);
            return max + 1;
        }
    }

    public int Level
    {
        get
        {
            if (Parent == null)
                return Depth;
            return Parent.Level – 1;
        }
    }
}

 

Here depth and level property is borrowed by David’s article. We also borrowed multi value converter from David’s article. Here is a code of multi value level converter.

Code Snippet
public class LevelConverter : IMultiValueConverter
{

    public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
    {
        int? level = values[0] as int?;
        double? indent = values[1] as double?;

        return indent * level;
    }

    public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
    {
        throw new NotImplementedException();
    }
}

 

Here the real work is done by two Grid properties IsSharedSizeScope and SharedSizeGroup. But the important thing is we are going to create two different hierarchical data templates. In fact we can create as many hierarchical data template but here for simplicity we are making only two.

Here is our template for header

Code Snippet
<HierarchicalDataTemplate x:Key="headerTemplate" ItemsSource="{Binding Employees}">
    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition SharedSizeGroup="Department"/>
            <ColumnDefinition SharedSizeGroup="Toggle"/>
            <ColumnDefinition SharedSizeGroup="ID"/>
        </Grid.ColumnDefinitions>
        <TextBlock Grid.Column="0" Foreground="Red" FontWeight="Bold" Text="{Binding Name}"/>
        <Rectangle Grid.Column="1">
            <Rectangle.Width>
                <MultiBinding Converter="{StaticResource levelConverter}">
                    <Binding Path="Level"/>
                    <Binding ElementName="treeViewItemToMeasure" Path="ActualWidth"/>
                </MultiBinding>
            </Rectangle.Width>
        </Rectangle>
    </Grid>
</HierarchicalDataTemplate>

 

And this is detail template

Code Snippet
<HierarchicalDataTemplate x:Key="detailTemplate" ItemsSource="{Binding Employees}">
    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition SharedSizeGroup="Department"/>
            <ColumnDefinition SharedSizeGroup="Toggle"/>
            <ColumnDefinition SharedSizeGroup="ID"/>
            <ColumnDefinition SharedSizeGroup="Toggle"/>
            <ColumnDefinition SharedSizeGroup="City"/>
            <ColumnDefinition SharedSizeGroup="Toggle"/>
            <ColumnDefinition SharedSizeGroup="State"/>
        </Grid.ColumnDefinitions>
        <TextBlock Grid.Column="0" Foreground="Blue" Text="{Binding Name}"/>
        <Rectangle Grid.Column="1">
            <Rectangle.Width>
                <MultiBinding Converter="{StaticResource levelConverter}">
                    <Binding Path="Level"/>
                    <Binding ElementName="treeViewItemToMeasure" Path="ActualWidth"/>
                </MultiBinding>
            </Rectangle.Width>
        </Rectangle>
        <TextBlock Grid.Column="2" Foreground="Blue" Text="{Binding ID}"/>
        <TextBlock Grid.Column="4" Foreground="Blue" Text="{Binding City}"/>
        <TextBlock Grid.Column="6" Foreground="Blue" Text="{Binding State}"/>
    </Grid>
</HierarchicalDataTemplate>

 

Now we need to inherit class from DataTemplateSelector to select the appropriate data template depends on condition. Here is our class.

Code Snippet
public class MyTemplateSelector : DataTemplateSelector
{
    public HierarchicalDataTemplate HeaderTemplate
    { get; set; }

    public HierarchicalDataTemplate DetailTemplate
    { get; set; }

    public override DataTemplate SelectTemplate(object item, DependencyObject container)
    {
        EmployeeInfo emp = item as EmployeeInfo;

        if (emp.ID == 0)
            return HeaderTemplate;
        else
            return DetailTemplate;
    }
}

 

Then we are going to create an object of our template selector class in XAML and assigned our data template to it.

Code Snippet
<local:MyTemplateSelector x:Key="myTemplateSelector"
                          HeaderTemplate="{StaticResource headerTemplate}"
                          DetailTemplate="{StaticResource detailTemplate}"/>

 

Our last step is to use the class of my template selector class in TreeView. Here is a code of it.

Code Snippet
<TreeView Grid.Row="1"
          ItemsSource="{Binding Employees}"
          ItemTemplateSelector="{StaticResource myTemplateSelector}">
</TreeView>

 

Here is complete XAML code of our program. In this code I tried to make it as simple as I can by avoid all cosmetic changes.

Code Snippet
<Window x:Class="HierarchicalGrid.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation&quot;
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml&quot;
        xmlns:local="clr-namespace:HierarchicalGrid"
        Title="MainWindow" Height="350" Width="525">
    <Window.Resources>
        <local:LevelConverter x:Key="levelConverter"/>
        <HierarchicalDataTemplate x:Key="headerTemplate" ItemsSource="{Binding Employees}">
            <Grid>
                <Grid.ColumnDefinitions>
                    <ColumnDefinition SharedSizeGroup="Department"/>
                    <ColumnDefinition SharedSizeGroup="Toggle"/>
                    <ColumnDefinition SharedSizeGroup="ID"/>
                </Grid.ColumnDefinitions>
                <TextBlock Grid.Column="0" Foreground="Red" FontWeight="Bold" Text="{Binding Name}"/>
                <Rectangle Grid.Column="1">
                    <Rectangle.Width>
                        <MultiBinding Converter="{StaticResource levelConverter}">
                            <Binding Path="Level"/>
                            <Binding ElementName="treeViewItemToMeasure" Path="ActualWidth"/>
                        </MultiBinding>
                    </Rectangle.Width>
                </Rectangle>
            </Grid>
        </HierarchicalDataTemplate>
        <HierarchicalDataTemplate x:Key="detailTemplate" ItemsSource="{Binding Employees}">
            <Grid>
                <Grid.ColumnDefinitions>
                    <ColumnDefinition SharedSizeGroup="Department"/>
                    <ColumnDefinition SharedSizeGroup="Toggle"/>
                    <ColumnDefinition SharedSizeGroup="ID"/>
                    <ColumnDefinition SharedSizeGroup="Toggle"/>
                    <ColumnDefinition SharedSizeGroup="City"/>
                    <ColumnDefinition SharedSizeGroup="Toggle"/>
                    <ColumnDefinition SharedSizeGroup="State"/>
                </Grid.ColumnDefinitions>
                <TextBlock Grid.Column="0" Foreground="Blue" Text="{Binding Name}"/>
                <Rectangle Grid.Column="1">
                    <Rectangle.Width>
                        <MultiBinding Converter="{StaticResource levelConverter}">
                            <Binding Path="Level"/>
                            <Binding ElementName="treeViewItemToMeasure" Path="ActualWidth"/>
                        </MultiBinding>
                    </Rectangle.Width>
                </Rectangle>
                <TextBlock Grid.Column="2" Foreground="Blue" Text="{Binding ID}"/>
                <TextBlock Grid.Column="4" Foreground="Blue" Text="{Binding City}"/>
                <TextBlock Grid.Column="6" Foreground="Blue" Text="{Binding State}"/>
            </Grid>
        </HierarchicalDataTemplate>
        <local:MyTemplateSelector x:Key="myTemplateSelector"
                                  HeaderTemplate="{StaticResource headerTemplate}"
                                  DetailTemplate="{StaticResource detailTemplate}"/>
    </Window.Resources>
    <Grid>
        <Grid Grid.IsSharedSizeScope="True">
            <Grid.RowDefinitions>
                <RowDefinition Height="Auto"/>
                <RowDefinition/>
            </Grid.RowDefinitions>
            <TreeViewItem Grid.Row="0">
                <TreeViewItem.Header>
                    <Grid>
                        <Grid.ColumnDefinitions>
                            <ColumnDefinition SharedSizeGroup="Name"/>
                            <ColumnDefinition SharedSizeGroup="Toggle"/>
                            <ColumnDefinition SharedSizeGroup="Toggle"/>
                            <ColumnDefinition SharedSizeGroup="ID"/>
                            <ColumnDefinition SharedSizeGroup="Toggle"/>
                            <ColumnDefinition SharedSizeGroup="City"/>
                            <ColumnDefinition SharedSizeGroup="Toggle"/>
                            <ColumnDefinition SharedSizeGroup="State"/>
                        </Grid.ColumnDefinitions>
                        <TextBlock Grid.Column="0" Text="Name"/>
                        <TreeViewItem Grid.Column="1">
                            <TreeViewItem.Header>
                                <TreeViewItem Name="treeViewItemToMeasure" Padding="0"/>
                            </TreeViewItem.Header>
                            <TreeViewItem.Width>
                                <MultiBinding Converter="{StaticResource levelConverter}">
                                    <Binding Path="Level"/>
                                    <Binding ElementName="treeViewItemToMeasure" Path="ActualWidth"/>
                                </MultiBinding>
                            </TreeViewItem.Width>
                        </TreeViewItem>
                        <TextBlock Grid.Column="3" Text="ID"/>
                        <TextBlock Grid.Column="5" Text="City"/>
                        <TextBlock Grid.Column="7" Text="State"/>
                    </Grid>
                </TreeViewItem.Header>
                        </TreeViewItem>
            <TreeView Grid.Row="1"
                      ItemsSource="{Binding Employees}"
                      ItemTemplateSelector="{StaticResource myTemplateSelector}">
            </TreeView>
        </Grid>
    </Grid>
</Window>

 

And here is complete C# code of the program.

Code Snippet
using System;
using System.Collections.ObjectModel;
using System.Globalization;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;

namespace HierarchicalGrid
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {

        private EmployeeInfo employees = new EmployeeInfo(null);
   
        public MainWindow()
        {
            InitializeComponent();

            EmployeeInfo sales = new EmployeeInfo(employees);
            sales.Name = "Sales";
            sales.Employees = new ObservableCollection<EmployeeInfo>()
                {   
                    new EmployeeInfo(sales) { ID = 10, Name = "Mark", City = "Los Angeles", State = "CA" },
                    new EmployeeInfo(sales) { ID = 50, Name = "John", City = "Boston", State = "MA" },
                    new EmployeeInfo(sales) { ID = 60, Name = "Scott", City = "Houston", State = "TX" },
                    new EmployeeInfo(sales) { ID = 70, Name = "Jake", City = "Baltimore", State = "MD" },
                    new EmployeeInfo(sales) { ID = 110, Name = "Craig", City = "Las Vegas", State = "NV" }
                };

            EmployeeInfo accounts = new EmployeeInfo(employees);
            accounts.Name = "Accounts";
            accounts.Employees = new ObservableCollection<EmployeeInfo>()
                {   
                    new EmployeeInfo(accounts) { ID = 20, Name = "David", City = "New York", State = "NY" },
                    new EmployeeInfo(accounts) { ID = 40, Name = "Erik", City = "Chicago", State = "IL" },
                    new EmployeeInfo(accounts) { ID = 90, Name = "Chris", City = "Atlanta", State = "GA" }
                };

            EmployeeInfo management = new EmployeeInfo(employees);
            management.Name = "Management";
            management.Employees = new ObservableCollection<EmployeeInfo>()
                {   
                    new EmployeeInfo(management) { ID = 30, Name = "Alex", City = "Seattle", State = "WA" },
                    new EmployeeInfo(management) { ID = 80, Name = "Bill", City = "Denver", State = "CO" },
                    new EmployeeInfo(management) { ID = 100, Name = "Steve", City = "Philadelphia", State = "PA" }
                };

            employees.Employees = new ObservableCollection<EmployeeInfo>();
            employees.Employees.Add(sales);
            employees.Employees.Add(accounts);
            employees.Employees.Add(management);

            DataContext = employees;

        }
    }

    public class EmployeeInfo
    {
        public EmployeeInfo(EmployeeInfo parent)
        {
            this.Parent = parent;
        }

        public int ID
        { get; set; }

        public string Name
        { get; set; }

        public string City
        { get; set; }

        public string State
        { get; set; }

        public EmployeeInfo Parent
        { get; set; }

        public ObservableCollection<EmployeeInfo> Employees
        { get; set; }

        public int Depth
        {
            get
            {
                int max;
                if (Employees == null || Employees.Count == 0)
                    max = 0;
                else
                    max = (int)Employees.Max(r => r.Depth);
                return max + 1;
            }
        }

        public int Level
        {
            get
            {
                if (Parent == null)
                    return Depth;
                return Parent.Level – 1;
            }
        }
    }

    public class LevelConverter : IMultiValueConverter
    {

        public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
        {
            int? level = values[0] as int?;
            double? indent = values[1] as double?;

            return indent * level;
        }

        public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
        {
            throw new NotImplementedException();
        }
    }

    public class MyTemplateSelector : DataTemplateSelector
    {
        public HierarchicalDataTemplate HeaderTemplate
        { get; set; }

        public HierarchicalDataTemplate DetailTemplate
        { get; set; }

        public override DataTemplate SelectTemplate(object item, DependencyObject container)
        {
            EmployeeInfo emp = item as EmployeeInfo;

            if (emp.ID == 0)
                return HeaderTemplate;
            else
                return DetailTemplate;
        }
    }
}

 

This is an output of the program.

HierarchicalGridOutput

 

Thanks Dave to give an idea of creating multi level Hierarchical Grid.

Advertisements

Responses

  1. Great work, it solved my problem, thanks

    • Thanks for visiting my blog and like it. I am glad that it helped and solved your problem.

      Regards
      Zeeshan Amjad


Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Categories

%d bloggers like this: