Using HierarchicalDataTemplate with TreeView in WPF / MVVM

Some instructions on how to create and display a hierarchical data structure in WPF by using HierarchicalDataTemplate with TreeView

Visual Studio 2019 project downloadable from here.

Stack Overflow link

Step 1: Create a new WPF application


Step 2: Create the model class

The Model class we use to define our Directory ‘Item’ structure.

In this instance I’m keeping things really simple, just a ‘Name’ and a List of other ‘DirectoryItem’ objects.

Also contained in this class is a recursive function to traverse the ‘DirectoryItem’ node elements:

Model.cs

using System.Collections.Generic;

namespace WpfTreeViewBinding.Model
{
   public class Item
   {
      public string Name { get; set; }
   }

   public class DirectoryItem : Item
   {
      public DirectoryItem()
      {
         Items = new List<DirectoryItem>();
      }

      public List<DirectoryItem> Items { get; set; }

      public void AddDirItem(DirectoryItem directoryItem)
      {
         Items.Add(directoryItem);
      }

      public List<Item> Traverse(DirectoryItem it)
      {
         var items = new List<Item>();

         foreach (var itm in it.Items)
         {
            Traverse(itm);
            items.Add(itm);
         }

         return items;
      }
   }
}

Step 3: Create the ‘Item Provider’ class

I use this class to knock up an example hierarchical structure of ‘DirectoryItem’ elements and a public property to return this:

ItemProvider.cs

using System.Collections.Generic;
using WpfTreeViewBinding.Model;

namespace WpfTreeViewBinding
{
   public class ItemProvider
   {
      private readonly DirectoryItem _rootDirectoryItem;

      public ItemProvider()
      {
         _rootDirectoryItem = new DirectoryItem {Name = "X"};

         var childItem1 = new DirectoryItem {Name = "A"};

         var grandChildItem11 = new DirectoryItem {Name = "A1"};
         var grandChildItem12 = new DirectoryItem {Name = "A2"};

         var greatgrandChildItem2 = new DirectoryItem {Name = "A2_1"};
         grandChildItem11.AddDirItem(greatgrandChildItem2);

         childItem1.AddDirItem(grandChildItem11);
         childItem1.AddDirItem(grandChildItem12);

         var childItem2 = new DirectoryItem {Name = "B"};
         var childItem3 = new DirectoryItem {Name = "C"};
         var childItem4 = new DirectoryItem {Name = "D"};

         var grandChildItem121 = new DirectoryItem {Name = "B1"};
         childItem2.AddDirItem(grandChildItem121);

         var childList1 = new List<DirectoryItem>
         {
            childItem1,
            childItem2,
            childItem3,
            childItem4
         };

         _rootDirectoryItem.Items = childList1;
      }

      public List<Item> DirItems => _rootDirectoryItem.Traverse(_rootDirectoryItem);
   }
}

Step 4: Create a ViewModel class

This class we use to bind to the XAML elements that we will define shortly.

It contains the binding property ‘DirItems’ which we create using the ‘ItemProvider’ class defined previously.

ViewModel.cs

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using WpfTreeViewBinding.Model;

namespace WpfTreeViewBinding
{
    public class ViewModel : INotifyPropertyChanged
    {
        private List<Item> _dirItems;

        public ViewModel()
        {
            var itemProvider = new ItemProvider();
            DirItems = itemProvider.DirItems;
        }

        public List<Item> DirItems
        {
            get { return _dirItems; }
            set
            {
                _dirItems = value;
                OnPropertyChanged(nameof(DirItems));
            }
        }

        #region INotifyPropertyChanged Members

        public event PropertyChangedEventHandler PropertyChanged;

        #endregion

        protected void OnPropertyChanged(string propertyName)
        {
            VerifyPropertyName(propertyName);
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }

        protected void OnPropertyChanged(int propertyValue)
        {
            VerifyPropertyName(propertyValue);
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyValue.ToString()));
        }

        [Conditional("DEBUG")]
        private void VerifyPropertyName(string propertyName)
        {
            if (TypeDescriptor.GetProperties(this)[propertyName] == null)
                throw new ArgumentNullException(GetType().Name + " does not contain property: " + propertyName);
        }

        [Conditional("DEBUG")]
        private void VerifyPropertyName(int propertyValue)
        {
            if (TypeDescriptor.GetProperties(this)[propertyValue] == null)
                throw new ArgumentNullException(GetType().Name + " does not contain property: " + propertyValue.ToString());
        }
    }
}

Step 5: Define the Main Window xaml.

This xaml we will update to implement the TreeView and HierarchicalDataTemplate elements needed to display the hierarchical tree structure:

MainWindow.xaml

<Window x:Class="WpfTreeViewBinding.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:vm="clr-namespace:WpfTreeViewBinding"
        xmlns:model="clr-namespace:WpfTreeViewBinding.Model"
        mc:Ignorable="d"
        Title="MainWindow" Height="200" Width="325">

    <Window.DataContext>
        <vm:ViewModel />
    </Window.DataContext>

    <TreeView ItemsSource="{Binding DirItems}">
        <TreeView.Resources>
            <HierarchicalDataTemplate DataType="{x:Type model:DirectoryItem}" ItemsSource="{Binding Path=Items}">
                <TextBlock Text="{Binding Path=Name}" />
            </HierarchicalDataTemplate>
        </TreeView.Resources>
    </TreeView>

</Window>

Running the program we can see our ‘DirectoryItem’ elements are displayed hierarchically as shown: