Basically Visual

December/January 1997

by Peter G. Aitken 1997

Originally published in Visual Developer magazine


Out of My Tree

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.

Adding Nodes to a Tree

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.

Displaying Images at 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

Working With Nodes

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.

Associating Data With Nodes

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)

There's Lots More!

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.

A Demonstration

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.

Visual Basic 5

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!

Bigger is Better?

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.

Listing 1: TREEVIEW1.FRM, the main form in the Visual Basic TreeView demonstration program.

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

 

Listing 2. FRMADDNODE.FRM, the second form in the Visual Basic TreeView demonstration program.

 

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