Basic TreeView
The TreeView is a hierarchical collection of elements, which are called nodes. This collection is provided through the TreeView.Nodes property. With this collection, it's quite easy to add a few basic nodes:
treeFood.Nodes.Add("Apple");
treeFood.Nodes.Add("Peach");
treeFood.Nodes.Add("Tofu");
In this example, three nodes are added with descriptive text. If you've worked with the TreeView before through its ActiveX control, you might notice that the .NET implementation dodges a few familiar headaches, because it doesn't require a unique key for relating parent nodes to child nodes. This means it's easier to quickly insert a new node. It also means that unless you take specific steps to record a unique identifier with each item, you won't be able to distinguish duplicates. For example, the only difference between the two "Apple" entries in the example is their respective position in the list.
To specify more information about a node, you have to construct a TreeNode object separately, and then add it to the list. In the example that follows, a unique identifier is stored in the Tag property.
TreeNode newNode = new TreeNode();
newNode.Text = "Apple";
newNode.Tag = 1;
treeFood.Nodes.Add(newNode);
In this case, a simple integer is used, but the Tag property can hold any type of object if needed, even a reference to a corresponding database record.
foreach (DataRow drFood in dtFoods.Rows)
{
TreeMode newNode = new TreeNode();
newNode.Text = drFoods["Name"].ToString();
newNode.Tag = drFood;
treeFood.Nodes.Add(newNode);
}
TreeView Structure
Nodes can be nested in a complex structure with a virtually unlimited number of layers. Adding subnodes is similar to adding submenu items. First you find the parent node, and then you add the child node to the parent's Nodes collection.
TreeNode node;
node = treeFood.Nodes.Add("Fruits");
node.Nodes.Add("Apple");
node.Nodes.Add("Peach");
node = treeFood.Nodes.Add("Vegetables");
node.Nodes.Add("Tomato");
node.Nodes.Add("Eggplant");
The Add() method always returns the newly added node object. You can then use this node object to add child nodes. If you wanted to add child nodes to the Apple node you would follow the same pattern, and catch the node reference returned by the Add() method.
This code produces a hierarchical tree structure as shown in Figure 6-5.

Figure 6-5: A basic TreeView
Microsoft suggests that the preferred way to add items to a TreeView is by using the AddRange() method to insert an entire block of nodes at once. It works similarly, but requires an array of node objects.
TreeNode[] nodes = new TreeNode[2];
nodes[0] = new TreeNode("Fruits");
nodes[0].Nodes.Add("Apple");
nodes[0].Nodes.Add("Peach");
nodes[1] = new TreeNode("Vegetables");
nodes[1].Nodes.Add("Tomato");
nodes[1].Nodes.Add("Eggplant");
treeFoods.Nodes.AddRange(nodes);
By using this technique, you ensure that the TreeView is updated all at once, improving performance dramatically. You can achieve a similar performance gain by using the BeginUpdate() and EndUpdate() methods, which suspends the graphical refresh of the TreeView control, allowing you to perform a series of operations at once.
// Suspend automatic refreshing.
treeFood.BeginUpdate();
// (Add or remove several nodes here.)
// Enable automatic refreshing.
treeFood.EndUpdate();
TreeView Navigation
The TreeView's multileveled structure can make it difficult to navigate through your tree structure to perform common tasks. For example, you might want to use a TreeView to provide a hierarchical list of check box settings (as Windows does for the View tab in its Folder Options, shown in Figure 6-6). You can configure the TreeView to display check boxes next to each node by setting a single property:
treeSettings.CheckBoxes = true;

Figure 6-6: Using a TreeView to configure settings
When the OK or Apply button is clicked, you then search through the list of settings and make the corresponding changes.
The following section of code might seem like a reasonable attempt, but it won't work:
foreach (TreeNode node in treeSettings.Nodes)
{
// (Process node here.)
}
The problem is that the TreeView.Nodes collection only contains the first level of the nodes hierarchy, which in this case corresponds to the main groupings (like "Files and Folders.") The correct code would go another level deep:
Dim node, nodeChild As TreeNode
foreach (TreeNode node in treeSettings.Nodes)
{
// (Process first-level node here.)
foreach (TreeNode nodeChild in node.Nodes)
{
// (Process second-level node here.)
}
}
Alternatively, if you have a less structured organization where similar types of elements are held at various levels, you need to search through all the nodes recursively. The following code calls a ProcessNodes procedure recursively until it has walked through the entire tree structure.
private void cmdOK_Click(object sender, System.EventArgs e)
{
// Start the update.
ProcessNodes(treeSettings.Nodes);
}
private void ProcessNodes(TreeNodeCollection nodes)
{
foreach (TreeNode node in nodes)
{
ProcessNode(node);
ProcessNodes(node.Nodes);
}
}
private void ProcessNode(TreeNode node)
{
// Check if the node interests us.
// If it does, process it.
// To verify that this routine works, display the node text.
MessageBox.Show(node.Text);
}
Tip
To count all the nodes in your tree, you don't need to enumerate through the collections and sub-collections. Instead, you can use the TreeView.GetNodeCount() method. Make sure you specify true for the required parameter-this indicates that you want to count the items in subtrees. Each TreeNode object also provides a GetNodeCount() method, allowing you to count the items in selected branches of a tree.You can also use relative-based navigation. In this model, you don't iterate through the whole collection. Instead, you go from a current node to another node.
currentNode = currentNode.Parent.Parent.NextNode;
This example takes the current node, finds its parent (by moving one level up the hierarchy), then finds the parent's parent, and then moves to the next sibling (the next node in the list that is at the same level). If there is no next node, a null reference is returned. If one of the parents is missing, an error occurs. Table 6-4 lists the relative-based navigation properties you can use.
Table 6-4: Relative-based Navigation Properties
Node PropertyMoves…
ParentOne level up the hierarchy, to the node that contains the current node.
FirstNodeOne level down the node hierarchy, to the first node in the current node's Nodes collection.
LastNodeOne level down the node hierarchy, to the last node in the current node's Nodes collection.
PrevNodeTo the node at the same level, but just above the current node.
NextNodeTo the node at the same level, but just below the current node.
The next example shows how you could use the relative-based navigation to walk over every node in a tree.
private void cmdOK_Click(object sender, System.EventArgs e)
{
// Start the update.
ProcessNodes(treeUsers.Nodes.Item[0]);
}
private void ProcessNodes(TreeNode nodeStart)
{
do
{
ProcessNode(nodeStart);
// Check for contained (child nodes).
if (nodeStart.Nodes.Count > 0)
{
ProcessNodes(nodeStart.FirstNode);
}
// Move to the next (sibling) node.
nodeStart = nodeStart.NextNode();
}
while (nodeStart != null);
}
private void ProcessNode(TreeNode node)
{
// Check if the node interests us.
// If it does, process it.
// To verify that this routine works, display the node text.
MessageBox.Show(node.Text);
}
This type of navigation is generally less common in .NET programs, because the collection-based syntax is more readable and easier to deal with.
Note
The Nodes collection is not read-only. That means that you can safely delete and insert nodes while enumerating through the Nodes collection.
Manipulating Nodes
Now that you have a good idea of how to add nodes and find them in the tree structure, it's time to consider how nodes can be deleted and rearranged. Once again, you use the methods of the Nodes collection.
Generally, the best way to delete a node is by first obtaining a reference to the node. You could also remove a node using its index number, but index numbers can change as nodes are removed or if sorting is used, so they raise the potential for unexpected problems.
Once again, consider our tree of food products:
TreeNode node;
node = treeFood.Nodes.Add("Fruits");
node.Nodes.Add("Apple");
node.Nodes.Add("Peach");
node = treeFood.Nodes.Add("Vegetables");
node.Nodes.Add("Tomato");
node.Nodes.Add("Eggplant");
You can now search for the "Fruits" node in the collection and delete it. Note that when you use the Remove() method, all the child nodes are automatically deleted as well.
foreach (TreeNode searchNode in treeFood.Nodes)
{
if (searchNode.Text == "Fruits")
{
treeFood.Nodes.Remove(searchNode);
}
}
You can use the Remove() method to delete a node that exists several layers down the hierarchy. What that means is that if you obtain a reference to the "Apple" node, you can delete it directly from the treeFood.Nodes collection even though the collection doesn't really contain that node.
TreeNode nodeApple, nodeFruits;
nodeFruits = treeFood.Nodes.Add("Fruits");
nodeApple = nodeFruits.Nodes.Add("Apple");
// This works. It finds the nodeApple in the nodeFruits.Nodes sub-collection.
treeFood.Nodes.Remove(nodeApple);
// This also works. It directly removes the apple from nodeFruits.Nodes.
nodeFruits.Nodes.Remove(nodeApple);
The Nodes property provides an instance of the TreeNodeCollection. Table 6-5 lists a few more of its node manipulation features. Some, like the ability to Clear() all child nodes and Insert() a node at a specific position, are particularly useful.
Table 6-5: Useful TreeNodeCollection Methods
MethodDescription
Add()Adds a new node at the bottom of the list.
AddRange() and CopyTo()Allows you to copy node objects to and from an array. This technique and CopyTo() can be used to update a TreeView in a single batch operation, and thereby optimize performance. The CopyTo() method copies the entire tree into an array, which allows you to easily transfer it to another TreeView control or serialize it to disk.
Clear()Clears all the child nodes of the current node. Any sublevels are also deleted, meaning that if you call this method for the TreeView the whole structure is cleared.
Contains()Returns true or false, depending on whether a given node object is currently part of the Nodes collection. If you want to provide a search that is more than one level deep, you need write your own method and use recursion, as shown in the previous examples.
IndexOf()Returns the current (zero-based) index number for a node. Remember, node indexes change as nodes are added and deleted. This method returns −1 if the node is not found.
Insert()This method allows you to insert a node in a specific position. It's similar to the Add() method, but it takes an additional parameter specifying the index number where you want to add the node. The node that is currently there is shifted down. Unlike the Add() method, the Insert() method does not return the node reference.
Remove()Accepts a node reference and removes the node from the collection. All subsequent tree nodes are moved up one position.
.NET provides another way to manipulate nodes-using their own methods. For example, you can delete a node without worrying about what TreeView it belongs to by using the Node.Remove() method. This shortcut is extremely convenient.
nodeApple.Remove();
Nodes also provide a built-in clone method that copies the node and any child nodes. This can allow you to transfer a batch of nodes between TreeView controls without needing to iterate over the Nodes collection. (A node object cannot be assigned to more than one TreeView control.)
// Select the first node.
TreeNode node = treeOrigin.Nodes[0];
// Clone it and all the sublevels.
TreeNode nodeNew = node.Clone();
// Add the nodes to a new tree.
treeDestination.Add(nodeNew);
Selecting Nodes
On their own, TreeNode objects don't raise any events. The TreeView control, however, provides notification about important node actions like selections and expansions. Each of these actions is composed of two events: a "Before" event that occurs before the TreeView display is updated, and an "After" event that allows you to react to the event in the traditional way when it is completed. (You'll see in some of the advanced examples how the "Before" event can allow you to perform just-in-time node additions. This technique is used in Table 6-6 lists the key TreeView events.
Table 6-6: TreeView Node Events
EventDescription
BeforeCheck and AfterCheckOccurs when a user clicks to select or deselect a check box.
BeforeCollapse and AfterCollapseOccurs when a user collapses a node, either by double-clicking it or by using the plus/minus box.
BeforeExpand and AfterExpandOccurs when a user expands a node, either by double-clicking it or by using the plus/minus box.
BeforeSelect and AfterSelectOccurs when a user clicks a node. This event can also be triggered for other reasons. For example, deleting the currently selected node causes another node to be selected.
Every custom event in the TreeView is node-specific, and provides a reference to the relevant node. The TreeView control also inherits some generic events that allow it to react to mouse-clicks and other actions that occur to any part of the control, but these are generally not very useful. These TreeView node-based events provide a special TreeViewEventArgs object. This object has two properties: a Node property that provides the affected node, and an Action property that indicates how the action was triggered. The Action property uses the TreeViewAction enumeration, and can indicate whether an event was caused by a key press, mouse-click, or a node expansion/collapse.
The next example reacts to the AfterSelect event and gives the user the chance to remove the selected node. You'll notice that when a node is deleted, the closest node is automatically selected.
private void treeUsers_AfterSelect(object sender,
System.Windows.Forms.TreeViewEventArgs e)
{
string message;
message = "You selected " + e.Node.Text + " with this action: " +
e.Action.ToString() + "\n\nDelete it?";
DialogResult result;
result = MessageBox.Show(message, "Delete", MessageBoxButtons.YesNo);
if (result == DialogResult.Yes)
{
e.Node.Remove();
}
}
Depending on your TreeView, just having a reference to the node object may not be enough. For example, you might add duplicate node entries into different subgroups. This technique isn't that unusual: for example, you might have a list of team members subgrouped by role (programmer, tester, documenter, and so on). A single team member might play more than one role. However, depending on what subgroup the selected node is in, you might want to perform a different action.
In this case, you need to determine where the node is positioned. You can use the node-relative properties (like Parent) to move up the tree, or you can retrieve a string that represents the full path from the node's FullPath property. A few possible values for the FullPath property are:
Fruits
Fruits\Peach
Country\State\City\Street
In these examples, a backslash is used to separate each tree level, although you can set a different delimiter by setting the TreeView.PathSeparator property.