(Print this page)

The Treeview control
Published date: Saturday, April 1, 2006
On: Moer and Éric Moreau's web site

I have already used the Treeview control in some previous articles without having explained how to use it and without having added some neat little features. This control is the ideal one for showing hierarchical (parent / child relationship) data. Almost every application has this kind of data to show and not all programmers are ready to use it. Sure there are many other ways of showing this kind of data but many times, the Treeview control is the one dedicated for the task.

Filling the Treeview

The first thing you normally do (after having added a Treeview control to the form) is to fill the control with its data. It may be one thing that stops many programmers using this control is that it cannot be bound. Unlike other controls like a grid, you cannot simply set its DataSource property to a DataTable object you have (simply because the Treeview control does not have this property). Instead, you need to go through your data and create the hierarchy by adding nodes to your Treeview that are related one to the other.

This is what is done in the btnLoadTreeview’ button click event handler. First a DataSet is filled with 2 DataTable objects: the first one contains the Categories table from the Northwind database (will become the parents in the Treeview) and the second contains the Products table). Then a relation is established between the 2 tables (if you never used that object, carefully look at it and also how to use it as it is a great helper in this condition).

'Creates a connection to the NorthWind database
 Dim strCN As String = "Database=(local); Initial Catalog=Northwind; " & _
                       "Integrated security=True;"
 Dim cn As New SqlConnection(strCN)
 'Creates 2 DataAdapters
 Dim daProducts As New SqlDataAdapter("SELECT * FROM Products", cn)
 Dim daCategories As New SqlDataAdapter("SELECT * FROM Categories", cn)
 'Creates and fills the dataset with the 2 DataAdapters
 Dim ds As New DataSet
 daProducts.Fill(ds, "Products")
 daCategories.Fill(ds, "Categories")
 'Creates a relation between the 2 DataTables
 Dim relPC As DataRelation = ds.Relations.Add("relProductsCategories", _
                               ds.Tables("Categories").Columns("CategoryID"), _
                               ds.Tables("Products").Columns("CategoryID"))
Now that we have the data, we can start filling the Treeview control. We first indicate that we want to prevent updating the control until we are done. Otherwise, the control would try to repaint each time a node is added. We then delete all the nodes that could already exist in the Treeview (in case you press the button a second time).

You are now ready to start adding nodes. You first loop through your parents objects which, in our case, is the DataTable containing the Categories. For each row found in this table, you add a node to the Treeview (Treeview1.Nodes.Add) and you keep the reference to this node (into nodParent). You now need to loop through the child elements and this is where the Relation object we created in the DataSet object will be of great help. The GetChildRows method can be applied on a DataRow object to filter for rows matching the relation.

Now that your nodes have been created, you can call the ExpandAll method to open all parent nodes (note that there is also a CollapseAll method). EndUpdate will repaint the Treeview with all the nodes in. Finally, the EnsureVisible method, which gets the index of a node as argument, will ensure that this node is visible. In this case, it will go completely up.

Here is the code that does that:

 With TreeView1
     'prevents the control from painting until the EndUpdate method is called
     .BeginUpdate()
     'Clears/Deletes all nodes that currently exist
     .Nodes.Clear()
     'Loop through all rows of the Categories table
     For Each drCat As DataRow In ds.Tables("Categories").Rows
         'Adds the Category as a root node
         Dim nodParent As TreeNode
         nodParent = TreeView1.Nodes.Add(drCat.Item("CategoryName").ToString)
         'Loops through the products related to the Category
         For Each drProduct As DataRow In drCat.GetChildRows(relPC)
             'Adds the Product as a child of the Category
             Dim nodChild As TreeNode
             nodChild = nodParent.Nodes.Add(drProduct.Item("ProductName").ToString)
             If nodChild.Text.ToUpper.IndexOf("Y") >= 0 Then
                 nodChild.BackColor = Color.Gray
             End If
         Next drProduct
     Next drCat
     'Expands all nodes
     .ExpandAll()
     'Enables the redrawing of the tree view
     .EndUpdate()
     'Ensures that the first node is visible
     .Nodes(0).EnsureVisible()
 End With

Displaying images on each node

Adding icons to each node is very simple. You first need to add an ImageList control to your form and fill it with some icons. Then you need to attach the list to the ImageList property of the Treeview:

Treeview1.ImageList = ImageList1
Once you have that, you can use images from your image list by setting the ImageIndex property of a node object to a valid index of your ImageList:
nodParent.ImageIndex = 1
You could also specify to show a different image when a node is selected by setting the SelectedImageIndex and nothing more:
nodParent.SelectedImageIndex = 1

FullPath and PathSeparator

These 2 properties are very helpful when you want to know where you stand in your hierarchy. For example, in then demo application, I have added a Label (named lblSelectedNode) and this code in the AfterSelect event handler of the Treeview control:

lblSelectedNode.Text = e.Node.FullPath
This will write the full path into the Label control. By full path, I mean the text appearing in the currently selected node and the text of each of its parent. All these elements (starting from the root) will be separated by the character specified by the PathSeparator property which is set to the backslash (\) by default.

Adding checkboxes

This is another very easy task to do. Simply set the CheckBoxes property of the Treeview control to either True or False. In my example, I have added a checkbox on the form to toggle this setting live.

Select/Unselect all features

A feature you may need with checkboxes is the possibility to check (or uncheck) all the child node of a node you check (or uncheck). This can be easily done by trapping the AfterCheck event of the Treeview control like this:

Dim nodX As TreeNode = e.Node
Dim blnChecked As Boolean = nodX.Checked
For intI As Integer = 0 To nodX.Nodes.Count - 1
    SetNodeChecked(nodX.Nodes(intI), blnChecked)
Next
You will notice that another method is called from there. This method is SetNodeChecked and contains these lines of code:
Private Sub SetNodeChecked(ByVal pNode As TreeNode, ByVal pChecked As Boolean)
    pNode.Checked = pChecked
    For intI As Integer = 0 To pNode.Nodes.Count - 1
        SetNodeChecked(pNode.Nodes(intI), pChecked)
    Next
End Sub
In my case, it would have worked even if I haven’t added this recursive call because I only have 2 levels (Categories and Products). This recursive call comes handy when you have more then 2 levels (otherwise, only 1 level deep would be affected by the checkboxes).

Every node can be the parent of other nodes. Each node contains a Nodes collection (that is be empty if the node does not have any children). We know that a node is the parent of other nodes by checking this node collection. This is why we pass pNode.Nodes(intI) when we call the method recursively. We don’t want to pass the current node; we want to pass the child node.

Prevent selection of a node

I have implemented a special feature that will stop the user from changing the checkbox of some node. For the demo, I have decided that all nodes that contain the letter Y in their text value won’t be selectable. When the nodes are created, I have added these 3 lines that are changing the BackColor property for these non-selectable nodes:

If nodChild.Text.ToUpper.IndexOf("Y") >= 0 Then
    nodChild.BackColor = Color.Gray
End If
I can now use this indicator to valid if the checkbox can be changed or not. This validation has to be done in the BeforeCheck event handler like this:
If e.Node.BackColor.ToArgb = Color.Gray.ToArgb Then
    e.Cancel = True
    Microsoft.VisualBasic.Beep()
    Return
End If
Setting the e.Cancel property to True will stop the user from changing the value of the checkbox. The Beep is only to warn the user.

Prevent editing

Treeviews’ nodes are not necessarily read-only. The text can be changed by the user. You need to set the LabelEdit property of the Treeview to true (which is false by default). I have added a checkbox to my demo application to toggle this setting.

Now you will ask this: What if I don’t want some node to be changed? The answer is quite easy again. You will use the BeforeLabelEdit event this time to handle that request. For example, adding this code to this event will prevent the user from editing any nodes having the letter E in its text:

If e.Node.Text.ToUpper.IndexOf("E") >= 0 Then
    e.CancelEdit = True
    MessageBox.Show("You cannot edit this node because it contains the letter E")
End If
There is one small thing I really don’t like about this control in edit mode is that you need to click a node twice to start editing (not a double click but 2 clicks).

Figure 1: The Demo application

Conclusion

I hope that those of you who never used a Treeview control will try it and I also hope that those of you who are already using it have learned a new trick in this column.

I hope you appreciated the topic and see you next month.


(Print this page)