Introduction
You would think it would be simple to allow a user to select more than one item in a TreeView, the same way you can in a ComboBox, and then explore the collection of selected items, but it isn't. Microsoft does not provide an easy way to do so. This article will show you an easy way to implement multi-select, and an easy way to maintain a collection of the nodes that are selected--without deriving a new control and without using recursion.
Background
Microsoft's TreeView
control allows you to display a checkbox next to every node. But, the checkboxes are a bit ugly, and they are next to every node, which may not be what you want.
I knew I could use an ImageList
and substitute my own more graceful checkbox, but the TreeView
control would insist on adding an image to every node. If I placed a blank image in the first position of the ImageList
, that left ugly gaps next to nodes that didn't get a checkbox.
Just as bad, all the solutions I found for building a collection of selected nodes involved using recursion. Implemented properly, recursion is very useful. But, it can create a terrible mess.
I wanted more control than these approaches allowed, so I decided to bend the rules a bit and implement a very simple solution. Rather than displaying the ugly checkboxes, I decided to just change the background color of the selected nodes. And rather than use recursion, I figured a second, hidden, TreeView
could hold my collection of selected nodes.
Using the code
My approach involves trapping the TreeView
AfterSelect
event. In fact, that's where all of the work happens. The code is surprisingly simple. It assumes you have two TreeView
s, one that is marked Visible=False
. Since it's not visible, it can be any size you want, and positioned anywhere you want. Think of it as your hidden Notepad.
Private Sub TreeView1_AfterSelect(ByVal sender As System.Object,
_ByVal e As System.Windows.Forms.TreeViewEventArgs)
_Handles TreeView1.AfterSelect
If TreeView1.SelectedNode.Level = 0 Then Exit Sub
Dim newNode As New TreeNode
newNode = TreeView1.SelectedNode.Clone
With TreeView1.SelectedNode
If .BackColor = Color.White Then
.BackColor = Color.Yellow
TreeView2.Nodes.Add(newNode)
Else
.BackColor = Color.White
TreeView2.Nodes.RemoveByKey(.Name)
End If
TreeView1.SelectedNode = .Parent
End With
End Sub
The first line of this subroutine prevents the user from selecting a root node (level 0). You can add to this restriction to handle other levels of the TreeView
, if you want. For instance, I use this to block all but the lowest level of my TreeView
nodes from being selected, which in my case is level 3. So, my code reads:
If TreeView1.SelectedNode.Level < 3 Then Exit Sub
You can't copy a node from one TreeView
to another directly, so I clone the SelectedNode
. Then, I decide what to do next. If the SelectedNode
has a BackColor
of white, I change it to yellow. This makes it appear as if a highlighter had marked it. You can choose any color you want, of course. After I change it to yellow, I add the cloned node (a perfect copy of the SelectedNode
) to the second, hidden TreeView
. If the SelectedNode
already had a BackColor
of yellow (meaning that the user had selected it previously but now wants to deselect it), I restore its white BackColor
and then remove the cloned node from the second TreeView
.
The result is that the second, hidden TreeView
always contains a collection of the nodes the user has selected in the first, visible TreeView
. Once the user has clicked OK, it's a simple matter to iterate through these chosen nodes:
Private Sub ShowIt(ByVal sender As System.Object, ByVal e As System.EventArgs)
_Handles btnOK.Click
Dim aNode As TreeNode
Dim msg As String = "Selected nodes:" & vbCrLf
For Each aNode In TreeView2.Nodes
msg = msg & aNode.Text & vbCrLf
Next
MsgBox(msg)
End Sub
Points of interest
All of this works because each node must have a unique name. Trying to use the Text
property makes a mess, because you can't guarantee that two nodes will not have the same text.
The last line of the first subroutine above is interesting. I found it annoying that because the focus remained on the SelectedNode
, the change in BackColor
wasn't immediately apparent. Since I knew that every SelectedNode
would have a parent, I decided to shift focus to the parent node.
Conclusion
There are other solutions to these issues, as I mentioned above, and they illustrate some significant programming approaches and creative ideas. One of those solutions might better fit your needs. But, I hope that you find my simple approach a nice alternative.