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"))
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
nodParent.ImageIndex = 1
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
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
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
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
If e.Node.BackColor.ToArgb = Color.Gray.ToArgb Then e.Cancel = True Microsoft.VisualBasic.Beep() Return End If
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
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.