Windows Presentation Foundation - TreeView Control Part 3
Language(s):C#, WPF
Category(s):TreeView
This article will walk you through a simple example of recursive data binding using the WPF TreeView control using hierarchical data binding.

My last two articles on the WPF TreeView control, Part 1 and Part 2, showed you an old school method of dealing with a TreeView control. This method works fine, and should look familiar to anyone who has used the Winforms version of the TreeView control. In this article, we will look at a more native way of doing this in WPF using an observable collection and a HierachicalDataTemplate. A screenshot of the program is shown in Figure 1.

Figure 1: WPF TreeView Control

Figure 1: WPF TreeView Control

Creating the Underlying Classes

First of all, let’s start off by creating the underlying classes that will represent the data in the TreeView control. The Node class will have a Name property declared as string. We will also have a Parent property that points to the parent object this Node is attached to, if any as well as a Children property that will point to any child nodes.  This class will implement the INotifyPropertyChanged interface, which will allow another object to subscribe to the changes made inside this class and basically allow us to point the TreeView control to our data source and forget about it. We will no longer need to add and remove nodes from the TreeView control explicitly.

The INotifyPropertyChanged Interface

We can easily implement this class by first declaring the class and specifying that it implements the INotifyPropertyChanged interface:

class Node : INotifyPropertyChanged

{

}

 

Now, right-click the INotifyPropertyChanged string and select Resolve|using System.ComponentModel; from the popup menu. This will add the declaration for the System.ComponentModel namespace:

using System.ComponentModel;

 

Next, right-click again and select Implement Interface|Implement Interface, which will add the following code:

#region INotifyPropertyChanged Members

 

public event PropertyChangedEventHandler  PropertyChanged;

 

#endregion

   

 By declaring our class as a class that implements the INotifyPropertyChanged interface, we are telling .Net that this class will have an event named PropertyChanged. We can do whatever else we want with the class and as long as it has this event, we are satisfying the requirements for a class that implements the INotifyPropertyChanged. So another class that expects an INotifyPropertyChanged type of class can use our class and all will be right with the world.

We will raise this event in the ‘get’ method of our class properties. It is convenient to declare a method that will accept the property name and properly raise this event:

private void RaisePropertyChanaged(string Property)

{

    if (PropertyChanged != null)

        PropertyChanged(this, new PropertyChangedEventArgs(Property));

}

 

The public properties in this class will call this method:

private string _name = null;

public string Name

{

    get

    {

        return(_name);

    }

    set

    {

        _name = value;

        RaisePropertyChanaged("Name");

    }

}

 

 

The ObservableCollection Base Class

We still have a bit of work to do to the Node class, but we need to put that aside and declare the class that will store a collection of Node objects. This class will be based on the ObservableCollection base class, and is declared as follows:

public class Nodes: ObservableCollection<Node>

{

}

 

Right-click on ‘ObservableCollection’ and select Resolve|using System.Collections.ObjectModel; from the popup menu to add the declaration for the System.Collections.ObjectModel namespace. The Node class will represent a single node in the TreeView and the Nodes class will store a list of Node objects.

A Family of Nodes

Now that we have the Nodes class declared, let’s pop back and finish up the Node class. We will add Parent and Child properties that will represent the Nodes collection that contains this Node and the Nodes collection that can be used to store child nodes. How convenient it is that we just declared such an object. (This stuff just writes itself doesn’t it :-)).

Here is the code for the Parent and Children properties - which are declared within the Node class:

private Nodes _parent = null;

public Nodes Parent

{

    get

    {

        return (_parent);

    }

    set

    {

        _parent = value;

        RaisePropertyChanaged("Parent");

    }

}

 

private Nodes _children = null;

public Nodes Children

{

    get

    {

        return (_children);

    }

    set

    {

        _children = value;

        RaisePropertyChanaged("Children");

    }

}

 

With the properties defined, we will finish up the Node class by adding a constructor that will accept a Name:

public Node(string Name)

{

    this.Name = Name;

    this.Children = new Nodes();

}

 

Before we move on to the rest of the project, let’s revisit the Nodes class. Since it is derived from the ObservableCollection base class, it comes along with an Add method that is used to add a Node object. Since the Add method will be adding a Node whose parent is the Nodes object, we can make life a little easier by overriding the Add method to assign the parent at the time a Node is added. Actually – to be more exact, we can’t actually override the method since it is not defined as virtual, abstract or override (as the compiler will happily tell you if you try), but we can do something very similar by hiding the base Add method. For our purposes, this is simply a matter of syntax. Here’s what this looks like:

new public void Add(Node item)

{

    item.Parent = this;

    base.Add(item);

}

The new keyword tells the compiler that we are declaring a method with the same name as the base class method, which for all practical purposes is the same as an override. This method sets itself as the parent if the item being added, and then adds it.

Rendering the TreeView Control

With the Node and Nodes classes defined, we can move onto the XAML to define the TreeView control. The first thing we need to do is define a HierarchicalDataTemplate that will tell the TreeView control how to display each Node object and where to find that Node’s Children. We will want the Name property to display on each node, and we will want the TreeView control to use that Node’s Children property to render the child items. The HierarchicalDataTemplate will be defined in a Window.Resource tag and looks like the following:

<Window.Resources>

    <HierarchicalDataTemplate x:Key="ShowDataTreeItems"

           ItemsSource="{Binding Path=Children}" >

        <TextBlock Text="{Binding Path=Name}" />

    </HierarchicalDataTemplate>

</Window.Resources>

 

What this says, in XAML-eese is that we are defining a template named “ShowDataTreeItems.” When all the plumbing is connected – i.e. the Binding, then we will look to a property called Children that will give us the source of the nodes items (hence the clever name ItemsSource). Meanwhile, to display a particular node, we will use a block of text and populate it using the Name property.

Now we are ready to render the TreeView control, so let’s just take a look at that now:

<TreeView Name="tvExample" ItemsSource="{Binding}"

          ItemTemplate="{StaticResource ShowDataTreeItems}" />

 

In English this means that we are defining a TreeView control named “tvExample” which is populated using the plumbing or Binding (which will be hooked up later). The description of how to render the control has already been defined using an ItemTemplate which has been defined as a static resource and is named “ShowDataTreeItems.”

Populating the TreeView control

Populating the TreeView control can now be done by simply manipulating the Node and Nodes classes and pointing the TreeView control to the top level Nodes object. We will do this in the constructor for our main form, which I have named “winMain.” The following code will load the TreeView control to look like the screen-shot in Figure 1:

public winMain()

{

    InitializeComponent();

    Nodes nodes = new Nodes();

    LoadTree(nodes);

    tvExample.DataContext = nodes;

 

    EnableControls();

}

private void LoadTree(Nodes nodes)

{

    Node family = new Node(nodes, "Arthopod");

    //nodes.Add(family);

 

    Nodes classifications = family.Children;

    Node classification = new Node(classifications, "Insect");

    //classifications.Add(classification);

   

    Nodes genera = classification.Children;

    Node genus = new Node(genera, "Diptera");

    //genera.Add(genus);

 

    Nodes species = genus.Children;

    species.Add(new Node("Fly"));

    species.Add(new Node("Gnat"));

    species.Add(new Node("Mosquito"));

}

 

Adding and Deleting Nodes

Ok! We are almost done. The code for the AddNode button will first check to make sure a node has been selected. Assuming we have a selected node, we will pop up a dialog box to allow the user to enter a Name for the new node and specify whether the node should be a child or sibling of the currently selected node. The dialog window is shown in figure 2

Figure 1: WPF TreeView Control

Figure 2 – the Add Node form

The Add Node window defines an enum called NodeTypeResult that returns one of three values indicating whether the user has chosen to add a sibling node, child node or has clicked the Cancel button. There is a single method defined (not shown here) that simply displays itself modally, and returns the result to the caller. The code for adding a node is as follows:

private void btnAddNode_Click(object sender, RoutedEventArgs e)

{

    if (tvExample.SelectedItem == null)

        return;

 

    string nodeText = null;

    winGetNodeType dialog = new winGetNodeType();

    winGetNodeType.NodeTypeResult result = dialog.GetNodeType(ref nodeText);

 

    if (result == winGetNodeType.NodeTypeResult.Cancel)

        return;

 

    Node selectedNode = (Node)tvExample.SelectedItem;

    if (result == winGetNodeType.NodeTypeResult.AddChild)

        AddNode(selectedNode.Children, nodeText);

    else if (result == winGetNodeType.NodeTypeResult.AddSibling)

        AddNode(selectedNode.Parent, nodeText);

}

private void AddNode(Nodes Parent, string NodeText)

{

    Parent.Add(new Node(NodeText));

}

 

Deleting a node is even simpler. We just need to grab the selected node and call the Remove method on its Parent object:

private void btnDeleteNode_Click(object sender, RoutedEventArgs e)

{

    if (tvExample.SelectedItem == null)

        return;

 

    Node selectedNode = (Node)tvExample.SelectedItem;

    selectedNode.Parent.Remove(selectedNode);

}

 

So that’s it. I hope you will find this example helpful. The complete source code for this program can be downloaded from the link above.

Like what you see? Want more? Hire me! :-)

 

 
From: akohan - 2012-06-29
I thought your article would be nice but first you have not completed it and worse than that your download link gives nothing but Page Not found. You started it nice but at the end is a mess. No idea what Nodes.cs class contents are or what EnableControls might be so ... you be the judge.

From: akohan - 2012-06-29
Your constructor is declared such to accept one argument as string but you are passing two arguments one class type and 2nd a string. What is this? very bad

From: akohan - 2012-06-29
Your constructor is declared such to accept one argument as string but you are passing two arguments one class type and 2nd a string. What is this? very bad

This article has been viewed 18489 times.
The examples on this page are presented "as is". They may be used in code as long as credit is given to the original author. Contents of this page may not be reproduced or published in any other manner what so ever without written permission from Idioma Software Inc.