Advanced TreeView Tricks
The TreeView is a sophisticated control, and it provides a great deal of customization possibilities. Some of the additional appearance-related properties are described in Table 6-7.
Table 6-7: TreeView Appearance Properties
PropertyDescription
CheckBoxesSet this to true to display a check box next to each node.
FullRowSelectWhen set to true, selecting a node shows a highlight box that spans the full width of the tree.
HotTrackingWhen set to true, the text in a node changes to a highlighted hyperlink style when the user positions the mouse over it.
IndentSpecifies the left-to-right distance between each level of items in the tree, in pixels.
ShowLines, ShowPlusMinus, and ShowRootLinesBoolean properties that configure the appearance of lines linking each node, the plus/minus box that allows users to easily expand a node, and the root lines that connect the first level of objects together.
SortedWhen set to true, nodes are sorted in each group alphabetically using their text names. There is no way to specify a custom sort order, other than to add the nodes in a predetermined order.
The TreeNode also provides some useful properties that haven't been discussed yet (Table 6-8). Mainly, these properties allow you to determine the state of node. Additional properties exist that let you modify a node's background and foreground color, and determine its relatives, as you saw earlier.
Table 6-8: TreeNode State Properties
PropertyDescription
CheckedTrue if you are using a TreeView with check box nodes, and the node is checked.
IsEditingTrue if the user is currently editing this node's label. Label editing is explained later in this section.
IsExpandedTrue if this node is expanded, meaning its child nodes are displayed.
IsSelectedTrue if this is the currently selected node. Only one node can be selected at a time, and you can control which one is using the TreeView.SelectedNode property.
IsVisibleTrue if the node is currently visible. A node is visible if its parent is collapsed, or if you need to scroll up or down to find it. To programmatically show a node, use its EnsureVisible() method.
Node Pictures
One frequently used feature is the ability to assign icons to each node. As with all modern controls, this works by using a paired ImageList control.
treeFood.ImageList = imagesFood;
You can assign a default picture index that will be used by any node that does not specifically override it:
treeFood.ImageIndex = 0;
You can set an image for each individual node through the properties of the TreeNode object. Each node can have two linked images: a default image, and one that is used when the node is selected.
TreeNode node = new TreeNode("Apples");
node.ImageIndex = 1;
node.SelectedImageIndex = 2;
treeFood.Nodes.Add(node);
Expanding and Collapsing Levels
You've already learned how to react when the user expands and collapses levels. However, you can also programmatically expand and collapse nodes. There are many uses for this trick:
Restoring a TreeView control to its "last viewed" state, so users can continue right where they left off with the control in the exact same state.
Ensuring that a particular node or set of nodes is visible to correspond with another activity. For example, the user might have made a selection in a different part of the window, or might be using a wizard that is stepping through the process.
Configuring the TreeView when the window is first loaded so that the user sees the most important (or most commonly used) nodes.
.NET provides a few ways to accomplish these tasks. First, every node provides four useful methods: Collapse(), Expand(), ExpandAll(), and Toggle(). The Expand() method acts on the immediate children, while ExpandAll() expands the node and all subnodes. To expand or collapse the entire tree, you can use one of the TreeView methods: ExpandAll() or CollapseAll().
node.Expand(); // Expand the node to display its immediate children.
node.Toggle(); // Switches the node: it was expanded, so now it is collapsed.
node.ExpandAll(); // Expand all nodes and subnodes.
tree.ExpandAll(); // Expand the entire tree.
You can also use a node's EnsureVisible() method. This extremely useful method expands whatever nodes are required to make a node visible, and scrolls to the appropriate location. This is extremely useful if you are iterating through a tree looking for a node that matches certain criteria.
// Search the first level of a TreeView control.
foreach (TreeNode node in tree.Nodes)
{
if ((int)(node.Tag) == 12)
{
// Collapse the whole tree to hide unimportant nodes.
tree.CollapseAll();
// Expand just the node that interests the user.
node.EnsureVisible();
break;
}
}
The TreeView control also provides a TopNode property that references the first fully visible node at the top of the current display window. It also provides a VisibleCount property that identifies the maximum number of nodes that can be displayed at a time in the TreeView at its current height.
TreeView Drag-and-Drop
TreeView controls can support drag-and-drop operations just as easily as any other .NET control. However, when information is dragged onto a TreeView, you generally need to determine what node it was "dropped" on. To perform this magic, you need to perform your own hit testing, with a little help from the TreeView.GetNodeAt() method.
The following example presents a form with two TreeViews. The user can drag a node from one TreeView to the other TreeView, or to another location in the same TreeView (see Figure 6-7). When a node is dropped, its content is copied, and the original branch is left untouched. Best of all, the code is generic, meaning that one set of event handlers responds to the events from both trees.

Figure 6-7: Drag-and-drop operations with a TreeView
To start, you need to make sure that both TreeView controls can receive drag-and-drop events. At the same time, disable the HideSelection property so that you can highlight the node that will be the drop target, even if the TreeView doesn't have the focus.
treeOne.AllowDrop = true;
treeTwo.AllowDrop = true;
treeOne.HideSelection = false;
treeTwo.HideSelection = false;
The step is to create the MouseDown event handling logic that starts the drag-and-drop operation. This code needs to investigate whether there is a node under the mouse pointer. If there is, the node is copied (along with all subnodes) and a drag-and-drop operation is started.
private void tree_MouseDown(object sender, System.Windows.Forms.MouseEventArgs e)
{
// Get the tree.
TreeView tree = (TreeView)sender;
// Get the node underneath the mouse.
TreeNode node = tree.GetNodeAt(e.X, e.Y);
tree.SelectedNode = node;
// Start the drag-and-drop operation with a cloned copy of the node.
if (node != null)
{
tree.DoDragDrop(node.Clone(), DragDropEffects.Copy);
}
}
Note that all the TreeView event handlers handle events in both trees. For example, the MouseDown event handler is attached to treeOne.MouseDown and treeTwo.MouseDown. This provides the flexibility that allows the user to drag nodes back and forth between both trees. In addition, this means that the event handler must retrieve the TreeView reference from the sender parameter to determine which tree fired the event.
Next, both trees need to handle the DragOver event. Note that you use this event, instead of the DropEnter event, because the operation is permitted or allowed based on whether there is a node under the current mouse pointer.
private void tree_DragOver(object sender, System.Windows.Forms.DragEventArgs e)
{
// Get the tree.
TreeView tree = (TreeView)sender;
// Drag and drop denied by default.
e.Effect = DragDropEffects.None;
// Is it a valid format?
if (e.Data.GetData(typeof(TreeNode)) != null)
{
// Get the screen point.
Point pt = new Point(e.X, e.Y);
// Convert to a point in the TreeView's coordinate system.
pt = tree.PointToClient(pt);
// Is the mouse over a valid node?
TreeNode node = tree.GetNodeAt(pt);
if (node != null)
{
e.Effect = DragDropEffects.Copy;
tree.SelectedNode = node;
}
}
}
Note that the drag-and-drop events provide mouse coordinates in the screen's frame of reference (measuring from the top left corner of the desktop). To perform the hit testing, you need to convert this point to a point in the TreeView control's coordinate system (which measures from the top left of the control).
Finally, the actual copied node is inserted by a DragDrop event handler. The node that contains the added node is expanded to ensure that the addition is visible.
private void tree_DragDrop(object sender, System.Windows.Forms.DragEventArgs e)
{
// Get the tree.
TreeView tree = (TreeView)sender;
// Get the screen point.
Point pt = new Point(e.X, e.Y);
// Convert to a point in the TreeView's coordinate system.
pt = tree.PointToClient(pt);
// Get the node underneath the mouse.
TreeNode node = tree.GetNodeAt(pt);
// Add a child node.
node.Nodes.Add((TreeNode)e.Data.GetData(typeof(TreeNode)));
// Show the newly added node if it is not already visible.
node.Expand();
}
You can try this example in the TreeViewDragAndDrop project. This example doesn't provide any restrictions—it allows you to copy nodes anywhere you want. Most programs probably add more restrictive logic in the DragOver event handler. In addition, you might want to create a tree where dragging and dropping moves items instead of copies them. In this case, the easiest approach is to store a reference to the original node object (without cloning it):
tree.DoDragDrop(node, DragDropEffects.Copy);
The DragDrop event handler would then remove the node from the source tree, and add it to the target tree. However, you would typically need to perform some validation to ensure that the dragged node is an allowed child of the target node.
TreeNode nodeDragged = e.Data.GetData(typeof(TreeNode));
// Copy to new position.
node.Nodes.Add(nodeDragged.Clone());
// Remove from original position.
nodeDragged.Remove();
Tip
For even more advanced drag-and-drop possibilities, you can use the DoDragDrop() method with an instance of a custom class that encapsulates all the relevant information, instead of just the TreeView object.