Introduction
The code fills WPF TreeView
with a multilevel hierarchical data. It steps through all the levels of hierarchy, from top to bottom and adds to each item a collection of its children. The source of data is a self-referencing collection of items with the following fields: Id of Item, Name of Item, Id of parent. The code supports the parent/child hierarchy with one root. Each parent can have numerous children, but a child can have only one parent.
Background
The presented code was written to fill TreeView
UI with the hierarchical data from database. To simplify the example, the database and relevant entity framework's classes were replaced by generic list. The main goal was to create the TreeView
UI using basic, fully controllable statements.
Using the Code
Description of Declared Classes and Objects
GIER_Hierarchy
class and the GIER_Hierarchy_List
generic list were created only to define some examples of hierarchical data, in this case, fictional organizational company structure. It will be our source of data to populate TreeView
. Let's assume that "GIER_Hierarchy_List
" generic list is database table and therefore its contents will be loaded to datatable
object for further processing in memory. - In the next step, all data from this generic list will be loaded into "
InputDT
" datatable
object using foreach
statement. - "
InputDT
" datatable
object reflects the same structure as the "GIER_Hierarchy_List
" list and is used to process the data in memory. During single iteration, "InputDT
" datatable
is joined with "sub
" datatable
using LINQ statement to select rows with children for specific level of hierarchy in the subsequent iterations. "sub
" datatable is an auxiliary object with similar structure as "InputDT
" datatable
, but contains additional "Level
" column. It allows to select only a specific level of hierarchy in the relevant iteration. - There are two special classes to define nodes of the tree:
TreeViewModel
and NodeViewModel
. TreeViewModel
contains "Items
" field to hold the collection of NodeViewModel
objects. The NodeViewModel
object contains the following 4 fields: The element ID (numeric type), element name (string types), expand property (bool type, to define whether node should be expanded or collapsed at the start) and the fourth field is the collection of children of the item. - "
NodesFactory
" is a recursive function that accepts three parameters: "InputDT
" datatable
, "sub
" datatable
, level number. This function contains the entire logic to step through all the levels of hierarchy iteratively to find collections of children and create items of the TreeViewModel
.
Overall Description of How the Code Works
- First, the "
InputDT
" datatable
object is queried using LINQ statement to find the root item. The results are loaded to "sub
" datatable
object and at the same time, the level of hierarchy number 0 is assigned. Further processing takes place inside the "NodesFactory
" function. Joining with "sub
" datatable
and filtering the relevant hierarchy level number makes it possible to find children of the nodes created in the previous iteration. Using recursive function, LINQ statement, joining with "sub
" datatable
and assigning relevant level of hierarchy and then filtering by relevant level ,"InputDT
" datatable
is iteratively queried to find all items for specific level of hierarchy. In each individual iteration, one hierarchy level is processed. During single iteration, using foreach
loop, the NodeViewModel
objects are created for all items within specific level. These nodes are created with empty Children
field first. This field will be filled in the next iteration. In the current iteration, created nodes are added to the children collection field of the nodes created in previous iteration. " List
<nodeviewmodel> nodesList
" list is created to hold NodeViewModel
objects to the next iteration in which their children
collection will be added. - At the end,
TreeViewModel
object is bound with datatreeView
component.
C# Code
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Data;
using System.Linq;
using System.Windows;
namespace TreeView_DataHierarchy
{
public partial class MainWindow : Window, INotifyPropertyChanged
{
public void NodesFactory(DataTable InptDT, DataTable subDT, long level)
{
level = level + 1;
var querryLevel =
from itemsInputDT in InptDT.AsEnumerable()
join itemSub in subDT.AsEnumerable() on
itemsInputDT.Field<long>("GparentId") equals itemSub.Field<long>("Gid")
where itemSub.Field<long>("level") == level - 1
select new { Gid = itemsInputDT.Field<long>("Gid"),
Gname = itemsInputDT.Field<string>("Gname"),
GparentId = itemsInputDT.Field<long>("GparentId"), level = level };
foreach (var x in querryLevel)
{
DataRow rowSub = subDT.NewRow();
rowSub["Gid"] = x.Gid;
rowSub["Gname"] = x.Gname;
rowSub["GparentId"] = x.GparentId;
rowSub["level"] = x.level;
subDT.Rows.Add(rowSub);
nodesList.Add(new NodeViewModel { Id = x.Gid, Name = x.Gname,
Expand = true, Children = new ObservableCollection<NodeViewModel>() });
nodesList.Find(gNode => gNode.Id == x.GparentId).Children.Add
(nodesList.Last());
}
if (querryLevel.Count() > 0)
{ NodesFactory(InptDT, subDT, level); }
}
public event PropertyChangedEventHandler PropertyChanged;
List<NodeViewModel> nodesList = new List<NodeViewModel>();
TreeViewModel MyTreeModel = new TreeViewModel();
public MainWindow()
{
List<GIER_Hierarchy> GIER_Hierarchy_List = new List<GIER_Hierarchy>();
GIER_Hierarchy_List.Add(new GIER_Hierarchy(1, "root", 0));
GIER_Hierarchy_List.Add(new GIER_Hierarchy(2, "Production B", 7));
GIER_Hierarchy_List.Add(new GIER_Hierarchy(12, "Document Control", 17));
GIER_Hierarchy_List.Add(new GIER_Hierarchy(17, "Engineering", 7));
GIER_Hierarchy_List.Add(new GIER_Hierarchy(16, "Executive", 1));
GIER_Hierarchy_List.Add(new GIER_Hierarchy(14, "Facilities and Maintenance", 7));
GIER_Hierarchy_List.Add(new GIER_Hierarchy(10, "Finance", 16));
GIER_Hierarchy_List.Add(new GIER_Hierarchy(9, "Human Resources", 16));
GIER_Hierarchy_List.Add(new GIER_Hierarchy(11, "Information Services", 4));
GIER_Hierarchy_List.Add(new GIER_Hierarchy(4, "Marketing", 16));
GIER_Hierarchy_List.Add(new GIER_Hierarchy(7, "Production", 16));
GIER_Hierarchy_List.Add(new GIER_Hierarchy(8, "Production Control", 7));
GIER_Hierarchy_List.Add(new GIER_Hierarchy(5, "Purchasing", 18));
GIER_Hierarchy_List.Add(new GIER_Hierarchy(13, "Quality Assurance", 7));
GIER_Hierarchy_List.Add(new GIER_Hierarchy(6, "Research and Development", 16));
GIER_Hierarchy_List.Add(new GIER_Hierarchy(3, "Sales", 16));
GIER_Hierarchy_List.Add(new GIER_Hierarchy(15, "Shipping and Receiving", 18));
GIER_Hierarchy_List.Add(new GIER_Hierarchy(19, "Tool Design", 2));
GIER_Hierarchy_List.Add(new GIER_Hierarchy(18, "Logistic", 16));
GIER_Hierarchy_List.Add(new GIER_Hierarchy(20, "Logistic A", 18));
GIER_Hierarchy_List.Add(new GIER_Hierarchy(21, "Logistic A1", 20));
GIER_Hierarchy_List.Add(new GIER_Hierarchy(22, "Logistic A1", 21));
GIER_Hierarchy_List.Add(new GIER_Hierarchy(23, "Logistic A1", 22));
GIER_Hierarchy_List.Add(new GIER_Hierarchy(24, "Logistic A1", 23));
GIER_Hierarchy_List.Add(new GIER_Hierarchy(25, "Logistic A1", 24));
if (GIER_Hierarchy_List.Count > 0)
{
DataTable InputDT = new DataTable();
InputDT.Columns.Add("Gid", typeof(long));
InputDT.Columns.Add("Gname", typeof(string));
InputDT.Columns.Add("GparentId", typeof(long));
foreach (var x in GIER_Hierarchy_List)
{
DataRow rowInput = InputDT.NewRow();
rowInput["Gid"] = x.Gid;
rowInput["Gname"] = x.Gname;
rowInput["GparentId"] = x.GparentId;
InputDT.Rows.Add(rowInput);
}
var queryRoot =
from itemsInputDT in InputDT.AsEnumerable()
where itemsInputDT.Field<long>("GparentId") == 0
select new { Gid = itemsInputDT.Field<long>("Gid"),
Gname = itemsInputDT.Field<string>("Gname"),
GparentId = itemsInputDT.Field<long>("GparentId"), level = 1 };
DataTable sub = new DataTable();
sub.Columns.Add("Gid", typeof(long));
sub.Columns.Add("Gname", typeof(string));
sub.Columns.Add("GparentId", typeof(long));
sub.Columns.Add("level", typeof(long));
foreach (var x in queryRoot)
{
DataRow rowSub = sub.NewRow();
rowSub["Gid"] = x.Gid;
rowSub["Gname"] = x.Gname;
rowSub["GparentId"] = x.GparentId;
rowSub["level"] = 0;
sub.Rows.Add(rowSub);
nodesList.Add(new NodeViewModel
{
Id = x.Gid,
Name = x.Gname,
Expand = true,
Children =
new ObservableCollection<NodeViewModel>()
});
}
MyTreeModel.Items =
new ObservableCollection<NodeViewModel> { nodesList.Last() };
long level = 0;
NodesFactory(InputDT, sub,level);
}
else { MessageBox.Show("List of Directory is empty"); }
InitializeComponent();
GIER_catalogHierarchy.ItemsSource = MyTreeModel.Items;
}
}
public class GIER_Hierarchy
{
public long Gid { get; set; }
public string Gname { get; set; }
public long GparentId { get; set; }
public GIER_Hierarchy(long xGid, string xGname, long xGparentId)
{
this.Gid = xGid;
this.Gname = xGname;
this.GparentId = xGparentId;
}
}
public class TreeViewModel
{
public ObservableCollection<NodeViewModel> Items { get; set; }
}
public class NodeViewModel : INotifyPropertyChanged
{
public long Id { get; set; }
public string _Name;
public bool _Expand;
public string Name
{
get { return _Name; }
set
{
if (_Name != value)
{
_Name = value;
NotifyPropertyChanged("Name");
}
}
}
public bool Expand
{
get { return _Expand; }
set
{
if (_Expand != value)
{
_Expand = value;
NotifyPropertyChanged("Expand");
}
}
}
public ObservableCollection<NodeViewModel> Children { get; set; }
#region INotifyPropertyChanged Members
private void NotifyPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
public event PropertyChangedEventHandler PropertyChanged;
#endregion
}
}
WPF Code
<xmp>
<Window x:Class="TreeView_DataHierarchy.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:TreeView_DataHierarchy"
xmlns:src="clr-namespace:TreeView_DataHierarchy"
Title="MainWindow" Height="350" Width="525">
<Grid>
<TreeView x:Name="GIER_catalogHierarchy" BorderThickness="1"
DataContext="{Binding TreeModel}" SelectedValuePath="Id"
MinWidth="300" MinHeight="440" Width="Auto"
ScrollViewer.VerticalScrollBarVisibility="Auto"
ScrollViewer.HorizontalScrollBarVisibility="Auto"
Margin="1,1,1,1" Height="Auto">
<TreeView.Resources>
<HierarchicalDataTemplate
DataType= "{x:Type local:NodeViewModel}" ItemsSource="{Binding Children}" >
<Grid >
<Grid.ColumnDefinitions>
<ColumnDefinition x:Name="Line" Width="20" />
<ColumnDefinition x:Name="Rectangle" />
<ColumnDefinition x:Name="Empty1" Width="10" />
<ColumnDefinition x:Name="Department" />
</Grid.ColumnDefinitions>
<Border Grid.Column="0" x:Name="HorLn" Margin="9,0,0,2"
HorizontalAlignment="Stretch" Height="1"
BorderThickness="0,0,0,1" VerticalAlignment="Bottom">
<Border.BorderBrush>
<LinearGradientBrush StartPoint="0,0"
EndPoint="2,0" SpreadMethod="Repeat" MappingMode="Absolute">
<GradientStop Color="Transparent" Offset="0" />
<GradientStop Color="Transparent" Offset="0.499" />
<GradientStop Color="#999" Offset="0.5" />
</LinearGradientBrush>
</Border.BorderBrush>
</Border>
<Border Grid.Column="0" x:Name="VerLn" Margin="0,0,1,2"
Grid.RowSpan="2" VerticalAlignment="Stretch"
Width="1" BorderThickness="0,0,1,0" >
<Border.BorderBrush>
<LinearGradientBrush StartPoint="0,0" EndPoint="0,2"
SpreadMethod="Repeat" MappingMode="Absolute">
<GradientStop Color="Transparent" Offset="0" />
<GradientStop Color="Transparent" Offset="0.499" />
<GradientStop Color="#999" Offset="0.5" />
</LinearGradientBrush>
</Border.BorderBrush>
</Border>
<Rectangle Grid.Column="1" Width="14" Height="10"
Stroke="Gold" SnapsToDevicePixels="true" VerticalAlignment="Center">
<Rectangle.Fill>
<LinearGradientBrush EndPoint="0.5,2" StartPoint="0.5,0">
<GradientStop Color="White" Offset="0"/>
<GradientStop Color="Gold" Offset="0.5"/>
<GradientStop Color="Honeydew" Offset="1"/>
</LinearGradientBrush>
</Rectangle.Fill>
</Rectangle>
<Rectangle Grid.Column="1" Width="7" Height="4"
Stroke="Gold" SnapsToDevicePixels="true" VerticalAlignment="Top"
HorizontalAlignment="Left" Fill="White" />
<TextBlock x:Name="textBlockHeader" Grid.Column="3"
Grid.Row="1" Text="{Binding Name}" FontSize="9" />
</Grid>
</HierarchicalDataTemplate>
<Style TargetType="{x:Type TreeViewItem}">
<Style.Triggers>
<DataTrigger Binding="{Binding Expand}" Value="True">
<Setter Property="IsExpanded" Value="True"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TreeView.Resources>
</TreeView>
</Grid>
</Window>
</xmp>
History
- 24th August, 2019: Initial version