December/January 1997
by Peter G. Aitken ©1997
Originally published in Visual Developer magazine
Many Visual Basic programmers never even come close to exploring the full range of capabilities in the product. I am guilty of this too, probably not as much as many of you if only because writing this column keeps me active looking into all the nooks and crannies of the Visual Basic tool chest. Even so, I am occasionally pleasantly surprised at finding some very useful doodad that I had been overlooking. This was the case recently with the TreeView control. This object lets you display and manipulate items in a hierarchical view. An example that most of us see every day is the Windows Explorer, which uses a TreeView control to display folders and file names. You can select single or multiple items, open and close branches on the tree, and control the way items are displayed. Each item in a TreeView can have an icon associated with it, such as file folder and page icons used in Windows Explorer. You're not limited to disk-related items, though a TreeView control can be used for any information that is arranged hierarchically.
To be honest, it's easy to see why this control has been overlooked by many Visual Basic programmers. The functionality it provides is not called for all that often. Furthermore, it is not all that easy to understand and use. It is a complicated control, and does not provide the almost-instant gratification that we Visual Basic programmers have perhaps become too accustomed to. You'll have to exercise your noggin a bit before you can get a TreeView control doing just what you want it to.
The main reason for the TreeView control's complexity is its structure. It is not, as are most Visual Basic controls, just a single object with a set of properties and methods. Oh no, that would make life too easy! Let me explain.
I think you can see where some of the confusion comes from. When you want to do something with a TreeView object, it's not always clear whether you use the methods and properties of the TreeView object itself, or whether you must manipulate its associated nodes collection or perhaps work directly with a node object. While the TreeView control by itself can be useful, it really comes into its own when you associate each node in the tree with something else. When a TreeView is used for a folder/file display, then the association is automatic. What about other associations? I'll be showing you how to associate a Text Box with each node in a tree, and you can use the same or similar techniques to associate other objects with TreeView nodes. I won't be able to show all the details of using the TreeView control, but I can get you started so you can explore further on your own. I'll include a sample application that illustrates the techniques I cover.
A TreeView object starts out its life with no nodes. In form design you'll see a few "sample nodes" displayed on the control, but these are present only so you can view the effect of changing properties that determine how the nodes are displayed. They are not present when you run the program. Nodes exist at various levels, with the topmost level being called the root. Two nodes at the same level are siblings. When one node is subsidiary to another, one s the parent and the other is a child. While a program is executing the user can open a node, displaying all of its children (if any), or close it, hiding any child nodes. You can also open and close nodes in code, making it possible to perform such useful tasks as opening or closing all branches on a given tree with a single command.
Nodes must be added programmatically using the Add method of the Nodes collection. Here's the syntax:
node = TV.Nodes.Add(relative, relationship, key, text, image, selectedImage)
TV is the name of the TreeView control. All of the method arguments except one are optional, although it's pretty rare to use this method without at least some of the optional arguments being used. Let's take a look at them.
The relative argument is required when you want to insert the new node in a relationship to an existing node. You specify the relative node by either its index property or its key property (see below)
The relationship argument is used only when you specify a relative. It determines the relationship that the new node has to the existing node. Your choices are as follows:
The key argument is a unique string that identifies the node. If you specify this argument you can later refer to specific nodes by using the Item() method with the key value.
text is the node label that is displayed in the TreeView control. This is the only required argument to the Add() method.
image is the key property of the image in the associated ImageList control that will be displayed as part of the node.
selectedImage specifies the image that will be displayed when the node is selected. If omitted, then the image specified by image will be displayed whether the node is selected or not.
Each node that is added to a tree is automatically assigned a unique Index property. These are nothing more than sequential integers that uniquely identify each node. Each node in a tree, therefore, can always be uniquely identified by its Index property, and also by its Key property if you assigned one. As we will see, it's the Index property that provides a method of linking each note to some other object. Note also that each Node object also has the Tag property that can provide another way of both identifying and linking nodes.
To display images along with the text in TreeView nodes you must associate an ImageList control with the TreeView object. This is one of the relatively simple aspects of working with a TreeView control. You need only to place an ImageList control on your form, then include code to load it with the needed images assigning each one a unique key. The following code snippet loads the image in OPEN.BMP and assigns it the key value "open":
Dim img As ListImage
Set img = ImageList1.ListImages.Add _
( , "open", LoadPicture _
("bitmaps\outline\open.bmp"))
Then, you associate the ImageList control with the TreeView by setting the appropriate property. You can do this at design time, using the TreeView control's Custom Properties dialog box, or at run-time as follows:
TreeView1.ImageList = ImageList1
Finally, each time you add a node to the tree you specify which image is to be displayed with the node, using the image's key to refer to it. You can also specify a different image to be used when the node is open and displaying its child nodes, just like Explorer displays open or closed file folder images as appropriate. This is done by setting the
Where do you get the images? You can create your own, but Visual Basic provides a set of small images designed specifically for use with the TreeView control. They are located in the bitmaps\outline folder:
OPEN.BMP An open file folder
CLOSED.BMP A closed file folder
LEAF.BMP A document page
PLUS.BMP A plus sign
MINUS.BMP A minus sign
At any given moment a TreeView object has at least one selected node (assuming, of course, that you have added one or more nodes to it). The selected node can be specified in code, and it also automatically set when the user clicks a node. You might also expect that the highlight would move to the selected node, but this is not the case. Being selected and being highlighted are two separate things as far as a node on a TreeView control is concerned! In many applications, however, you want them to coincide. You must do this in code using the DropHighlight property as follows:
Set TV.DropHighlight = Nothing
Set TV.DropHighlight = node
The first line removes any existing highlight, and the second line highlights the node referenced by node. Where do you put this code? In an event procedure, of course! Not in the TreeView control's Click procedure, however. This procedure is called whenever the control is clicked, regardless of whether the click happened on a node or elsewhere on the control. Rather, you use the NodeClick event procedure which is called only when a node is clicked:
Private Sub TreeView1_NodeClick(ByVal Node As Node)
Keeping track of nodes which one is selected, which one is highlighted, and so on can indeed be a bit tricky. It is often necessary, as I have done in the demonstration program, to use an independent program variable to hold the Index property of the currently selected node. Then, when the user clicks another node to select it, you can determine the identity of the previously selected node in case you need to manipulate it in some way, such as removing its highlight.
As I mentioned earlier, the TreeView control becomes a lot more useful when you have the capability of associating data with each node. At the simplest level, you can use each Node object's Tag property to hold a chunk of text. While this is suitable for some applications, it is too limited to take you very far.
A better idea is to make use of the fact that each Node object is automatically assigned a unique Index property when it is created. You can use this number as a link between each node and a specific item in any of Visual Basic's data storage methods that uses indexed retrieval. For example, each node's Index could point to a record in a random access file or to an element in an array. Either of these methods would be pretty easy to work out.
Potentially more interesting, I believe, is to combine a TreeView control with a control array. This is the method I have used in the demonstration program. Recall that a control array consists of two or more controls of the same type and name, distinguished from each other by their Index property. Thus you could have ten Text Box controls named MyText: MyText(0), MyText(1), and so on up to MyText(9). You can create a control array at design time, but you have more flexibility if you do it in code. You must place the first element of the array on a form during design, and be sure to set its Index property to 0. Then, use the Load statement to create new array elements. If the control you inserted at design time is named MyText:
Load MyText(Index)
where Index is the index of the new control. The value of Index must be unique for each control in the array, of course, but the values do not have to be sequential. Thus, we can add a new node to a TreeView control, inserting it as a child of the currently selected node, and create a new Text Box to go with it as follows:
Dim newNode as node
Set newNode = TV.Nodes.Add(TV.SelectedItem.Index, _
tvwChild, , newNodeName, pictureKey)
Load MyText(newNode.Index)
I wish I had the time and space to tell you more about this control, but the best I can do is point you in the right direction and let you explore on your own. The TreeView object has a variety of properties that control its appearance, letting you vary things such as the amount of indentation child nodes have with respect to their parent, how lines between nodes are drawn, and the like. You can also specify that all nodes at each level be sorted alphabetically.
The Nodes collection has only a few properties and methods. You can add and remove nodes, clear all nodes at once, and determine the number of nodes present.
Each individual Node object has a set of properties and methods that provide a great deal of flexibility to the programmer. You can, for example, determine the parent, children, root, and siblings of any node, as well as refer to the next and previous node in the Nodes collection. Using these properties you can do things such as delete the current node and all its children, or use drag-and-drop to move nodes around in the tree.
I could go on and on explaining various details of how the TreeView control works, but I think it's better to show you. The code for TREEVIEW is presented in Listings 1 and 2. When you start the program it asks you for the name of the tree's first node. Then you can click the Add a Node button to add a new node as either a child or a sibling to the current node (which you select by clicking). New nodes can also be designated as Documents, in which case a Text Box is created and associated with it, or as a Folder, which gets no Text Box. You can enter text in the Text Boxes, and when you click a different node its text is displayed.
This is most definitely a demonstration program because it lacks many of the features that a real-world program would require, such as the ability to save data to disk or to delete existing nodes. The techniques that it uses could, however, form the nucleus of a useful program that permits the user to organize sections of text in a hierarchical manner. Without much thought I can already imagine many useful enhancements, such as the ability to combine the text from a selected range of nodes into a single document, or to drag and drop nodes to change the tree's organization. There are plenty of possibilities here for you to explore.
We all know that Visual Basic version 5 is coming. My guess is that it will be released early next year. I am not guessing when I say it is going to make a big splash! The first beta is just out, and it is awesome. I am afraid that non-disclosure agreements prevent me from telling you much about it, but I can say that it will produce native compiled code and permit creation of ActiveX controls. I'll also mention that if you're considering abandoning Visual Basic for Java or C++ you might want to hold off. No, you definitely want to hold off! I'll be devoting an entire column to VB5 soon, so hold onto your hats and start saving your pennies!
Not always, but sometimes true. I got tired of fighting with the 15 screen on my NEC MultiSynch monitor and decided to blow a wad on one of those monster screens I have envied on my friends' desks for all too long. I was convinced that it would make my life easier, but I wasn't prepared for just how much easier. For the kind of work that I do, which involves having several apps open at once and constantly referring back and forth, all the extra screen real estate make a big difference. A 20 inch monitor gives you 192 square inches of viewable screen, compared with perhaps 135 for a 17 inch monitor and even less for smaller sizes. It's really convenient to have Word open over here, Visual Basic running over there, Netscape Navigator up in that corner ... well, you get the idea! Of course, if you spend most of your time hunkered down in one application a big screen may not make much difference to you.
VERSION 4.00
Begin VB.Form Form1
Caption = "TreeView Demonstration"
ClientHeight = 7620
ClientLeft = 2325
ClientTop = 2010
ClientWidth = 5775
BeginProperty Font
name = "MS Sans Serif"
charset = 0
weight = 400
size = 12
underline = 0 'False
italic = 0 'False
strikethrough = 0 'False
EndProperty
Height = 7980
Left = 2265
LinkTopic = "Form1"
ScaleHeight = 7620
ScaleWidth = 5775
Top = 1710
Width = 5895
Begin VB.TextBox Text1
BeginProperty Font
name = "MS Sans Serif"
charset = 0
weight = 400
size = 8.25
underline = 0 'False
italic = 0 'False
strikethrough = 0 'False
EndProperty
Height = 1815
Index = 0
Left = 120
MultiLine = -1 'True
ScrollBars = 2 'Vertical
TabIndex = 2
Top = 5760
Visible = 0 'False
Width = 5535
End
Begin VB.CommandButton cmdAddNode
Caption = "&Add a Node"
BeginProperty Font
name = "MS Sans Serif"
charset = 0
weight = 400
size = 8.25
underline = 0 'False
italic = 0 'False
strikethrough = 0 'False
EndProperty
Height = 615
Left = 240
TabIndex = 1
Top = 5040
Width = 5295
End
Begin ComctlLib.ImageList ImageList1
Left = 0
Top = 120
_ExtentX = 794
_ExtentY = 794
BackColor = -2147483643
End
Begin ComctlLib.TreeView TV1
Height = 4740
Left = 120
TabIndex = 0
Top = 240
Width = 5535
_ExtentX = 9763
_ExtentY = 8361
Indentation = 88
PathSeparator = "\"
Style = 7
BeginProperty Font {0BE35203-8F91-11CE-9DE3-00AA004BB851}
name = "MS Sans Serif"
charset = 0
weight = 400
size = 12
underline = 0 'False
italic = 0 'False
strikethrough = 0 'False
EndProperty
MouseIcon = "TreeView1.frx":0000
End
End
Attribute VB_Name = "Form1"
Attribute VB_Creatable = False
Attribute VB_Exposed = False
Option Explicit
' Global variable to hold the Index
' property of the currently selected node.
Dim CurrentNode As Integer
Private Sub cmdAddNode_Click()
' Display the "add a node" form.
frmAddNode.Show 1
' If user selected Cancel,
' don't do anything.
If frmAddNode.txtNodeName.Text = "" Then Exit Sub
' Create variables.
Dim newNode As Node
Dim nodeType As Integer
Dim relation As Integer
Dim picture As String
' Set picture type for document or folder.
If frmAddNode.optDocument = True Then
picture = "leaf"
Else
picture = "closed"
End If
' Specify child node or sibling node.
If frmAddNode.optSibling.Value = True Then
relation = tvwNext
Else
relation = tvwChild
End If
' Be sure the current node is expanded.
TV1.SelectedItem.Expanded = True
' Create the new node.
Set newNode = TV1.Nodes.Add(TV1.SelectedItem.Index, relation, , frmAddNode.txtNodeName.Text, picture)
' Deselect the current node and
' select the new node.
TV1.SelectedItem.Selected = False
TV1.Nodes(newNode.Index).Selected = True
CurrentNode = newNode.Index
If (TV1.Nodes.Item(CurrentNode).Tag = "doc") Then
Text1(CurrentNode).Visible = False
End If
' Unhighlight the highlighted node (if any)
' and highlight the new node.
Set TV1.DropHighlight = Nothing
Set TV1.DropHighlight = newNode
' If the new node is a document node, create its
' Text Box and make it visible.
If frmAddNode.optDocument = True Then
newNode.Tag = "doc"
Load Text1(newNode.Index)
Text1(newNode.Index).Text = Str$(newNode.Index)
Text1(newNode.Index).Visible = True
Else
newNode.Tag = "folder"
End If
End Sub
Private Sub Form_Load()
' Initialize current node pointer.
CurrentNode = -1
' Load the ImageList control with bitmaps.
Dim img As ListImage
Set img = ImageList1.ListImages.Add(, "open", LoadPicture("bitmaps\outline\open.bmp"))
Set img = ImageList1.ListImages.Add(, "closed", LoadPicture("bitmaps\outline\closed.bmp"))
Set img = ImageList1.ListImages.Add(, "leaf", LoadPicture("bitmaps\outline\leaf.bmp"))
' Clear the TreeView control
TV1.Nodes.Clear
' Set TreeView control properties.
TV1.ImageList = ImageList1
TV1.Style = tvwTreelinesPlusMinusPictureText
TV1.LineStyle = tvwTreeLines
' Get top node name.
Dim s As String
s = InputBox("Name for top-level node?")
If s = "" Then End
' Create Node object.
Dim newNode As Node
' Add the first node.
Set newNode = TV1.Nodes.Add(, , , s, "closed")
newNode.Selected = True
CurrentNode = newNode.Index
Set TV1.DropHighlight = newNode
newNode.Tag = "folder"
End Sub
Private Sub TV1_NodeClick(ByVal Node As Node)
' Called when the user clicks a node.
' If the previous node was a document,
' hide its Text Box.
If TV1.Nodes.Item(CurrentNode).Tag = "doc" Then
Text1(CurrentNode).Visible = False
End If
CurrentNode = Node.Index
' If the newly selected node is a document,
' show its Text Box.
If Node.Tag = "doc" Then
Text1(Node.Index).Visible = True
End If
' Highlight the newly selected node.
Set TV1.DropHighlight = Nothing
Set TV1.DropHighlight = Node
End Sub
VERSION 4.00
Begin VB.Form frmAddNode
Caption = "Add a Node"
ClientHeight = 2985
ClientLeft = 4320
ClientTop = 5070
ClientWidth = 5700
Height = 3345
Left = 4260
LinkTopic = "Form2"
ScaleHeight = 2985
ScaleWidth = 5700
Top = 4770
Width = 5820
Begin VB.TextBox txtNodeName
Height = 288
Left = 2472
TabIndex = 8
Top = 492
Width = 3012
End
Begin VB.CommandButton Command1
Cancel = -1 'True
Caption = "Cancel"
Height = 372
Index = 1
Left = 4320
TabIndex = 7
Top = 2352
Width = 1236
End
Begin VB.CommandButton Command1
Caption = "OK"
Default = -1 'True
Height = 372
Index = 0
Left = 4320
TabIndex = 6
Top = 1836
Width = 1236
End
Begin VB.Frame Frame2
Caption = "Node type"
Height = 924
Left = 312
TabIndex = 1
Top = 1356
Width = 1908
Begin VB.OptionButton optDocument
Caption = "&Document"
Height = 240
Left = 264
TabIndex = 5
Top = 600
Width = 1368
End
Begin VB.OptionButton optFolder
Caption = "&Folder"
Height = 252
Left = 264
TabIndex = 4
Top = 288
Value = -1 'True
Width = 1452
End
End
Begin VB.Frame Frame1
Caption = "Node level"
Height = 924
Left = 312
TabIndex = 0
Top = 348
Width = 1908
Begin VB.OptionButton optChild
Caption = "&Child"
Height = 240
Left = 252
TabIndex = 3
Top = 552
Width = 1608
End
Begin VB.OptionButton optSibling
Caption = "&Sibling"
Height = 204
Left = 252
TabIndex = 2
Top = 288
Width = 1476
End
End
Begin VB.Label Label1
Caption = "&Node name:"
Height = 216
Left = 2448
TabIndex = 9
Top = 216
Width = 1488
End
End
Attribute VB_Name = "frmAddNode"
Attribute VB_Creatable = False
Attribute VB_Exposed = False
Private Sub Command1_Click(Index As Integer)
Select Case Index
Case 0: ' OK button
If txtNodeName.Text = "" Then
MsgBox ("You must enter a node name!")
txtNodeName.SetFocus
Exit Sub
Else
Hide
End If
Case 1: ' Cancel button
txtNodeName.Text = ""
Hide
End Select
End Sub