Visual basic

Listing 8.14: Setting the Captions of the Undo and Redo Commands
Private Sub EditToolStripMenuItem DropDownOpened(...)
Handles EditToolStripMenuItem.DropDownOpened
If Editor.UndoActionName <> ”” Then
UndoToolStripMenuItem.Text =
”Undo ” & Editor.UndoActionName
UndoToolStripMenuItem.Enabled = TruePetroutsos V1 c08.tex Page 299 01/28/2008 1:24pm
UndoToolStripMenuItem.Text = ”Undo”
UndoToolStripMenuItem.Enabled = False
End If
If Editor.RedoActionName <> ”” Then
RedoToolStripMenuItem.Text =
”Redo” & Editor.RedoActionName
RedoToolStripMenuItem.Enabled = True
RedoToolStripMenuItem.Text = ”Redo”
RedoToolStripMenuItem.Enabled = False
End If
End Sub
When the user selects one of the Undo or Redo commands, the code simply calls the
appropriate method from within the menu item’s Click event handler, as shown in Listing 8.15.
Listing 8.15: Undoing and Redoing Actions
Private Sub RedoToolStripMenuItem Click(...)
Handles RedoToolStripMenuItem.Click
If Editor.CanRedo Then Editor().Redo()
End Sub
Private Sub UndoToolStripMenuItem Click(...)
Handles UndoToolStripMenuItem.Click
If Editor.CanUndo Then Editor.Undo()
End Sub
Calling the CanUndo and CanRedo method is unnecessary; if the corresponding action can’t be
performed, the two menu items will be disabled, but an additional check does no harm.
The Format Menu
The commands of the Format menu control the alignment and the font attributes of the current
selection. The Font command displays the Font dialog box and then assigns the font selected by
the user to the current selection. Listing 8.16 shows the code behind the Font command.
Listing 8.16: The Font Command
Private Sub FontToolStripMenuItem Click(...)
Handles FontToolStripMenuItem.Click
If Not Editor.SelectionFont Is Nothing Then
FontDialog1.Font = Editor.SelectionFont
ElsePetroutsos V1 c08.tex Page 300 01/28/2008 1:24pm
FontDialog1.Font = Nothing
End If
FontDialog1.ShowApply = True
If FontDialog1.ShowDialog() = DialogResult.OK Then
Editor.SelectionFont = FontDialog1.Font
End If
End Sub
Notice that the code preselects a font in the dialog box, which is the font of the current selection.
If the current selection isn’t formatted with a single font, no font is preselected.
To enable the Apply button of the Font dialog box, set the control’s ShowApply property to True
and insert the following statement in its Apply event handler:
Private Sub FontDialog1 Apply(
ByVal sender As Object,
ByVal e As System.EventArgs)
Handles FontDialog1.Apply
Editor.SelectionFont = FontDialog1.Font
End Sub
The options of the Align menu set the RichTextBox control’s SelectionAlignment property
to different members of the HorizontalAlignment enumeration. The Align  Left command, for
example, is implemented with the following statement:
Editor.SelectionAlignment = HorizontalAlignment.Left
The Search & Replace Dialog Box
The Find command in the Edit menu opens the dialog box shown in Figure 8.10, which per-
forms search-and-replace operations (whole-word or case-sensitive match, or both). The Search &
Replace form (it’s the frmFind form in the project) has its TopMost property set to True, so that
it remains visible while it’s open, even if it doesn’t have the focus. The code behind the buttons
on this form is quite similar to the code for the Search & Replace dialog box of the TextPad appli-
cation, with one basic difference: the RTFPad project’s code uses the RichTextBox control’s Find
method; the simple TextBox control doesn’t provide an equivalent method and we had to use the
methods of the String class to perform the same operations. The Find method of the RichTextBox
control performs all types of searches, and some of its options are not available with the IndexOf
method of the String class.
To invoke the Search & Replace dialog box, the code calls the Show method of the frmFind
form, as discussed in Chapter 6, via the following statement:
The Findmethod of the RichTextBox control allows you to performcase-sensitive or -insensitive
searches, as well as search for whole words only. These options are speci?ed through an argument
of the RichTextBoxFinds type. The SetSearchMode() function (see Listing 8.17) examines the set-
tings of the two check boxes at the bottom of the form and sets the Find method’s search mode.Petroutsos V1 c08.tex Page 301 01/28/2008 1:24pm
Figure 8.10
The Search & Replace
dialog box of the RTFPad
Listing 8.17: Setting the Search Options
Function SetSearchMode() As RichTextBoxFinds
Dim mode As RichTextBoxFinds =
If chkCase.Checked = True Then
mode = mode Or RichTextBoxFinds.MatchCase
End If
If chkWord.Checked = True Then
mode = mode Or RichTextBoxFinds.WholeWord
End If
Return mode
End Function
The Click event handlers of the Find and Find Next buttons call this function to retrieve the
constant that determines the type of search speci?ed by the user on the form. This value is then
passed to the Find method. Listing 8.18 shows the code behind the Find and Find Next buttons.
Listing 8.18: The Find and Find Next Commands
Private Sub bttnFind Click(...)
Handles bttnFind.Click
Dim wordAt As Integer
Dim srchMode As RichTextBoxFinds
srchMode = SetSearchMode()
wordAt = frmEditor.Editor.Find(
txtSearchWord.Text, 0, srchMode)
If wordAt = -1 Then
MsgBox(”Can’t find word”)
Exit SubPetroutsos V1 c08.tex Page 302 01/28/2008 1:24pm
End If
bttnFindNext.Enabled = True
bttnReplace.Enabled = True
bttnReplaceAll.Enabled = True
End Sub
Private Sub bttnFindNext Click(...)
Handles bttnFindNext.Click
Dim selStart As Integer
Dim srchMode As CompareMethod
srchMode = SetSearchMode()
selStart = frmEditor.Editor.Find(
frmEditor.Editor.SelectionStart + 2,
If selStart = -1 Then
MsgBox(”No more matches”)
Exit Sub
End If
selStart, txtSearchWord.Text.Length)
End Sub
Notice that both event handlers call the ScrollToCaret method to force the selected text to
become visible — should the Find method locate the desired string outside the visible segment
of the text.
The BottomLine
Use the OpenFileDialog and SaveFileDialog controls to prompt users for ?lenames.
Windows applications use certain controls to prompt users for common information, such as
?lenames, colors, and fonts. Visual Studio provides a set of controls, which are grouped in the
Dialogs section of the Toolbox. All common dialog controls provide a ShowDialog method,
which displays the corresponding dialog box in a modal way. The ShowDialog method returns
a value of the DialogResult type, which indicates how the dialog box was closed, and you
should examine this value before processing the data.
Master It Your application needs to open an existing ?le. How will you prompt users for
the ?le’s name?
Master It You’re developing an application that encrypts multiple ?les (or resizes many
images) in batch mode. How will you prompt the user for the ?les to be processed?
Use the ColorDialog and FontDialog controls to prompt users for colors and typefaces.
The Color and Font dialog boxes allow you to prompt users for a color value and a font,Petroutsos V1 c08.tex Page 303 01/28/2008 1:24pm
respectively. Before showing the corresponding dialog box, set its Color or Font property
according to the current selection, and then call the control’s ShowDialog method.
Master It How will you display color attributes in the Color dialog box when you open it?
How will you display the attributes of the selected text’s font in the Font dialog box when
you open it?
Use the RichTextBox control as an advanced text editor to present richly formatted text.
The RichTextBox control is an enhanced TextBox control that can display multiple fonts and
styles, format paragraphs with different styles, and provide a few more advanced text-editing
features. Even if you don’t need the formatting features of this control, you can use it as an
alternative to the TextBox control. At the very least, the RichTextBox control provides more
editing features, a more-useful undo function, and more-?exible search features.
Master It You want to display a document with a title in large, bold type, followed by a
couple of items in regular style. How will you create a document like the following one on
a RichTextBox control?
Document’s Title
    Item 1
         Description for item 1
    Item 2
         Description for item 2Petroutsos V1 c08.tex Page 304 01/28/2008 1:24pmPetroutsos c09.tex V2 - 01/28/2008 1:28pm Page 305
Chapter 9
The TreeView and ListView
In Chapter 6, ‘‘Basic Windows Controls,’’ you learned how to use the ListBox control for
displaying lists of strings and storing objects. The items of a ListBox control can be sorted, but
they have no particular structure. I’m sure most of you wish that the ListBox control had more
‘‘features,’’ such as the means to store additional information along with each item or to present
hierarchical lists. A hierarchical list is a tree that re?ects the structure of the list: items that belong
to other items appear under their parent with the proper indentation. For instance, a list of city
and state names should be structured so that each city appears under the corresponding state.
The answer to the shortcomings of the ListBox control can be found in the TreeView and
ListView controls. These twoWindows controls are among the more-advanced ones, and they are
certainly more dif?cult to program than the ones discussed in the preceding chapters. These two
controls, however, are the basic makings of unique user interfaces, as you’ll see in this chapter’s
examples. The TreeView and ListView controls implement two of the more-advanced data struc-
tures and were designed to hide much of the complexity of these structures — and they do this
very well.
In this chapter, you’ll learn how to do the following:
? Create and present hierarchical lists by using the TreeView control
? Create and present lists of structured items by using the ListView control
Understanding the ListView, TreeView, and ImageList
I will start with a general discussion of the two controls to help you understand what they do and
when to use them. A basic understanding of the data structures they implement is also required
to use them ef?ciently in your applications. Then I’ll discuss their members and demonstrate
how to use the controls. If you ?nd the examples too dif?cult to understand, you can always
postpone the use of these controls in your applications.
Some of the code I present in this chapter can be used as is in many situations, so you should
look at the examples and see whether you can incorporate some of their code in your applications.
The ListView and TreeView controls are excellent tools for designing elaborate Windows inter-
faces, and I feel they deserve to be covered in detail. It’s also common to use the ImageList control
in conjunction with the ListView and TreeView controls. The purpose of the ImageList control is
to store the images that we want to display, along with the items of the other two controls, so I’ll
discuss brie?y the ImageList control in this chapter.Petroutsos c09.tex V2 - 01/28/2008 1:28pm Page 306
Figure 9.1 shows the TreeView and ListView controls used in tandem. What you see in
Figure 9.1 is Windows Explorer, a utility for examining and navigating your hard disk’s struc-
ture. The left pane, where the folders are displayed, is a TreeView control. The folder names are
displayed in a manner that re?ects their structure on the hard disk. You can expand and contract
certain branches and view only the segment(s) of the tree structure you’re interested in.
Figure 9.1
Windows Explorer is
made up of a Tree-
View (left pane) and
a ListView (right pane)
The right pane is a ListView control. The items on the ListView control can be displayed in
?ve ways (as large or small icons, as a list, on a grid, or tiled). They are the various views you
can set through the View menu of Windows Explorer. Although most people prefer to look at the
contents of the folders as icons, the most common view is the Details view, which displays not
only ?lenames, but also their attributes. In the Details view, the list can be sorted according to any
of its columns, making it easy for the user to locate any item based on various criteria (?le type,
size, creation date, and so on). A Windows Explorer window with a detailed view of the ?les is
shown later in this chapter, in Figure 9.4.
Tree and List Structures
The TreeView control implements a data structure known as a tree. A tree is the most appropriate
structure for storing hierarchical information. The organizational chart of a company, for example,
is a tree structure. Every person reports to another person above him or her, all the way to the
president or CEO. Figure 9.2 depicts a possible organization of continents, countries, and cities
as a tree. Every city belongs to a country, and every country to a continent. In the same way,
every computer ?le belongs to a folder that may belong to an even bigger folder, and so on up to
the drive level. You can’t draw large tree structures on paper, but it’s possible to create a similar
structure in the computer’s memory without size limitations.
Each item in the tree of Figure 9.2 is called a node, and nodes can be nested to any level. Oddly,
the top node is the root of the tree, and the subordinate nodes are called child nodes.Ifyoutryto
visualize this structure as a real tree, think of it as an upside-down treewith the branches emerging
from the root. The end nodes, which don’t lead to any other nodes, are called leaf nodes or
end nodes.Petroutsos c09.tex V2 - 01/28/2008 1:28pm Page 307
Figure 9.2
The world viewed as a
Berlin Munich Frankfurt
Germany France Spain
Africa Asia Europe S. America 1st level nodes
2nd level nodes
3rd level nodes
Globe Root node

To locate a city, you must start at the root node and select the continent to which the city
belongs. Then you must ?nd the country (in the selected continent) to which the city belongs.
Finally, you can ?nd the city you’re looking for. If it’s not under the appropriate country node, it
doesn’t exist.
TreeView Items Are Just Strings
The items displayed on a TreeView control are just strings. Moreover, the TreeView control doesn’t
require that the items be unique. You can have identically named nodes in the same branch — as
unlikely as this might be for a real application. There’s no property that makes a node unique in the
tree structure or even in its own branch.
You can also start with a city and ?nd its country. The country node is the city node’s parent
node. Notice that there is only one route from child nodes to their parent nodes, which means that
you can instantly locate the country or continent of a city. The data of Figure 9.2 is shown in Figure
9.3 in a TreeView control. Only the nodes we’re interested in are expanded. The plus sign indicates
that the corresponding node contains child nodes. To view them, click the button with the plus
sign to expand the node.
The tree structure is ideal for data with parent-child relations (relations that can be described as
belongs to or owns). The continents-countries-cities data is a typical example. The folder structure
on a hard disk is another typical example. Any given folder is the child of another folder or the
root folder.
Many programs are based on tree structures. Computerized board games use a tree structure
to store all possible positions. Every time the computer has to make a move, it locates the board’s
status on the tree and selects the ‘‘best’’ next move. For instance, in tic-tac-toe, the tree structure
that represents the moves in the game has nine nodes on the ?rst level, which correspond to all the
possible positions for the ?rst token on the board (the X or O mark). Under each possible initial
position, there are eight nodes, which correspond to all the possible positions of the second token
on the board (one of the nine positions is already taken). On the second level, there are 9 × 8,
or 72, nodes. On the third level, there are 7 child nodes under each node that correspond to all
the possible positions of the third token, a total of 72 × 7, or 504 nodes, and so on. In each node,
you can store a value that indicates whether the corresponding move is good or bad. When the
computer has to make a move, it traverses the tree to locate the current status of the board, and
then it makes a good move.
Of course, tic-tac-toe is a simple game. In principle, you could design a chess game by using a
tree. This tree, however,would growso large so quickly that it couldn’t be stored in any reasonablePetroutsos c09.tex V2 - 01/28/2008 1:28pm Page 308
amount of memory. Moreover, scanning the nodes of this enormous tree would be an extremely
slow process. If you also consider that chess moves aren’t just good or bad (there are better and
not-so-good moves), and you must look ahead many moves to decide which move is the best
for the current status of the board, you’ll realize that this ad hoc approach is totally infeasible.
Practically speaking, such a program requires either in?nite resources or in?nite time. That’s why
the chess-playing algorithms use heuristic approaches, which store every recorded chess game in a
database and consult this database to pick the best next move.
Figure 9.3
The tree of Figure 9.2
implemented with a
TreeView control
Maintaining a tree structure is a fundamental operation in software design; computer science
students spend a good deal of their time implementing tree structures. Fortunately, with Visual
Basic you don’t have to implement tree structures on your own. The TreeView control is a mech-
anism for storing hierarchically structured data in a control with a visible interface. The TreeView
control hides (or encapsulates, in object-oriented terminology) the details of the implementation
and allows you to set up tree structures with a few lines of code — in short, all the gain without
the pain (almost).
The ListView control implements a simpler structure, known as a list. A list’s items aren’t
structured in a hierarchy; they are all on the same level and can be traversed serially, one after the
other. You can also think of the list as a multidimensional array, but the list offers more features. A
list item can have subitems and can be sorted according to any column. For example, you can set
up a list of customer names (the list’s items) and assign a number of subitems to each customer:
a contact, an address, a phone number, and so on. Or you can set up a list of ?les with their
attributes as subitems. Figure 9.4 shows a Windows folder mapped on a ListView control. Each
?le is an item, and its attributes are the subitems. As you already know, you can sort this list by
?lename, size, ?le type, and so on. All you have to do is click the header of the corresponding
The ListView control is a glori?ed ListBox control. If all you need is a control to store sorted
objects, use a ListBox control. If you want more features, such as storing multiple items per row,Petroutsos c09.tex V2 - 01/28/2008 1:28pm Page 309
sorting them in different ways, or locating them based on any subitem’s value, you must consider
the ListView control. You can also look at the ListView control as a view-only grid.
Figure 9.4
A folder’s ?les displayed
in a ListView control
(Details view)
The TreeView and ListView controls are commonly used along with the ImageList control. The
ImageList control is a simple control for storing images so they can be retrieved quickly and used
at runtime. You populate the ImageList control with the images you want to use on your interface,
usually at design time, and then you recall them by an index value at runtime. Before we get into
the details of the TreeView and ListView controls, a quick overview of the ImageList control is
in order.
The ImageList Control
The ImageList is a simple control that stores images used by other controls at runtime. For
example, a TreeView control can use icons to identify its nodes. The simplest and quickest method
of preparing these images is to create an ImageList control and add to it all the icons you need for
decorating the TreeView control’s nodes. The ImageList control maintains a series of bitmaps in
memory that the TreeView control can access quickly at runtime. Keep in mind that the ImageList
control can’t be used on its own and remains invisible at runtime.
To use the ImageList control in a project, double-click its icon in the Toolbox (you’ll ?nd it in the
Components tab) to place an instance of the control on your form. To load images to an ImageList
control, locate the Images property in the Properties window and click the ellipsis button next
to the property name. Alternatively, you can select the Choose Images command of the control’s
context menu. The Images Collection Editor dialog box (see Figure 9.5) will pop up, and you can
load all the images you want by selecting the appropriate ?les. All the images should have the
same dimensions — but this is not a requirement. Notice that the ImageList control doesn’t
resize the images; you must make sure that they have the proper sizes before loading them into
the control.
To add an image to the collection, click the Add button. You’ll be prompted to select an image
?le through the Open File dialog box. Each image you select is added to the list. When you select
an image in this list, the properties of the image are displayed in the same dialog box — but you
can’t change these properties, except for the image’s name, which is the ?le’s name by default. Add
a few images and then close the Images Collection Editor. In the control’s Properties window, you
can set the size of all images and the TransparentColor property, which is a color that will bePetroutsos c09.tex V2 - 01/28/2008 1:28pm Page 310
treated as transparent for all images (this color is also known as the key color). The images will be
resized accordingly by the control as they’re displayed.
Figure 9.5
The Images Collection
Editor dialog box
The other method of adding images to an ImageList control is to call the Add method of the
Images collection, which contains all the images stored in the control. To add an image at runtime,
you must ?rst create an Image object with the image (or icon) you want to add to the control and
then call the Add method as follows:
where image is an Image object with the desired image. You will usually call this method as
where path is the full path of the ?le with the image.
The Images collection of the ImageList control is a collection of Image objects, not the ?les in
which the pictures are stored. This means that the image ?les need not reside on the computer
on which the application will be executed, as long as they have been added to the collection at
design time.
The TreeViewControl
Let’s start our discussion with a few simple properties that you can set at design time. To experi-
ment with the properties discussed in this section, open the TreeViewDemo project. The project’s
main form is shown in Figure 9.6. After setting some properties (they are discussed next), run the
project and click the Populate button to populate the control. After that, you can click the other
buttons to see the effect of the various property settings on the control.Petroutsos c09.tex V2 - 01/28/2008 1:28pm Page 311
Figure 9.6
The TreeViewDemo
project demonstrates
the basic properties
and methods of the
TreeView control.
Here are the basic properties that determine the appearance of the control:
ShowCheckBoxes If this property is True, a check box appears in front of each node. If the
control displays check boxes, you can select multiple nodes; otherwise, you’re limited to a
single selection.
FullRowSelect This True/False value determines whether a node will be selected even if the
user clicks outside the node’s caption.
HideSelection This property determines whether the selected node will remain highlighted
when the focus is moved to another control. By default, the selected node doesn’t remain
highlighted when the control loses the focus.
HotTracking This property is another True/False value that determines whether nodes are
highlighted as the pointer hovers over them.When it’s True, the TreeView control behaves like
a web document with the nodes acting as hyperlinks — they turn blue while the pointer hovers
over them. Use the NodeMouseHover event to detect when the pointer hovers over a node.
Indent This property speci?es the indentation level in pixels. The same indentation applies
to all levels of the tree—each level is indented by the same number of pixels with respect to its
parent level.
PathSeparator A node’s full name is made up of the names of its parent nodes, separated by
a backslash. To use a different separator, set this property to the desired symbol.
ShowLines The ShowLines property is a True/False value that determines whether the
control’s nodes will be connected to its parent items with lines. These lines help users visualize
the hierarchy of nodes, and it’s customary to display them.
ShowPlusMinus The ShowPlusMinus property is a True/False value that determines
whether the plus/minus button is shown next to the nodes that have children. The plus button
is displayed when the node is collapsed, and it causes the node to expand when clicked. Like-
wise, the minus sign is displayed when the node is expanded, and it causes the node to collapsePetroutsos c09.tex V2 - 01/28/2008 1:28pm Page 312
when clicked. Users can also expand the current node by pressing the left-arrow button and
collapse it with the right-arrow button.
ShowRootLines This is another True/False property that determines whether there will
be lines between each node and root of the tree view. Experiment with the ShowLines and
ShowRootLines properties to ?nd out how they affect the appearance of the control.
Sorted This property determines whether the items in the control will be automatically
sorted. The control sorts each level of nodes separately. In our Globe example, it will sort the
continents, then the countries within each continent, and then the cities within each country.
Adding Nodes at Design Time
Let’s look now at the process of populating the TreeView control. Adding an initial collection of
nodes to a TreeView control at design time is trivial. Locate the Nodes property in the Properties
window, and you’ll see that its value is Collection. To add items, click the ellipsis button, and the
TreeNode Editor dialog box will appear, as shown in Figure 9.7. To add a root item, just click
the Add Root button. The new item will be named Node0 by default. You can change its caption
by selecting the item in the list and setting its Text property accordingly. You can also change the
node’s Name property, as well as the node’s appearance by using the NodeFont, FontColor,and
ForeColor properties.
Figure 9.7
The TreeNode Editor
dialog box
To specify an image for the node, set the control’s ImageList property to the name of an
ImageList control that contains the appropriate images, and then set either the node’s ImageKey
property to the name of the image, or the node’s ImageIndex property to the index of the desired
image in the ImageList control. If you want to display a different image when the control is
selected, set the SelectedImageKey or the SelectedImageIndex property accordingly.
You can add root items by clicking the Add Root button, or you can add items under the
selected node by clicking the Add Child button. Follow these steps to enter the root node with the
string Globe, a child node for Europe, and two more nodes under Europe: Germany and Italy. I’m
assuming that you’re starting with a clean control. If your TreeView control contains any items,
clear them all by selecting one item at a time in the list and pressing the Delete key, or clicking the
delete button (the one with the X icon) on the dialog box.Petroutsos c09.tex V2 - 01/28/2008 1:28pm Page 313
Click the Add Root button ?rst. A new node is added automatically to the list of nodes, and it is
named Node0. Select it with the mouse, and its properties appear in the right pane of the TreeNode
Editor window. Here you can change the node’s Text property to GLOBE. You can specify the
appearance of each node by setting its font and fore/background colors.
Then click the Add Child button, which adds a new node under the GLOBAL root node. Select
it with the mouse as before, and change its Text property to Europe. Then select the newly added
node in the list and click the Add Child button again. Name the new node Germany.You’ve
successfully added a small hierarchy of nodes. To add another node under Europe, select the
Europe node in the list and click the Add Child button again. Name the new item Italy.
Continue adding a few cities under each country. You might add child nodes under the wrong
parent, which can happen if you forget to select the proper parent node before clicking the Add
Child button. To delete a node, select it with themouse and click the Delete button.Note that when
a node is deleted, all the nodes under it are deleted, too. Moreover, this action can’t be undone. So
be careful when deleting nodes.
Click the OK button to close the TreeNode Editor’s window and return to your form. The
nodes you added to the TreeView control are there, but they’re collapsed. Only the root nodes are
displayed with the plus sign in front of their names. Click the plus sign to expand the tree and see
its child nodes. The TreeView control behaves the same at design time as it does at runtime — as
far as navigating the tree goes, at least.
The nodes added to a TreeView control at design time will appear each time the form is loaded.
You can add new nodes through your code, and you will see how this is done in the following
Adding Nodes at Runtime
Adding items to the control at runtime is a bit more involved. All the nodes belong to the control’s
Nodes collection, which is made up of TreeNode objects. To access the Nodes collection, use
the following expression, where TreeView1 is the control’s name and Nodes is a collection of
TreeNode objects:
This expression returns a collection of TreeNode objects and exposes the proper members for
accessing and manipulating the individual nodes. The control’s Nodes property is the collection of
all root nodes.
To access the ?rst node, use the expression TreeView.Nodes(0) (thisistheGlobenodeinour
example). The Text property returns the node’s value, which is a string. TreeView1.Nodes(0).
Text is the caption of the root node on the control. The caption of the second node on the same
level is TreeView1.Nodes(1).Text,andsoon.
The following statements print the strings shown highlighted below them (these strings are not
part of the statements; they’re the output that the statements produce):
ItalyPetroutsos c09.tex V2 - 01/28/2008 1:28pm Page 314
Let’s take a closer look at these expressions. TreeView1.Nodes(0) is the ?rst root node, the
Globe node. Under this node, there is a collection of nodes, the TreeView1.Nodes(0).Nodes
collection. Each node in this collection is a continent name. The ?rst node in this collection is
Europe, and you can access it with the expression TreeView1.Nodes(0).Nodes(0).Ifyouwant
to change the appearance of the node Europe, type a period after the preceding expression to
access its properties (the NodeFont property to set its font, the ForeColor property to set it color,
the ImageIndex property, and so on). Likewise, this node has its own Nodes collection, which
contains the countries under the speci?c continent.
Adding New Nodes
The Add method adds a new node to the Nodes collection. The Add methodacceptsasanargument
a string or a TreeNode object. The simplest form of the Add method is
newNode = Nodes.Add(nodeCaption)
where nodeCaption is a string that will be displayed on the control. Another form of the Add
method allows you to add a TreeNode object directly (nodeObj is a properly initialized TreeNode
newNode = Nodes.Add(nodeObj)
To use this form of the method, you must ?rst declare and initialize a TreeNode object:
Dim nodeObj As New TreeNode
nodeObj.Text = ”Tree Node”
nodeObj.ForeColor = Color.BlueViolet
The last overloaded form of the Add method allows you to specify the index in the current
Nodes collection, where the node will be added:
newNode = Nodes.Add(index, nodeObj)
The nodeObj TreeNode object must be initialized as usual.
To add a child node to the root node, use a statement such as the following:
To add a country under Asia, use a statement such as the following:
The expressions can get quite lengthy. The proper way to add child items to a node is to create
a TreeNode variable that represents the parent node, under which the child nodes will be added.
Let’s say that the ContinentNode variable in the following example represents the node Europe:
Dim ContinentNode As TreeNode
ContinentNode = TreeView1.Nodes(0).Nodes(2)Petroutsos c09.tex V2 - 01/28/2008 1:28pm Page 315
Then you can add child nodes to the ContinentNode node:
To add yet another level of nodes, the city nodes, create a new variable that represents a speci?c
country. The Add method actually returns a TreeNode object that represents the newly added
node, so you can add a country and a few cities by using statements such as the following:
Dim CountryNode As TreeNode
CountryNode = ContinentNode.Nodes.Add(”Germany”)
Then you can continue adding countries under another continent as follows:
CountryNode = ContinentNode.Nodes.Add(”Italy”)
The Nodes Collection Members
The Nodes collection exposes the usual members of a collection. The Count property returns the
number of nodes in the Nodes collection. Again, this is not the total number of nodes in the control,
just the number of nodes in the current Nodes collection. The expression
returns the number of all nodes in the ?rst level of the control. In the case of the Globe example,
it returns the value 1. The expression
returns the number of continents in the Globe example. Again, you can simplify this expression
by using an intermediate TreeNode object:
Dim Continents As TreeNode
Continents = TreeView1.Nodes(0)
”There are ” & Continents.Nodes.Count.ToString &
” continents on the control”)
The Clear method removes all the child nodes from the current node. If you apply this method to
the control’s root node, it will clear the control. To remove all the cities under the Germany node,
use a statement such as the following:
This example assumes that the third node under Globe corresponds to Europe, and the second
node under Europe corresponds to Germany.Petroutsos c09.tex V2 - 01/28/2008 1:28pm Page 316
The Item property retrieves a node speci?ed by an index value. The expression Nodes.Item(1)
is equivalent to the expression Nodes(1). Finally, the Remove method removes a node from the
Nodes collection. Its syntax is
where index istheorderofthenodeinthecurrent Nodes collection. To remove the selected node,
call the Remove method on the SelectedNode property without arguments:
Or you can apply the Remove method to a TreeNode object that represents the node you want
to remove:
Dim Node As TreeNode
Node = TreeView1.Nodes(0).Nodes(7)
There are four properties that allow you to retrieve any node at the current segment of the
tree: FirstNode, NextNode, PrevNode,and LastNode. Let’s say the current node is the Germany
node. The FirstNode property will return the ?rst city under Germany (the ?rst node in the
current segment of the tree), and LastNode will return the last city under Germany. PrevNode and
NextNode allow you to iterate through the nodes of the current segment: They return the next
and previous nodes on the current segment of the tree (the sibling nodes, as they’re called). See the
section called ‘‘Enumerating the Nodes Collection’’ later in this chapter for an example.
Basic Nodes Properties
There are a few properties you will ?nd extremely handy as you program the TreeView control.
The IsVisible property is a True/False value indicating whether the node to which it’s applied
is visible. To bring an invisible node into view, call its EnsureVisible method:
If Not TreeView1.SelectedNode.IsVisible Then
End If
How can the selected node be invisible? It can, if you select it from within your code in a
search operation. The IsSelected property returns True if the speci?ed node is selected, while
the IsExpanded property returns True if the speci?ed node is expanded. You can toggle a node’s
state by calling its Toggle method. You can also expand or collapse a node by calling its Expand
or Collapse method, respectively. Finally, you can collapse or expand all nodes by calling the
CollapseAll or ExpandAll method of the TreeView control.
VB 2008 at Work: The TreeViewDemo Project
It’s time to demonstrate the members discussed so far with an example. The project you’ll build in
this section is the TreeViewDemo project. The project’s main form is shown in Figure 9.6.
The Add Categories button adds the three top-level nodes to the TreeView control via the
statements shown in Listing 9.1. These are the control’s root nodes. The other two Add buttons
add nodes under the root nodes.Petroutsos c09.tex V2 - 01/28/2008 1:28pm Page 317
Listing 9.1: The Add Categories Button
Protected Sub AddCategories Click(...)
Handles AddCategories.Click
End Sub
When these statements are executed, three root nodes are added to the list. After clicking the
Add Categories button, your TreeView control looks like the one shown here.
To add a few nodes under the node Colors, you must retrieve the Colors Nodes collection and
add child nodes to this collection, as shown in Listing 9.2.
Listing 9.2: The Add Colors Button
Protected Sub AddColors Click(...)
Handles AddColors.Click
Dim cnode As TreeNode
cnode = TreeView1.Nodes(2)
End Sub
When these statements are executed, three more nodes are added under the Colors node, but
the Colors node won’t be expanded. Therefore, its child nodes won’t be visible. To see its child
nodes, you must double-click the Colors node to expand it (or click the plus sign in front of it,
if there is one). The same TreeView control with its Colors node expanded is shown to the left.
Alternatively, you can add a statement that calls the Expand method of the cnode object, after
adding the color nodes to the control:
cnode.Expand()Petroutsos c09.tex V2 - 01/28/2008 1:28pm Page 318
Run the project, click the ?rst button (Add Categories), and then click the second button (Add
Colors). If you click the Add Colors button ?rst, you’ll get a NullReferenceException, indicating
that the node can’t be inserted unless its parent node already exists. I added a few statements in
the TreeViewDemo project’s code to disable the buttons that generate similar runtime errors.
To add child nodes under the Shapes node, use the statements shown in Listing 9.3. This is the
Add Shapes button’s Click event handler.
Listing 9.3: The Add Shapes Button
Protected Sub AddShapes Click(...)
Handles AddShapes.Click
Dim snode As TreeNode
snode = treeview1.Nodes(0)
End Sub
If you run the project and click the three buttons in the order in which they appear on the
form, the TreeView control will be populated with colors and shapes. If you double-click the items
Colors and Shapes, the TreeView control’s nodes will be expanded.
Notice that the code knows the order of the root node to which it’s adding child nodes. This
approach doesn’t work with a sorted tree. If your TreeView control is sorted, you must create a
hierarchy of nodes explicitly by using the following statements:
snode = TreeView1.Nodes.Add(”Shapes”)
These statements will work regardless of the control’s Sorted property setting. The three
shapes will be added under the Shapes node, and their order will be determined automatically.
Of course, you can always populate the control in any way you like and then turn on the Sorted
property.Petroutsos c09.tex V2 - 01/28/2008 1:28pm Page 319
Inserting a Root Node
Let’s revise the code we’ve written so far to display all the nodes under a new header. In other
words, we’ll add a new node called Items that will act as the root node for existing nodes. It’s not
a common operation, but it’s an interesting example of how tomanipulate the nodes of a TreeView
control at runtime.
First, wemust add the new root node. Before we do so, however,wemust copy all the ?rst-level
nodes into local variables. We’ll use these variables to add the current root nodes under the new
(and single) root node. There are three root nodes currently in our control, so we need three local
variables. The three variables are of the TreeNode type, and they’re set to the root nodes of the
original tree. Then we must clear the entire tree, add the new root node (the Items node), and
?nally add all the copied nodes under the new root. The code behind the Move Tree button is
shown in Listing 9.4.
Listing 9.4: Moving an Entire Tree
Protected Sub MoveTree Click(...)
Handles bttnMoveTree.Click
Dim colorNode, shapeNode, solidNode As TreeNode
colorNode = TreeView1.Nodes(0)
shapeNode = TreeView1.Nodes(1)
solidNode = TreeView1.Nodes(2)
End Sub
You can revise this code so that it uses an array of Node objects instead of individual variables
to store all the root nodes. For a routine that will work with any tree, you must assume that the
number of nodes is unknown, so the ArrayList would be a better choice. The following loop stores
all the root nodes of the TreeView1 control to the TVList ArrayList:
Dim TVList As New ArrayList
Dim node As TreeNode
For Each node in TreeView1.Nodes
Likewise, the following loop extracts the root nodes from the TVList ArrayList:
Dim node As TreeNode
Dim itm As Object
For Each itm In TVList
node = CType(itm, TreeNode)Petroutsos c09.tex V2 - 01/28/2008 1:28pm Page 320
Enumerating the Nodes Collection
As you saw in the previous example, a Node object can include an entire tree under it. When we
move a node, it takes with it the entire Nodes collection located under it. You can scan all the
nodes in a Nodes collection by using a loop, which starts with the ?rst node and then moves to the
next node with the help of the FirstNode and NextNode properties. The following loop prints
the names of all continents in the GlobeTree control:
Dim CurrentNode As TreeNode
CurrentNode = GlobeTree.Nodes(0).Nodes(0).FirstNode
While CurrentNode IsNot Nothing
CurrentNode = CurrentNode.NextNode
End While
The last property demonstrated by the TreeViewDemo project is the Sorted property, which
sorts the child nodes of the node to which it’s applied. When you set the Sorted property of a
node to True, every child node you attach to it will be inserted automatically in alphabetical order.
If you reset the Sorted property to False, any child nodes you attach will be appended to the end
of the existing sorted nodes.
VB 2008 at Work: The Globe Project
The Globe project demonstrates many of the techniques we’ve discussed so far. It’s not the
simplest example of a TreeView control, and its code is lengthy, but it will help you understand
how to manipulate nodes at runtime. Because TreeView is not a simple control, before ending this
section I want to show you a nontrivial example that you can use as a starting point for your own
custom applications.
The Globe project consists of a single form, which is shown in Figure 9.8. The TreeView control
at the left contains a rather obvious tree structure that shows continents, countries, and cities. The
control is initially populated with the continents, which were added at design time. The countries
and cities are added from within the form’s Load event handler. Although the continents were
added at design time, there’s no particular reason not to add them to the control at runtime. It
would have been simpler to add all the nodes at runtime by using the TreeNode Editor, but I
decided to add a few nodes at design time just for demonstration purposes.
When a node is selected from the TreeView control, its text is displayed in the TextBox controls
at the bottom of the form. When a continent name is selected, the continent’s name appears in
the ?rst TextBox, and the other two TextBoxes are empty. When a country is selected, its name
appears in the second TextBox, and its continent appears in the ?rst TextBox. Finally, when a city
is selected, it appears in the third TextBox, along with its country and continent in the other two
You can also use the same TextBox controls to add new nodes. To add a new continent, just
supply the name of the continent in the ?rst TextBox and leave the other two empty. To add a new
country, supply its name in the second TextBox and the name of the continent it belongs to in the
?rst one. Finally, to add a city, supply a continent, country, and city name in the three TextBoxes.
The program will add new nodes as needed.Petroutsos c09.tex V2 - 01/28/2008 1:28pm Page 321
Figure 9.8
The Globe project
Run the Globe application and expand the continents and countries to see the tree structure
of the data stored in the control. Add new nodes to the control, and enumerate these nodes by
clicking the buttons on the right-hand side of the form. These buttons list the nodes at a given
level (continents, countries, and cities). When you add new nodes, the code places them in their
proper place in the list. If you specify a new city and a new country under an existing continent,
a new country node will be created under the speci?ed continent, and a new city node will be
inserted under the speci?ed country.
Adding New Nodes
Let’s take a look at the code of the Globe project. We’ll start by looking at the code that populates
the TreeView control. The root node (GLOBE) and the continent names were added at design time
through the TreeNode Editor.
When the application starts, the code adds the countries to each continent and adds the cities
to each country. The code in the form’s Load event goes through all the continents already in
the control and examines their Text properties. Depending on the continent represented by the
current node, the code adds the corresponding countries and some city nodes under each
country node.
If the current node is Africa, the ?rst country to be added is Egypt. The Egypt node is added
to the ContinentNode variable. The new node is returned as a TreeNode object and is stored in
the CountryNode variable. Then the code uses this object to add nodes that correspond to cities
under the Egypt node. The form’s Load event handler is quite lengthy, so I’m showing only the
code that adds the ?rst country under each continent and the ?rst city under each country (see
Listing 9.5). The variable GlobeNode is the root node of the TreeView control, and it was declared
and initialized with the following statement:
Dim GlobeNode As TreeNode = GlobeTree.Nodes(0)Petroutsos c09.tex V2 - 01/28/2008 1:28pm Page 322
Listing 9.5: Adding the Nodes of Africa
For Each ContinentNode In GlobeNode.Nodes
Select Case ContinentNode.Text
Case ”Europe”
CountryNode = ContinentNode.Nodes.Add(”Germany”)
Case ”Asia”
CountryNode = ContinentNode.Nodes.Add(”China”)
Case ”Africa”
CountryNode = ContinentNode.Nodes.Add(”Egypt”)
Case ”Oceania”
CountryNode = ContinentNode.Nodes.Add(”Australia”)
Case ”N. America”
CountryNode = ContinentNode.Nodes.Add(”USA”)
CountryNode.Nodes.Add(”New York”)
Case ”S. America”
CountryNode = ContinentNode.Nodes.Add(”Argentina”)
End Select
The remaining countries and their cities are added via similar statements, which you can
examine if you open the Globe project. Notice that the GlobeTree control could have been pop-
ulated entirely at design time, but this wouldn’t be much of a demonstration. Let’s move on to a
few more interesting aspects of programming the TreeView control.
Retrieving the Selected Node
The selected node is given by the property SelectedNode. After retrieving the selected node,
you can also retrieve its parent node and the entire path to the root node. The parent node of the
selected node is TreeView1.SelectedNode.Parent. If this node has a parent, you can retrieve
it by calling the Parent property of the previous expression. The FullPath property of a node
retrieves the selected node’s full path. The FullPath property of the Rome node is as follows:
The slashes separate the segments of the node’s path. As mentioned earlier, you can specify
any other character for this purpose by setting the control’s PathSeparator property.
To remove the selected node from the tree, call the Remove method:
If the selected node is a parent control for other nodes, the Remove method will take with
it all the nodes under the selected one. To select a node from within your code, set thePetroutsos c09.tex V2 - 01/28/2008 1:28pm Page 323
control’s SelectedNode property to the TreeNode object that represents the node you want
to select.
One of the operations you’ll want to perform with the TreeView control is to capture the
selection of a node. The TreeView control ?res the BeforeSelect and AfterSelect events, which
notify your application about the selection of another node. If you need to know which node was
previously selected, you must use the BeforeSelect event. The second argument of both events
has two properties, TreeNode and Action, which let you ?nd out the node that ?red the event
and the action that caused it. The e.Node property is a TreeViewNode object that represents the
selected node. Use it in your code as you would use any other node of the control. The e.Action
property is a member of the TreeViewAction enumeration (ByKeyboard, ByMouse, Collapse,
Expand, Unknown). Use this property to ?nd out the action that caused the event. The actions of
expanding and collapsing a tree branch ?re their own events, which are the BeforeExpand/
AfterExpand and the BeforeCollapse/AfterCollapse events, respectively.
The Globe project retrieves the selected node and extracts the parts of the node’s path. The
individual components of the path are displayed in the three TextBox controls at the bottom of
the form. Listing 9.6 shows the event handler for the TreeView control’s AfterSelect event.
Listing 9.6: Processing the Selected Node
Private Sub GlobeTree AfterSelect(...)
Handles GlobeTree.AfterSelect
If GlobeTree.SelectedNode Is Nothing Then Exit Sub
Dim components() As String
txtContinent.Text = ””
txtCountry.Text = ””
txtCity.Text = ””
Dim separators() As Char
separators = GlobeTree.PathSeparator.ToCharArray
components =
If components.Length > 1 Then
txtContinent.Text = components(1)
If components.Length > 2 Then
txtCountry.Text = components(2)
If components.Length > 3 Then
txtCity.Text = components(3)
End Sub
The Split method of the String data type extracts the parts of a string that are delimited by the
PathSeparator character (the backslash character). If any of the captions contain this character,
you should change the default to a different character by setting the PathSeparator property to
some other character.
The code behind the Delete Current Node and Expand Current Node buttons is simple. To
delete a node, call the selected node’s Remove method. To expand a node, call the selected node’s
Expand method.Petroutsos c09.tex V2 - 01/28/2008 1:28pm Page 324
Processing Multiple Selected Nodes
The GlobeTree control has its ShowCheckBoxes property set to True so that users can select
multiple nodes. I added this feature to demonstrate how you can allow users to select any number
of nodes and then process them.
As you will notice by experimenting with the TreeView control, you can select a node that has
subordinate nodes, but these nodes will not be affected; they will remain deselected (or selected,
if you have already selected them). In most cases, however, when we select a parent node, we
actually intend to select all the nodes under it. When you select a country, for example, you’re in
effect selecting not only the country, but also all the cities under it. The code of the Process Selected
Nodes button assumes that when a parent node is selected, the code must also select all the nodes
under it.
Let’s look at the code that iterates through the control’s nodes and isolates the selected ones.
It doesn’t really process them; it simply prints their captions in the ListBox control. However,
you can call a function to process the selected nodes in any way you like. The code behind the
Process Selected Nodes button starts with the continents. It creates a TreeNodeCollection with
all the continents and then goes through the collection with a For Each...Next loop. At each step,
it creates another TreeNodeCollection, which contains all the subordinate nodes (the countries
under the selected continent) and goes through the new collection. This loop is also interrupted
at each step to retrieve the cities in the current country and process them with another loop. The
code behind the Process Selected Nodes button is straightforward, as you can see in Listing 9.7.
Listing 9.7: Processing All Selected Nodes
Protected Sub bttnProcessSelected Click(...)
Handles bttnProcessSelected.Click
Dim continent, country, city As TreeNode
Dim Continents, Countries, Cities As TreeNodeCollection
Continents = GlobeTree.Nodes(0).Nodes
For Each continent In Continents
If continent.Checked Then ListBox1.Items.Add(continent.FullPath)
Countries = continent.Nodes
For Each country In Countries
If country.Checked Or country.Parent.Checked Then
ListBox1.Items.Add(” ” & country.FullPath)
Cities = country.Nodes
For Each city In Cities
If city.Checked Or city.Parent.Checked Or
city.Parent.Parent.Checked Then
ListBox1.Items.Add(” ” & city.FullPath)
End Sub
Thecodeexaminesthe Checked property of the current node, as well as the Checked property
of the parent node, all the way to the root node. If any of them is True, the node is consideredPetroutsos c09.tex V2 - 01/28/2008 1:28pm Page 325
selected. You should try to add the appropriate code to select all subordinate nodes of a parent
node when the parent node is selected (whether you deselect the subordinate nodes when the
parent node is deselected is entirely up to you and depends on the type of application you’re
developing). The Nodes collection exposes the GetEnumerator method, and you can revise the last
listing so that it uses an enumerator in place of each For Each...Next loop. If you want to retrieve
the selected nodes only, and ignore the unselected child nodes of a selected parent node, use the
CheckedNodes collection.
Adding New Nodes
The Add This Node button lets the user add new nodes to the tree at runtime. The number and
type of the node(s) added depend on the contents of the TextBox controls:
? If only the ?rst TextBox control contains text, a new continent will be added.
? If the ?rst two TextBox controls contain text:
? If the continent exists, a new country node is added under the speci?ed continent.
? If the continent doesn’t exist, a new continent node is added, and then a new country
node is added under the continent’s node.
? If all three TextBox controls contain text, the program adds a continent node (if needed),
then a country node under the continent node (if needed), and ?nally, a city node under
the country node.
Obviously, you can omit a city, or a city and country, but you can’t omit a continent name.
Likewise, you can’t specify a city without a country, or a country without a continent. The code
will prompt you accordingly when it detects any condition that prevents it from adding the new
node. If the node exists already, the program selects the existing node and doesn’t issue any
warnings. The Add This Node button’s code is shown in Listing 9.8.
Listing 9.8: Adding Nodes at Runtime
Private Sub bttnAddNode Click(...)
Handles bttnAddNode.Click
Dim nd As TreeNode
Dim Continents As TreeNode
If txtContinent.Text.Trim <> ”” Then
Continents = GlobeTree.Nodes(0)
Dim ContinentFound, CountryFound, CityFound As Boolean
Dim ContinentNode, CountryNode, CityNode As TreeNode
For Each nd In Continents.Nodes
If nd.Text.ToUpper = txtContinent.Text.ToUpper Then
ContinentFound = True
Exit For
End If
If Not ContinentFound Then
nd = Continents.Nodes.Add(txtContinent.Text)
End IfPetroutsos c09.tex V2 - 01/28/2008 1:28pm Page 326
ContinentNode = nd
If txtCountry.Text.Trim <> ”” Then
Dim Countries As TreeNode
Countries = ContinentNode
If Not Countries Is Nothing Then
For Each nd In Countries.Nodes
If nd.Text.ToUpper = txtCountry.Text.ToUpper Then
CountryFound = True
Exit For
End If
End If
If Not CountryFound Then
nd = ContinentNode.Nodes.Add(txtCountry.Text)
End If
CountryNode = nd
If txtCity.Text.Trim <> ”” Then
Dim Cities As TreeNode
Cities = CountryNode
If Not Cities Is Nothing Then
For Each nd In Cities.Nodes
If nd.Text.ToUpper = txtCity.Text.ToUpper Then
CityFound = True
Exit For
End If
End If
If Not CityFound Then
nd = CountryNode.Nodes.Add(txtCity.Text)
End If
CityNode = nd
End If
End If
End If
End Sub
The listing is quite lengthy, but it’s not hard to follow. First, it attempts to ?nd a continent
that matches the name in the ?rst TextBox. If it succeeds, it does not need to add a new continent
node. If not, a new continent node must be added. To avoid simple data-entry errors, the code
converts the continent names to uppercase before comparing them to the uppercase of each node’s
name. The same happens with the countries and the cities. As a result, each node’s pathname
is unique — you can’t have the same city name under the same country more than once. It is
possible, however, to add the same city name to two different countries.
Listing Continents/Countries/Cities
The three buttons ListContinents, ListCountries, and ListCities populate the ListBox control with
the names of the continents, countries, and cities, respectively. The code is straightforward and
is based on the techniques discussed in previous sections. To print the names of the continents,Petroutsos c09.tex V2 - 01/28/2008 1:28pm Page 327
it iterates through the children of the GLOBE node. Listing 9.9 shows the complete code of the
ListContinents button.
Listing 9.9: Retrieving the Continent Names
Private Sub bttnListContinents Click(...)
Handles bttnListContinents.Click
Dim Nd As TreeNode, continentNode As TreeNode
Dim continent As Integer, continents As Integer
Nd = GlobeTree.Nodes(0)
continents = Nd.Nodes.Count
continentNode = Nd.Nodes(0)
For continent = 1 To continents
continentNode = continentNode.NextNode
End Sub
The code behind the ListCountries button is equally straightforward, although longer. It must
scan each continent, and within each continent, it must scan in a similar fashion the continent’s
child nodes. To do this, you must set up two nested loops: the outer one to scan the continents,
and the inner one to scan the countries. The complete code for the ListCountries button is shown
in Listing 9.10. Notice that in this example, I used For...Next loops to iterate through the current
level’s nodes, and I also used the NextNode method to retrieve the next node in the sequence.
Listing 9.10: Retrieving the Country Names
Private Sub bttnListCountries Click(...)
Handles bttnListCountries.Click
Dim Nd, CountryNode, ContinentNode As TreeNode
Dim continent, continents, country, countries As Integer
Nd = GlobeTree.Nodes.Item(0)
continents = Nd.Nodes.Count
ContinentNode = Nd.Nodes(0)
For continent = 1 To continents
countries = ContinentNode.Nodes.Count
CountryNode = ContinentNode.Nodes(0)
For country=1To countries
CountryNode = CountryNode.NextNode
ContinentNode = ContinentNode.NextNode
End SubPetroutsos c09.tex V2 - 01/28/2008 1:28pm Page 328
When the ContinentNode.Next method is called, it returns the next node in the Continents
level. Then the property ContinentNode.Nodes(0) returns the ?rst node in the Countries level.
As you can guess, the code of the ListCities button uses the same two nested lists as the previous
listing and an added inner loop, which scans the cities of each country.
The code behind these command buttons requires some knowledge of the information stored
in the tree. The code will work with trees that have two or three levels of nodes such as the Globe
tree, but what if the tree’s depth is allowed to grow to a dozen levels? A tree that represents the
structure of a folder on your hard disk, for example, might easily contain a dozen nested folders.
Obviously, to scan the nodes of this tree, you can’t put together unlimited nested loops. The next
section describes a technique for scanning any tree, regardless of how many levels it contains.
Finally, the application’s File menu contains commands for storing the nodes to a ?le and load-
ing the same nodes in a later session. These commands use serialization, a topic that’s discussed
in detail in Chapter 16, ‘‘XML and Object Serialization.’’ For now, you can use these commands to
persist the edited nodes to a disk ?le and read them back.
Scanning the TreeView Control
You have seen how to scan the entire tree of the TreeView control by using a For Each...Next
loop that iterates through the Nodes collection. This technique, however, requires that you know
the structure of the tree, and you must write as many nested loops as there are nested levels of
nodes. It works with simple trees, but it’s quite inef?cient when it comes to mapping a ?le system
to a TreeView control. The following section explains how to iterate through a TreeView control’s
node, regardless of the nesting depth.
VB 2008 at Work: The TreeViewScan Project
The TreeViewScan project, whose main form is shown in Figure 9.9, demonstrates the process of
scanning the nodes of a TreeView control. The form contains a TreeView control on the left, which
is populated with the same data as the Globe project, and a ListBox control on the right, in
which the tree’s nodes are listed. Child nodes in the ListBox control are indented according to the
level to which they belong.
Scanning the child nodes in a tree calls for a recursive procedure: a procedure that calls itself.
Think of a tree structure that contains all the ?les and folders on your C: drive. If this structure
contained no subfolders, you’d need to set up a loop to scan each folder, one after the other.
Because most folders contain subfolders, the process must be interrupted at each folder to scan the
subfolders of the current folder. The process of scanning a drive recursively is described in detail
in Chapter 15, ‘‘Accessing Folders and Files.’’
Recursive Scanning of the Nodes Collection
To scan the nodes of the TreeView1 control, start at the top node of the control by using the
following statement:
This is the code behind the Scan Tree button, and it doesn’t get any simpler. It calls the
ScanNode() subroutine to scan the child nodes of a speci?c node, which is passed to the sub-
routine as an argument. GlobeTree.Nodes(0) is the root node. By passing the root node to the
ScanNode() subroutine, we’re in effect asking it to scan the entire tree.Petroutsos c09.tex V2 - 01/28/2008 1:28pm Page 329
Figure 9.9
The TreeViewScan appli-
cation demonstrates
how to scan the nodes
of a TreeView control
This example assumes that the TreeView control contains a single root node and that all other
nodes are under the root node. If your control contains multiple root nodes, then you must set up
a small loop and call the ScanNode() subroutine once for each root node:
For Each node In GlobeTree.Nodes
Let’s look now at the ScanNode() subroutine shown in Listing 9.11.
Listing 9.11: Scanning a Tree Recursively
Sub ScanNode(ByVal node As TreeNode)
Dim thisNode As TreeNode
Static indentationLevel As Integer
ListBox1.Items.Add(Space(indentationLevel) & node.Text)
If node.Nodes.Count > 0 Then
indentationLevel += 5
For Each thisNode In node.Nodes
indentationLevel -= 5
End If
End SubPetroutsos c09.tex V2 - 01/28/2008 1:28pm Page 330
This subroutine is deceptively simple. First, it adds the caption of the current node to the
ListBox1 control. If this node (represented by the Node variable) contains child nodes, the code
must scan them all. The Node.Nodes.Count method returns the number of nodes under the cur-
rent node; if this value is positive, we must scan all the items of the Node.Nodes collection. To do
this, the ScanNode() subroutine must call itself, passing a different argument each time. If you’re
familiar with recursive procedures, you’ll ?nd the code quite simple. You may ?nd the notion of
a function calling itself a bit odd, but it’s no different from calling another function. The execution
of the function that makes the call is suspended until the called function returns.
You can use the ScanNode() subroutine as is to scan any TreeView control. All you need is a
reference to the root node (or the node you want to scan recursively), which you must pass to the
ScanNode() subroutine as an argument. The subroutine will scan the entire subtree and display its
nodes in a ListBox control. The nodes will be printed one after the other. To make the list easier to
read, the code indents the names of the nodes by an amount that’s proportional to the nesting level.
Nodes of the ?rst level aren’t indented at all. Nodes on the second level are indented by 5 spaces,
nodes on the third level are indented by 10 spaces, and so on. The variable indentationLevel
keeps track of the nesting level and is used to specify the indentation of the corresponding node.
It’s increased by 5 when we start scanning a new subordinate node and decreased by the same
amount when we return to the next level up. The indentationLevel variable is declared as Static
so that it maintains its value between calls.
Run the TreeViewScan project and expand all nodes. Then click the Scan Tree button to
populate the list on the right with the names of the continents/countries/cities. Obviously, the
ListBox control is not a substitute for the TreeView control. The data have no particular structure;
even when the names are indented, there are no tree lines connecting the nodes, and users can’t
expand and collapse the control’s contents.
The ListViewControl
The ListView control is similar to the ListBox control except that it can display its items in many
forms, along with any number of subitems for each item. To use the ListView control in your
project, place an instance of the control on a form and then set its basic properties, which are
described in the following list.
View and Arrange Two properties determine how the various items will be displayed on
the control: the View property, which determines the general appearance of the items, and the
Arrange property, which determines the alignment of the items on the control’s surface. The
View property can have one of the values shown in Table 9.1.
The Arrange property can have one of the settings shown in Table 9.2.
HeaderStyle This property determines the style of the headers in Details view. It has no
meaning when the View property is set to anything else, because only the Details view has
columns. The possible settings of the HeaderStyle property are shown in Table 9.3.
AllowColumnReorder This property is a True/False value that determines whether the user
can reorder the columns at runtime, and it’s meaningful only in Details view. If this property
is set to True, the user can move a column to a new location by dragging its header with the
mouse and dropping it in the place of another column.
Activation This property, which speci?es how items are activated with the mouse, can have
one of the values shown in Table 9.4.Petroutsos c09.tex V2 - 01/28/2008 1:28pm Page 331
Table 9.1: Settings of the View Property
Setting Description
LargeIcon (Default) Each item is represented by an icon and a caption below the icon.
SmallIcon Each item is represented by a small icon and a caption that appears to the right of the icon.
List Each item is represented by a caption.
Details Each item is displayed in a column with its subitems in adjacent columns.
Tile Each item is displayed with an icon and its subitems to the right of the icon. This view is
available only on Windows XP and Windows Server 2003.
Table 9.2: Settings of the Arrange Property
Setting Description
Default When an item is moved on the control, the item remains where it is dropped.
Left Items are aligned to the left side of the control.
SnapToGrid Items are aligned to an invisible grid on the control. When the user moves an item, the item
moves to the closest grid point on the control.
Top Items are aligned to the top of the control.
Table 9.3: Settings of the HeaderStyle Property
Setting Description
Clickable Visible column header that responds to clicking
Nonclickable (Default) Visible column header that does not respond to clicking
None No visible column header
Table 9.4: Settings of the Activation Property
Setting Description
OneClick Items are activated with a single click. When the cursor is over an item, it changes shape, and
the color of the item’s text changes.
Standard (Default) Items are activated with a double-click. No change in the selected item’s text color
takes place.
TwoClick Items are activated with a double-click, and their text changes color as well.Petroutsos c09.tex V2 - 01/28/2008 1:28pm Page 332
FullRowSelect This property is a True/False value, indicating whether the user can select an
entire row or just the item’s text, and it’s meaningful only in Details view. When this property
is False, only the ?rst item in the selected row is highlighted.
GridLines Another True/False property. If True, grid lines between items and subitems are
drawn. This property is meaningful only in Details view.
Group The items of the ListView control can be grouped into categories. To use this feature,
you must ?rst de?ne the groups by using the control’s Group property, which is a collection of
strings. You can add as many members to this collection as you want. After that, as you add
items to the ListView control, you can specify the group to which they belong. The control will
group the items of the same category together and display the group’s title above each group.
You can easily move items between groups at runtime by setting the corresponding item’s
Group property to the name of the desired group.
LabelEdit The LabelEdit property lets you specify whether the user will be allowed to edit
the text of the items. The default value of this property is False. Notice that the LabelEdit
property applies to the item’s Text property only; you can’t edit the subitems (unfortunately,
you can’t use the ListView control as an editable grid).
MultiSelect A True/False value, indicating whether the user can select multiple items from
the control. To select multiple items, click them with the mouse while holding down the Shift
or Ctrl key. If the control’s ShowCheckboxes property is set to True, users can select multiple
items by marking the check box in front of the corresponding item(s).
Scrollable A True/False value that determines whether the scroll bars are visible. Even if the
scroll bars are invisible, users can still bring any item into view. All they have to do is select an
item and then press the arrow keys as many times as needed to scroll the desired item
into view.
Sorting This property determines how the items will be sorted, and its setting can be None,
Ascending, or Descending. To sort the items of the control, call the Sort method, which sorts
the items according to their caption. It’s also possible to sort the items according to any of their
subitems, as explained in the section ‘‘Sorting the ListView Control’’ later in this chapter.
The Columns Collection
To display items in Details view, you must ?rst set up the appropriate columns. The ?rst column
corresponds to the item’s caption, and the following columns correspond to its subitems. If you
don’t set up at least one column, no items will be displayed in Details view. Conversely, the
Columns collection is meaningful only when the ListView control is used in Details view.
The items of the Columns collection are of the ColumnHeader type. The simplest way to set up
the appropriate columns is to do so at design time by using a visual tool. Locate and select the
Columns property in the Properties window, and click the ellipsis button next to the property. The
ColumnHeader Collection Editor dialog box will appear, as shown in Figure 9.10, in which you
can add and edit the appropriate columns.
Adding columns to a ListView control and setting their properties through the dialog box
shown in Figure 9.10 is quite simple. Don’t forget to size the columns according to the data you
anticipate storing in them and to set their headers.
It is also possible tomanipulate the Columns collection fromwithin your code as follows. Create
a ColumnHeader object for each column in your code, set its properties, and then add it to the
control’s Columns collection:Petroutsos c09.tex V2 - 01/28/2008 1:28pm Page 333
Dim ListViewCol As New ColumnHeader
ListViewCol.Text = ”New Column”
ListViewCol.TextAlign = HorizontalAlignment.Center
ListViewCol.Width = 125
Figure 9.10
The ColumnHeader
Collection Editor dialog
Adding and Removing Columns at Runtime
To add a new column to the control, use the Add method of the Columns collection. The syntax of
the Add method is as follows:
ListView1.Columns.Add(header, width, textAlign)
The header argument is the column’s header (the string that appears on top of the items). The
width argument is the column’s width in pixels, and the last argument determines how the text
will be aligned. The textAlign argument can be Center, Left,or Right.
The Add method returns a ColumnHeader object, which you can use later in your code to
manipulate the corresponding column. The ColumnHeader object exposes a Name property, which
can’t be set with the Add method:
Header1 = TreeView1.Add(
”Column 1”, 60, ColAlignment.Left)
Header1.Name = ”Column1”
After the execution of these statements, the ?rst column can be accessed not only by index, but
also by name.
To remove a column, call the Remove method:
ListView1.Columns(3).RemovePetroutsos c09.tex V2 - 01/28/2008 1:28pm Page 334
The indices of the following columns are automatically decreased by one. The Clear method
removes all columns from the Columns collection. Like all collections, the Columns collection
exposes the Count property, which returns the number of columns in the control.
ListView Items and Subitems
As with the TreeView control, the ListView control can be populated either at design time or at
runtime. To add items at design time, click the ellipsis button next to the ListItems property in
the Properties window. When the ListViewItem Collection Editor dialog box pops up, you can
enter the items, including their subitems, as shown in Figure 9.11.
Figure 9.11
The ListViewItem
Collection Editor dialog
Click the Add button to add a new item. Each item has subitems, which you can specify as
members of the SubItems collection. To add an item with three subitems, you must populate the
item’s SubItems collection with the appropriate elements. Click the ellipsis button next
to the SubItems property in the ListViewItem Collection Editor; the ListViewSubItem Collection
Editor will appear. This dialog box is similar to the ListViewItem Collection Editor dialog box,
and you can add each item’s subitems. Assuming that you have added the item called Item 1 in
the ListViewItem Collection Editor, you can add these subitems: Item 1-a, Item 1-b,and
Item 1-c. The ?rst subitem (the one with zero index) is actually the main item of the control.
Notice that you can set other properties such as the color and font for each item, the check
box in front of the item that indicates whether the item is selected, and the image of the item. Use
this window to experiment with the appearance of the control and the placement of the items,
especially in Details view because subitems are visible only in this view. Even then, you won’t see
anything unless you specify headers for the columns. Note that you can add more subitems than
there are columns in the control. Some of the subitems will remain invisible.
Unlike the TreeView control, the ListView control allows you to specify a different appearance
for each item and each subitem. To set the appearance of the items, use the Font, BackColor,and
ForeColor properties of the ListViewItem object.Petroutsos c09.tex V2 - 01/28/2008 1:28pm Page 335
Almost all ListView controls are populated at runtime. Not only that, but you should be able
to add and remove items during the course of the application. The items of the ListView control
are of the ListViewItem type, and they expose members that allow you to control the appearance
of the items on the control. These members are as follows:
BackColor/ForeColor properties These properties set or return the background/foreground
colors of the current item or subitem.
Checked property This property controls the status of an item. If it’s True, the item has been
selected. You can also select an item from within your code by setting its Checked property
to True. The check boxes in front of each item won’t be visible unless you set the control’s
ShowCheckBoxes property to True.
Font property This property sets the font of the current item. Subitems can be displayed in a
different font if you specify one by using the Font property of the corresponding subitem
(see the section titled ‘‘The SubItems Collection,’’ later in this chapter). By default, subitems
inherit the style of the basic item. To use a different style for the subitems, set the item’s
UseItemStyleForSubItems property to False.
Text property This property indicates the caption of the current item or subitem.
SubItems collection This property holds the subitems of a ListViewItem. To retrieve a
speci?c subitem, use a statement such as the following:
sitem = ListView1.Items(idx1).SubItems(idx2)
where idx1 is the index of the item, and idx2 is the index of the desired subitem.*
To add a new subitem to the SubItems collection, use the Add method, passing the text of the
subitem as an argument:
LItem.SubItems.Add(”subitem’s caption”)
The argument of the Add method can also be a ListViewItem object. Create a ListViewItem,
populate it, and then add it to the Items collection as shown here:
Dim LI As New ListViewItem
LI.Text = ”A New Item”
Li.SubItems.Add(”Its first subitem”)
Li.SubItems.Add(”Its second subitem”)
‘ statements to add more subitems
If you want to add a subitem at a speci?c location, use the Insert method. The Insert method
of the SubItems collection accepts two arguments: the index of the subitem before which the
new subitem will be inserted, and a string or ListViewItem to be inserted:
LItem.SubItems.Insert(idx, subitem)
Like the ListViewItem objects, each subitem can have its own font, which is set with the Font
property.Petroutsos c09.tex V2 - 01/28/2008 1:28pm Page 336
The items of the ListView control can be accessed through the Items property, which is a
collection. As such, it exposes the standard members of a collection, which are described in the
following section. Its item has a SubItems collection that contains all the subitems of the
corresponding item.
The Items Collection
All the items on the ListView control forma collection: the Items collection. This collection exposes
the typical members of a collection that let you manipulate the control’s items. These members are
discussed next.
Add method This method adds a new item to the Items collection. The syntax of the Add
method is as follows:
You can also specify the index of the image to be used, along with the item and a collection of
subitems to be appended to the new item, by using the following form of the Add method:
ListView1.Items.Add(caption, imageIndex)
where imageIndex is the index of the desired image on the associated ImageList control.
Finally, you can create a ListViewItem object in your code and then add it to the ListView
control by using the following form of the Add method:
The following statements create a new item, set its individual subitems, and then add the
newly created ListViewItem object to the control:
LItem.Text = ”new item”
LItem.SubItems.Add(”sub item 1a”)
LItem.SubItems.Add(”sub item 1b”)
LItem.SubItems.Add(”sub item 1c”)
Count property Returns the number of items in the collection.
Item property Retrieves an item speci?ed by an index value.
Clear method Removes all the items from the collection.
Remove method Removes an item from the collection.
The SubItems Collection
Each item in the ListView controlmay have one or more subitems. You can think of the item as the
key of a record, and the subitems as the other ?elds of the record. The subitems are displayed only
in Details mode, but they are available to your code in any view. For example, you can display all
items as icons, and when the user clicks an icon, show the values of the selected item’s subitems
on other controls.Petroutsos c09.tex V2 - 01/28/2008 1:28pm Page 337
To access the subitems of a given item, use its SubItems collection. The following statements
add an item and three subitems to the ListView1 control:
Dim LItem As ListViewItem
LItem = ListView1.Items.Add(”Alfred’s Futterkiste”)
LItem.SubItems.Add(”Maria Anders”)
To access the SubItems collection, you need a reference to the item to which the subitems
belong. The Add method returns a reference to the newly added item, the LItem variable, which is
then used to access the item’s subitems, as shown in the preceding code segment.
Displaying the subitems on the control requires some overhead. Subitems are displayed only
in Details view mode. However, setting the View property to Details is not enough. You must ?rst
create the columns of the Details view, as explained earlier. The ListView control displays only as
many subitems as there are columns in the control. The ?rst column, with the header Company,
displays the items of the list. The following columns display the subitems. Moreover, you can’t
specify which subitemwill be displayed under each header. The ?rst subitem (Maria Anders in the
preceding example) will be displayed under the second header, the second subitem (030-0074321
in the same example) will be displayed under the third header, and so on. At runtime, the user
can rearrange the columns by dragging them with the mouse. To disable the rearrangement of the
columns at runtime, set the control’s AllowColumnReorder property to False (its default value
is True).
Unless you set up each column’s width, they will all have the same width. The width of
individual columns is speci?ed in pixels, and you can set it to a percentage of the total width of
the control, especially if the control is docked to the form. The following code sets up a ListView
control with four headers, all having the same width:
Dim LWidth As Integer
LWidth = ListView1.Width - 5
ListView1.ColumnHeaders.Add(”Company”, LWidth / 4)
ListView1.ColumnHeaders.Add(”Contact”, LWidth / 4)
ListView1.ColumnHeaders.Add(”Phone”, LWidth / 4)
ListView1.ColumnHeaders.Add(”FAX”, LWidth / 4)
ListView1.View = DetailsView
This subroutine sets up four headers of equal width. The ?rst header corresponds to the item
(not a subitem). The number of headers you set up must be equal to the number of subitems you
want to display on the control, plus one. The constant 5 is subtracted to compensate for the width
of the column separators. If the control is anchored to the vertical edges of the form, you must
execute these statements from within the form’s Resize event handler, so that the columns are
resized automatically as the control is resized.
VB 2008 at Work: The ListViewDemo Project
Let’s put together the members of the ListView control to create a sample application that
populates the control and enumerates its items. The sample application of this section is the
ListViewDemo project. The application’s form, shown in Figure 9.12, contains a ListView control
whose items can be displayed in all possible views, depending on the status of the RadioButton
controls in the List Style section on the right side of the form.Petroutsos c09.tex V2 - 01/28/2008 1:28pm Page 338
Figure 9.12
The ListViewDemo
project demonstrates
the basic members
of the ListView control.
The control’s headers and their widths were set at design time through the ColumnHeader
Collection Editor, as explained earlier. To populate the ListView control, click the Populate List
button, whose code is shown next. The code creates a new ListViewItem object for each item to be
added. Then it calls the Add method of the SubItems collection to add the item’s subitems (contact,
phone, and fax numbers). After the ListViewItem has been set up, it’s added to the control via the
Add method of its Items collection.
Listing 9.12 shows the statements that insert the ?rst two items in the list. The remaining items
are added by using similar statements, which need not be repeated here. The sample data I used
in the ListViewDemo application came from the Northwind sample database.
Listing 9.12: Populating a ListView Control
Dim LItem As New ListViewItem()
LItem.Text = ”Alfred’s Futterkiste”
LItem.SubItems.Add(”Anders Maria”)
LItem.ImageIndex = 0
LItem = New ListViewItem()
LItem.Text = ”Around the Horn”
LItem.SubItems.Add(”Hardy Thomas”)
LItem.SubItems.Add(”(171) 555-7788”)
LItem.SubItems.Add(”(171) 555-6750”)
LItem.ImageIndex = 0
ListView1.Items.Add(LItem)Petroutsos c09.tex V2 - 01/28/2008 1:28pm Page 339
Enumerating the List
The Enumerate List button scans all the items in the list and displays them along with their
subitems in the Immediate window. To scan the list, you must set up a loop that enumerates
all the items in the Items collection. For each item in the list, set up a nested loop that scans all the
subitems of the current item. The complete code for the Enumerate List button is shown in
Listing 9.13.
Listing 9.13: Enumerating Items and SubItems
Private Sub bttnEnumerate Click(...)
Handles bttnEnumerate.Click
Dim i, j As Integer
Dim LItem As ListViewItem
For i = 0 To ListView1.Items.Count - 1
LItem = ListView1.Items(i)
Forj=0To LItem.SubItems.Count - 1
Debug.WriteLine(” ” & ListView1.Columns(j).Text &
” ” & Litem.SubItems(j).Text)
End Sub
Notice that each item may have a different number of subitems. The output of this code in the
Immediate window is shown next. The subitems appear under the corresponding item, and they
are indented by three spaces:
Alfred’s Futterkiste
Company Alfred’s Futterkiste
Contact Anders Maria
Telephone 030-0074321
FAX 030-0076545
Around the Horn
Company Around the Horn
Contact Hardy Thomas
Telephone (171) 555-7788
FAX (171) 555-6750
The code in Listing 9.13 uses a For...Next loop to iterate through the items of the control. You
can also set up a For Each...Next loop, as shown here:
Dim LI As ListViewItem
For Each LI In ListView1.Items
{ access the current item through the LI variable}
NextPetroutsos c09.tex V2 - 01/28/2008 1:28pm Page 340
Sorting the ListView Control
The ListView control provides the Sort method, which sorts the list’s items, and the Sorting
property, which determines how the items will be sorted. The Sort methodsortstheitemsin
the ?rst column alphabetically. Each item may contain any number of subitems, and you should
be able to sort the list according to any column. The values stored in the subitems can represent
different data types (numeric values, strings, dates, and so on), but the control doesn’t provide
a default sorting mechanism for all data types. Instead, it uses a custom comparer object, which
you supply, to sort the items. (The topic of building custom comparers is discussed in detail in
Chapter 14, ‘‘Storing Data in Collections.’’) A custom comparer is a function that compares two
items and returns an integer value (–1, 0, or 1) that indicates the order of the two items. After this
function is in place, the control uses it to sort its items.
The ListView control’s ListViewItemSorter property accepts the name of a custom comparer,
and the items on the control are sorted according to the custom comparer as soon as you call the
Sort method. You can provide several custom comparers and sort the items in many different
ways. If you plan to display subitems along with your items in Details view, you should make the
list sortable by any column. It’s customary for a ListView control to sort its items according to
the values in a speci?c column each time the header of this column is clicked. And this is
exactly the type of functionality you’ll add to the ListViewDemo project in this section.
The ListViewDemo control displays contact information. The items are company names, and
the ?rst subitem under each item is the name of a contact. We’ll create two custom comparers to
sort the list according to either company name or contact. The two methods are identical because
they compare strings, but it’s not any more complicated to compare dates, distances, and so on.
Let’s start with the two custom comparers. Each comparer must be implemented in its own
class, and you assign the name of the custom comparer to the ListViewItem property of the
control. Listing 9.14 shows the ListCompanyComparer and ListContactComparer classes.
Listing 9.14: The Two CustomComparers for the ListViewDemo Project
Class ListCompanySorter
Implements IComparer
Public Function CompareTo(ByVal o1 As Object,
ByVal o2 As Object) As Integer
Implements System.Collections.IComparer.Compare
Dim item1, item2 As ListViewItem
item1 = CType(o1, ListViewItem)
item2 = CType(o2, ListViewItem)
If item1.ToString.ToUpper > item2.ToString.ToUpper Then
Return 1
If item1.ToString.ToUpper < item2.ToString.ToUpper Then
Return -1
Return 0
End If
End If
End Function
End ClassPetroutsos c09.tex V2 - 01/28/2008 1:28pm Page 341
Class ListContactSorter
Implements IComparer
Public Function CompareTo(ByVal o1 As Object,
ByVal o2 As Object) As Integer
Implements System.collections.IComparer.Compare
Dim item1, item2 As ListVewItem
item1 = CType(o1, ListViewItem)
item2 = CType(o2, ListViewItem)
If item1.SubItems(1).ToString.ToUpper >
item2.SubItems(1).ToString.ToUpper Then
Return 1
If item1.SubItems(1).ToString.ToUpper <
item2.SubItems(1).ToString.ToUpper Then
Return -1
Return 0
End If
End If
End Function
End Class
The code is straightforward. If you need additional information, see the discussion of the
IComparer interface in Chapter 14. The two functions are identical, except that the ?rst one sorts
according to the item, and the second one sorts according to the ?rst subitem.
To test the custom comparers, you simply assign their names to the ListViewItemSorter
property of the ListView control. To take advantage of our custom comparers, we must write
some code that intercepts the clicks on the control’s headers and calls the appropriate comparer.
The ListView control ?res the ColumnClick event each time a column header is clicked. This event
handler reports the index of the column that was clicked through the e.Column property, and
we can use this argument in our code to sort the items accordingly. Listing 9.15 shows the event
handler for the ColumnClick event.
Listing 9.15: The ListView Control’s ColumnClick Event Handler
Public Sub ListView1 ColumnClick(...)
Handles ListView1.ColumnClick
Select Case e.column
Case 0
ListView1.ListViewItemSorter = New ListCompanySorter()
ListView1.Sorting = SortOrder.Ascending
Case 1
ListView1.LisViewtItemSorter = New ListContactSorter()
ListView1.Sorting = SortOrder.Ascending
End Select
End SubPetroutsos c09.tex V2 - 01/28/2008 1:28pm Page 342
Processing Selected Items
The user can selectmultiple items froma ListView control by default. Even though you can display
a check mark in front of each item, it’s not customary. Multiple items in a ListView control are
selected with the mouse while holding down the Ctrl or Shift key.
The selected items form the SelectedListItemCollection, which is a property of the control.
You can iterate through this collection with a For...Next loop or through the enumerator object
exposed by the collection. In the following example, I use a For Each...Next loop. Listing 9.16
is the code behind the Selected Items button of the ListViewDemo project. It goes through the
selected items and displays each one of them, along with its subitems, in the Output window.
Notice that you can select multiple items in any view, even when the subitems are not visible.
They’re still there, however, and they can be retrieved through the SubItems collection.
Listing 9.16: Iterating the Selected Items on a ListView Control
Private Sub bttnIterate Click(...)
Handles bttnIterate.Click
Dim LItem As ListViewItem
Dim LItems As ListView.SelectedListViewItemCollection
LItems = ListView1.SelectedItems
For Each LItem In LItems
Debug.Write(LItem.Text & vbTab)
Debug.Write(LItem.SubItems(0).ToString & vbTab)
Debug.Write(LItem.SubItems(1).ToString & vbTab)
Debug.WriteLine(LItem.SubItems(2).ToString & vbTab)
End Sub
Fitting More Data into a ListView Control
A fairly common problem in designing practical user interfaces with the ListView control is how to
display more columns than can be viewed in a reasonably sized window. This is especially true for
accounting applications, whichmay have several debit/credit/balance columns. It’s typical to display
these values for the previous period, the current period, and then the totals, or to display the period
values along with the corresponding values of the previous year, year-to-date values, and so on.
The ?rst approach is to use a smaller font, but this won’t take you far. A more-practical approach is to
use two (or even more) rows on the control for displaying a single row of data. For example, you can
display credit and debit data in two rows, as shown in the following ?gure. This arrangement saves
you the space of one column on the screen. You could even display the balance on a third row and use
different colors. The auxiliary rows, which are introduced to accommodate more data on the control,
could have a different background color too.
Adding auxiliary columns is straightforward; just add an empty string for the cells that don’t change
values, because all rows must have the same structure. The ?rst two rows of the ListView control in
the preceding screen capture were added by using the following statements:Petroutsos c09.tex V2 - 01/28/2008 1:28pm Page 343
Dim LI As ListViewItem
LI = ListView1.Items.Add(”Customer 1”)
LI.SubItems.Add(”Paul Levin”)
LI = ListView1.Items.Add(””)
LI.SubItems.Add((12100.9 - 7489.3).ToString(”#,###.00”))
If your code reacts to the selection of an item with the mouse, or the double-click event, you must
take into consideration that users may click an auxiliary row. The following If structure in the con-
trol’s SelectedIndexChanged event handler prints the item’s text, no matter which of the two rows
of an item are selected on the control:
If ListView1.SelectedItems.Count = 0 Then Exit Sub
Dim idx As Integer
If ListView1.SelectedItems(0).Index Mod 2 <> 0 Then
idx = ListView1.SelectedItems(0).Index - 1
idx = ListView1.SelectedItems(0).Index
End If
VB 2008 at Work: The CustomExplorer Project
The last example in this chapter combines the TreeView and ListView controls. It’s a fairly
advanced example, but I included it here for the most ambitious readers. It can also be used as
the starting point for many custom applications, so give it a try. You can always come back to this
project after you’ve mastered other aspects of the Framework, such as the FileIO namespace.
The CustomExplorer project, shown in Figure 9.13, displays a structured list of folders in the left
pane, and a list of ?les in the selected folder in the right pane. The left pane is populated when the
application starts, and it might take a while. On my Pentium system, it takes nearly 30 seconds to
populate the TreeView control with the structure of the Windows folder (which includes FallBack
folders and three versions of the Framework;more than 50,000 ?les in 1,700 folders in all). You can
expand any folder in this pane and view its subfolders. To view the ?les in a folder, click the folder
name, and the right pane will be populated with the names of the selected folder’s ?les, along withPetroutsos c09.tex V2 - 01/28/2008 1:28pm Page 344
other data, such as the ?le size, date of creation, and date of last modi?cation. You haven’t seen the
classes for accessing folders and ?les yet, but you shouldn’t have a problem following the code. If
you have to, you can review the members of the IO namespace in Chapter 15, in which I discuss
in detail the same project’s code.
Figure 9.13
The CustomExplorer
project demonstrates
how to combine a
TreeView and a ListView
control on the same
This section’s project is not limited to displaying folders and ?les; you can populate the two
controls with data from several sources. For example, you can display customers in the left pane
(and organize them by city or state) and display their related data, such as invoices and payments,
in the right pane. Or you can populate the left pane with product names, and the right pane
with the respective sales. In general, you can use the project as an interface for many types of
applications. You can even use it as a custom Explorer to add features that are speci?c to
your applications.
The TreeView control on the left pane is populated from within the Form’s Load event handler
subroutine with the subfolders of the C:\Program Files folder:
Dim Nd As New TreeNode()
Nd = TreeView1.Nodes.Add(”C:\Program Files”)
ScanFolder(”c:\Program Files”, ND)
The ?rst argument is the name of the folder to be scanned, and the second argument is the root
node, under which the entire tree of the speci?ed folder will appear. To populate the control with
the ?les of another folder or drive, change the name of the path accordingly. The code is short, and
all the work is done by the ScanFolder() subroutine. The ScanFolder() subroutine, which
is a short recursive procedure that scans all the folders under a speci?c folder, is shown in
Listing 9.17.
Listing 9.17: The ScanFolder() Subroutine
Sub ScanFolder(ByVal folderSpec As String,
ByRef currentNode As TreeNode)Petroutsos c09.tex V2 - 01/28/2008 1:28pm Page 345
Dim thisFolder As FileIO.Folder
Dim allFolders As FileIO.FolderCollection
allFolders = My.Computer.FileSystem.
For Each thisFolder In allFolders
Dim Nd As TreeNode
Nd = New TreeNode(thisFolder.FolderName)
folderSpec = thisFolder.FolderPath
ScanFolder(folderSpec, Nd)
Me.Text = ”Scanning ” & folderSpec
End Sub
The variable FolderSpec represents the current folder (the one passed to the ScanFolder()
subroutine as an argument). The code creates the allFolders collection, which contains all the
subfolders of the current folder. Then it scans every folder in this collection and adds its name to
the TreeView control. After adding a folder’s name to the TreeView control, the procedure must
scan the subfolders of the current folder. It does so by calling itself and passing another folder’s
name as an argument.
Notice that the ScanFolder()subroutine doesn’t simply scan a folder. It also adds a node to the
TreeView control for each new folder it runs into. That’s why it accepts two arguments: the name
of the current folder and the node that represents this folder on the control. All folders are placed
under their parent folder, and the structure of the tree represents the structure of your hard disk
(or the section of the hard disk you’re mapping on the TreeView control). All this is done with a
small recursive subroutine: the ScanFolder() subroutine.
Viewing a Folder’s Files
To view the ?les of a folder, click the folder’s name in the TreeView control. As explained earlier,
the action of the selection of a new node is detected with the AfterSelect event. The code in this
event handler, shown in Listing 9.18, displays the selected folder’s ?les on the ListView control.
Listing 9.18: Displaying a Folder’s Files
Private Sub TreeView1 AfterSelect(...)
Handles TreeView1.AfterSelect
Dim Nd As TreeNode
Dim pathName As String
Nd = TreeView1.SelectedNode
pathName = Nd.FullPath
End Sub
The ShowFiles() subroutine actually displays the ?lenames, and some of their properties, in
the speci?ed folder on the ListView control. Its code is shown in Listing 9.19.Petroutsos c09.tex V2 - 01/28/2008 1:28pm Page 346
Listing 9.19: The ShowFiles() Subroutine
Sub ShowFiles(ByVal selFolder As String)
Dim files As FileIO.FileCollection
Dim file As FileIO.File
files = My.Computer.FileSystem.GetFolder(selFolder).FindFiles(”*.*”)
Dim TotalSize As Long
For Each file In files
Dim LItem As New ListViewItem
LItem.Text = file.FileName
TotalSize += file.Size
Me.Text = Me.Text & ” [” & TotalSize.ToString(”#,###”) & ” bytes]”
End Sub
The ShowFiles()subroutine creates a ListItem for each ?le. The item’s caption is the ?le’s
name, the ?rst subitem is the ?le’s length, and the other two subitems are the ?le’s creation and
last access times. You can add more subitems, if needed, in your application. The ListView control
in this example uses the Details view to display the items. As mentioned earlier, the ListView
control will not display any items unless you specify the proper columns through the Columns
collection. The columns, along with their widths and captions, were set at design time through the
ColumnHeader Collection Editor.
Additional Topics
The discussion of the CustomExplorer sample project concludes the presentation of the TreeView
and ListView controls. However, there are a few more interesting topics you might like to read
about, which weren’t included in this chapter. Like all Windows controls, the ListView control
doesn’t provide a Print method, which I think is essential for any application that displays data
on this control. In Chapter 20, ‘‘Printing with Visual Basic 2008,’’ you will ?nd the code for print-
ing the items of the ListView control. The printout we’ll generate will have columns, just like
the control, but it will display long cells (items or subitems with long captions) in multiple text
lines. Finally, in Chapter 16 you’ll learn how to save the nodes of a TreeView control to a disk ?le
between sessions by using a technique known as serialization. In that chapter, you’ll ?nd the code
behind the Load Nodes and Save Nodes buttons of the Globe project and a thorough explanation
of their function.Petroutsos c09.tex V2 - 01/28/2008 1:28pm Page 347
The BottomLine
Create and present hierarchical lists by using the TreeView control. The TreeView control
is used to display a list of hierarchically structured items. Each item in the TreeView con-
trol is represented by a TreeNode object. To access the nodes of the TreeView control, use
the TreeView.Nodes collection. The nodes under a speci?c node (in other words, the child
nodes) form another collection of Node objects, which you can access by using the expression
TreeView.Nodes(i).Nodes. The basic property of the Node object is the Text property, which
stores the node’s caption. The Node object exposes properties for manipulating its appearance
(its foreground/background color, its font, and so on).
Master It How will you set up a TreeView control with a book’s contents at design time?
Create and present lists of structured items by using the ListView control. The ListView
control stores a collection of ListViewItem objects, the Items collection, and can display them
inseveralmodes,asspeci?edbythe View property. Each ListViewItem object has a Text prop-
erty and the SubItems collection. The subitems are not visible at runtime unless you set the
control’s View property to Details and set up the control’s Columns collection. There must be a
column for each subitem you want to display on the control.
Master It How will you set up a ListView control with three columns to display names,
emails, and phone numbers at design time?
Master It How would you populate the same control with the same data at runtime?Petroutsos c09.tex V2 - 01/28/2008 1:28pm Page 348Petroutsos c10.tex V3 - 01/28/2008 1:32pm Page 349
Chapter 10
Building Custom Classes
Classes are practically synonymous with objects and they’re at the very heart of programming
with Visual Basic. The controls you use to build the visible interface of your application are objects,
and the process of designing forms consists of setting the properties of these objects, mostly with
point-and-click operations. The Framework itself is an enormous compendium of classes, and you
can import any of them into your applications and use them as if their members were part of the
language. You simply declare a variable of the speci?c class type, initialize it, and then use it in
your code.
You have already worked with ListViewItem objects; they’re the items that make up the
contents of a ListView control. You declare an object of this type, then set its properties, and
?nally add it to the control’s Items collection:
Dim LI As New ListViewItem
LI.Text = ”Item 1”
LI.Font = New Font(”Verdana”, 12, FontStyle.Regular)
LI is an object of the ListViewItem type. The New keyword creates a new instance of the
ListViewItem class; in other words, a new object. The following two statements set the basic prop-
erties of the LI variable. The Font property is also an object: it’s an instance of the Font class. You
can also add a few subitems to the LI variable and set their properties. When you’re ?nished,
you can add the LI variable to the ListView control:
Controls are also objects; they differ from other classes in that controls provide a visual inter-
face, whereas variables don’t. However, you manipulate all objects by setting their properties and
calling their methods.
In this chapter, you’ll learn how to do the following:
? Build your own classes
? Use custom classes in your projects
? Customize the usual operators for your classes
Classes andObjects
When you create a variable of any type, you’re creating an instance of a class. The variable lets you
access the functionality of the class through its properties and methods. Even the base data types
are implemented as classes (the System.Integer class, System.Double, and so on). An integer value,Petroutsos c10.tex V3 - 01/28/2008 1:32pm Page 350
such as 3, is an instance of the System.Integer class, and you can call the properties and methods
of this class by using its instance. Expressions such as CDec(3).MinValue and #1/1/2000#.Today
are odd but valid. The ?rst expression returns the minimum value you can represent with the
Decimal data type, whereas the second expression returns the current date. The DataTime data
type exposes the Today property, which returns the current date. The expression #1/1/2000# is a
value of the DataTime type, so you can ?nd out the current date by calling its Today property. If
you enter either one of the preceding expressions in your code, you’ll get a warning, but they will
be executed.
Classes are used routinely in developing applications, and you should get into the habit of
creating and using custom classes, even with simple projects. In team development, classes are
a necessity, because they allow developers to share their work easily. If you’re working in a
corporate environment, in which different programmers code different parts of an application,
you can’t afford to repeat work that someone else has already done. You should be able to get
their code and use it in your application as is. That’s easier said than done, because you can guess
what will happen as soon as a small group of programmers start sharing code — they’ll end up
with dozens of different versions for each function, and every time a developer upgrades a func-
tion, he or she will most likely break the applications that were working with the old version. Or
each time they revise a function, they must update all the projects by using the old version of the
function and test them. It just doesn’t work.
The major driving force behind object-oriented programming (OOP) is code reuse. Classes allow
you to write code that can be reused in multiple projects. You already know that
classes don’t expose their source code. In other words, you can use a class without having access
to its code, and therefore you can’t affect any other projects that use the class. You also know
that classes implement complicated operations and make these operations available to program-
mers through properties and methods. The Array class exposes a Sort method, which sorts its
elements. This is not a simple operation, but fortunately you don’t have to know anything about
sorting. Someone else has done it for you and made this functionality available to your applica-
tions. This is called encapsulation. Some functionality has been built into the class (or encapsulated
into the class), and you can access it from within your applications by using a simple method call.
The Framework is made up of thousands of classes, which allow you to access all the func-
tionality of the operating system. You don’t have to see the code, and you don’t have to know
anything about sorting to sort your arrays, just as you don’t need to know anything about encryp-
tion to encrypt a string by using the System.Security.Cryptography class. In effect, you’re reusing
code that Microsoft has already written. It is also possible to extend these classes by adding
custom members, and even override existing members. When you extend a class, you create a
new class based on an existing one. Projects using the original class will keep seeing the original
class, and they will work ?ne. New projects that see the derived class will also work.
What Is a Class?
A class is a program that doesn’t run on its own; it’s a collection of methods that must be used by
another application.We exploit the functionality of the class by creating a variable of the same type
as the class and then call the class’s properties and methods through this variable. The methods
and properties of the class, as well as its events, constitute the class’s interface. It’s not a visible
interface, like the ones you’ve learned to design so far, because the class doesn’t interact directly
with the user. To interact with the class, the application uses the class’s interface, just as users will
be interacting with your application through its visual interface.Petroutsos c10.tex V3 - 01/28/2008 1:32pm Page 351
You have already learned how to use classes. Now is the time to understand what goes on
behind the scenes when you interact with a class and its members. Behind every object, there’s a
class. When you declare an array, you’re invoking the System.Array class, which contains all the
code for manipulating arrays. Even when you declare a simple integer variable, you’re invoking a
class: the System.Integer class. This class contains the code that implements the various properties
(such as MinValue and MaxValue) and methods (such as ToString) of the Integer data type. The
?rst time you use an object in your code, you’re instantiating the class that implements this object.
The class’s code is loaded into memory, initializes its variables, and is ready to execute. The image
of the class in memory is said to be an instance of the class, and this is an object.
Classes versus Objects
Two of the most misused terms in OOP are object and class, and most people use them interchange-
ably. You should think of the class as the factory that produces objects. There’s only one System.Array
class, but you can declare any number of arrays in your code. Every array is an instance of the
System.Array class. All arrays in an application are implemented by the same code, but they store
different data. Each instance of a class is nothing more than a set of variables: the same code acts
on different sets of variables; each set of variables is a separate instance of the class.
Consider three TextBox controls on the same form. They are all instances of the System.Windows.
Forms.TextBox class, but changing any property of a TextBox control doesn’t affect the other two
controls. Classes are the blueprints on which objects are based. We use the same blueprint to build
multiple buildings with the same structural characteristics, but different properties (wall colors, doors,
and so on).
Objects are similar toWindows controls, except that they don’t have a visible interface. Controls
are instantiated when you place them on a form; classes are instantiated when you use a variable
of the same type — not when you declare the variable by using the Dim statement. To use a control,
you must make it part of the project by adding its icon to the Toolbox, if it’s not already there. To
use a class in your code, you must import the ?le that implements the class. (This is a Dynamic
Link Library, or DLL, ?le.) To manipulate a control from within your code, you call its properties
and methods. You do the same with classes. Finally, you program the various events raised by
the controls to interact with the users of your applications. Most classes don’t expose any events
because the user can’t interact with them, but some classes do raise events, which you can program
just as you program the events ofWindows controls.
Classes Combine Code with Data
Another way to view classes is to understand how they combine code and data. This simple
idea is the very essence of object-oriented programming. Data is data, and traditional procedural
languages allow you to manipulate data in any way. Meaningful data, however, is processed in
speci?c ways.
Let’s consider accounting data. You can add or subtract amounts to an account, sum similar
accounts (such as training and travel expenses), calculate taxes on certain account amounts, and
the like. Other types of processing may not be valid for this type of data. We never multiply the
amounts of two different accounts or calculate logarithms of account balances. These types of
processing are quite meaningful with different data, but not with accounting data.Petroutsos c10.tex V3 - 01/28/2008 1:32pm Page 352
Because the data itself determines to a large extent the type of processing that will take place
on the data, why not ‘‘package’’ the data along with the code for processing? Instead of simply
creating structures for storing our data, we also write the code to process them. The data and the
code are implemented in a single unit, a class, and the result is an object. After the class has been
built, we no longer write code for processing the data; we simply create objects of this type and
call their methods. To transfer an amount from one account to another, we call a method that
knows how to transfer the amount, and it also makes sure that the amount isn’t subtracted from
one account unless it has been added to the other account (and vice versa).
To better understand how classes combine code with data, let’s take a close look at a class
we’re all too familiar with, the Array class. The role of the array is to store sets of data. In addition
to holding data, the Array class also knows how to process data: how to retrieve an element,
how to extract a segment of the array, and even how to sort its elements. All these operations
require a substantial amount of code. The mechanics of storing data in the array and the code that
implements the properties and the methods of the array are hidden from you, the developer. You
can instruct the array to perform certain tasks by using simple statements.When you call the Sort
method, you’re telling the array to execute some code that will sort its elements. As a developer,
you don’t know how the data are stored in the array, or how the Sort method works. Classes
abstract many operations by hiding the implementation details, and developers can manipulate
arrays by calling methods. An instance of the Array class not only holds the elements thatmake up
an array, but also exposes the most common operation one would perform on arrays as methods.
Summing the logarithms of the elements of a numeric array is a specialized operation, and you
have to provide the code to implement it on your own. If you type System.Array., you will see a
list of all operations you can perform on an array.
In the following sections, you’ll learn how data and code coexist in a class, and how you can
manipulate the data through the properties and methods exposed by the class. In Chapter 3,
‘‘Programming Fundamentals,’’ you learned how to create Structures to store data. Classes are
similar to Structures, in that they represent custom data structures. In this chapter, we’ll take the
idea of de?ning custom data structures one step further, by adding properties and methods for
manipulating the custom data, something you can’t do with structures. Let’s start by building a
custom class and then using it in our code.
Building theMinimal Class
Our ?rst example is the Minimal class; we’ll start with the minimum functionality class and keep
adding features to it. The name of the class can be anything — just make sure that it’s suggestive
of the class’s functionality.
A class might reside in the same ?le as a form, but it’s customary to implement custom classes
in a separate module, a Class module. You can also create a Class project, which contains one or
more classes. However, a class doesn’t run on its own, and you can’t test it without a form. You
can create a Windows application, add the class to it, and then test it by adding the appropriate
code to the form. After debugging the class, you can remove the test form and reuse the class with
any other project. Because the class is pretty useless outside the context of aWindows application,
in this chapter I use Windows applications and add a Class module in the same solution.
Start a new Windows project and name it SimpleClass (or open the sample project by that
name). Then create a new class by adding a Class item to your project. Right-click the project’s
name in the Solution Explorer window and choose Add  Class from the context menu. In the
dialog box that pops up, select the Class icon and enter a name for the class. Set the class’s name
to Minimal, as shown in Figure 10.1.Petroutsos c10.tex V3 - 01/28/2008 1:32pm Page 353
Figure 10.1
Adding a Class item to a
The code that implements the class resides in the Minimal.vb ?le, and we’ll use the existing
form to test our class. After you have tested and ?nalized the class’s code, you no longer need the
form and you can remove it from the project.
When you open the class by double-clicking its icon in the Project Explorer window, you will
see the following lines in the code window:
Public Class Minimal
End Class
If you’d rather create a class in the same ?le as the application’s form, enter the Class keyword
followed by the name of the class, after the existing End Class of the form’s code window. The
editor will insert the matching End Class for you. Insert a class’s de?nition in the form’s code
window if the class is speci?c to this form only and no other part of the application will use it. At
this point, you already have a class, even if it doesn’t do anything.
Switch back to the Form Designer, add a button to the test form, and insert the following code
in its Click event handler:
Dim obj1 As Minimal
Press Enter and type the name of the variable, obj1, followed by a period, on the following
line. You will see a list of the methods your class exposes already:
If you don’t see all of these members, switch to the All Members tab of the IntelliSense drop-
down box.
These methods are provided by the Common Language Runtime (CLR), and you don’t have
to implement them on your own (although you will probably have to provide a new, nongeneric
implementation for some of them). They don’t expose any real functionality; they simply re?ectPetroutsos c10.tex V3 - 01/28/2008 1:32pm Page 354
the way VB handles all classes. To see the kind of functionality that these methods expose,
enter the following lines in the Button’s Click event handler and then run the application:
Dim obj1 As New Minimal
Dim obj2 As New Minimal
Debug.WriteLine(Minimal.ReferenceEquals(obj1, obj2))
The following lines will be printed in the Output window:
The name of the object is the same as its type, which is all the information about your new class
that’s available to the CLR. Shortly you’ll see how you can implement your own ToString method
and return a more-meaningful string. The hash value of the obj1 variable is an integer value that
uniquely identi?es the object variable in the context of the current application (it happens to be
18796293, but it is of no consequence).
The next line tells you that two variables of the same type are not equal. But why aren’t they
equal? We haven’t differentiated them at all, yet they’re different because they point to two dif-
ferent objects, and the compiler doesn’t know how to compare them. All it can do is ?gure out
whether the variables point to the same object. To understand how objects are compared, add the
following statement after the line that declares obj2:
obj2 = obj1
If you run the application again, the last two statements will print True in the Output window.
The Equals method compares the two objects and returns a True/False value. Because we haven’t
told it how to compare two instances of the class yet, it compares their references just like the
ReferenceEquals method. The ReferenceEquals method checks for reference equality; that is, it
returns True if both variables point to the same object (the same instance of the class). If you change
a property of the obj1 variable, the changes will affect obj2 as well, because both variables point
to the same object.We can’t modify the object because it doesn’t expose any members that we can
set to differentiate it from another object of the same type. We’ll get to that shortly.
Most classes expose a custom Equals method, which knows how to compare two objects of the
same type (two objects based on the same class). The custom Equals method usually compares
the properties of the two instances of the class and returns True if a set of basic properties (or all
of them) are the same. You’ll learn how to customize the default members of any class later in
this chapter.
Notice the name of the class: SimpleClass.Minimal. Within the current project, you can access
it as Minimal. Other projects can either import the Minimal class and access it asMinimal, or spec-
ify the complete name of the class, which is the name of the project it belongs to followed by the
class name.Petroutsos c10.tex V3 - 01/28/2008 1:32pm Page 355
Adding Code to the Minimal Class
Let’s add some functionality to our bare-bones class. We’ll begin by adding two trivial properties
and two methods to perform simple operations. The two properties are called strProperty (a
string) and dblProperty (a double). To expose these two members as properties, you can simply
declare them as Public variables. This isn’t the best method of implementing properties, but it
really doesn’t take more than declaring something as Public to make it available to code outside
the class. The following statement exposes the two properties of the class:
Public strProperty As String, dblProperty As Double
The two methods we’ll implement in our sample class are the ReverseString and Negate
Number methods. The ?rst method reverses the order of the characters in strProperty and
returns the new string. The NegateNumber method returns the negative of dblProperty.They’re
two simple methods that don’t accept any arguments; they simply operate on the values of the
properties. Methods are exposed as Public procedures (functions or subroutines), just as proper-
ties are exposed as Public variables. Enter the function declarations of Listing 10.1 between the
Class Minimal and End Class statements in the class’s code window. (I’m showing the entire
listing of the class here.)
Listing 10.1: Adding a FewMembers to theMinimal Class
Public Class Minimal
Public strProperty As String, dblProperty As Double
Public Function ReverseString() As String
Return (StrReverse(strProperty))
End Function
Public Function NegateNumber() As Double
Return (-dblProperty)
End Function
End Class
Let’s test the members we’ve implemented so far. Switch back to your form and enter the lines
shown in Listing 10.2 in a new button’s Click event handler. The obj variable is of the Minimal
type and exposes the Public members of the class. You can set and read its properties, and call its
methods. In Figure 10.2, you see a few more members than the ones added so far; we’ll extend our
Minimal class in the following section. Your code doesn’t see the class’s code, just as it doesn’t see
any of the built-in classes’ code. You trust that the class knows what it is doing and does it right.
Listing 10.2: Testing theMinimal Class
Dim obj As New Minimal
obj.dblProperty = 999999
Debug.WriteLine(obj.NegateNumber)Petroutsos c10.tex V3 - 01/28/2008 1:32pm Page 356
Figure 10.2
The members of the
class are displayed auto-
matically by the IDE, as
Every time you create a new variable of theMinimal type, you’re creating a new instance of the
Minimal class. The class’s code is loaded into memory only the ?rst time you create a variable of
this type, but every time you declare another variable of the same type, a new set of variables is
created. This is called an instance of the class. The code is loaded once, but it can act on different sets
of variables. In effect, different instances of the class are nothing more than different sets of local
The New Keyword
The New keyword tells VB to create a new instance of the Minimal class. If you omit the New keyword,
you’re telling the compiler that you plan to store an instance of the Minimal class in the obj variable,
but the class won’t be instantiated. All the compiler can do is prevent you from storing an object of
any other type in the obj variable. You must still initialize the obj variable with the New keyword on
a separate line:
obj = New Minimal
It’s the New keyword that creates the object in memory. The obj variable simply points to this object.
If you omit the New keyword, a Null Reference exception will be thrown when the code attempts to
use the variable. This means that the variable is Nothing — it hasn’t been initialized yet. Even as you
work in the editor’s window, the name of the variable will be underlined and the following warning
will be generated: Variable ‘obj’ is used before it has been assigned a value. A null reference exception
could result at runtime. You can compile the code and run it if you want, but everything will proceed
as predicted: As soon as the statement that produced the warning is reached, the runtime exception
will be thrown.
Using Property Procedures
The strProperty and dblProperty properties will accept any value, as long as the type is correct
and the value of the numeric property is within the acceptable range. But what if the generic
properties were meaningful entities, such as email addresses, ages, or zip codes? We should be
able to invoke some code to validate the values assigned to each property. To do so, we implement
each property as a special type of procedure: the so-called Property procedure.Petroutsos c10.tex V3 - 01/28/2008 1:32pm Page 357
Properties are implemented with a special type of procedure that contains a Get and a Set
section (frequently referred to as the property’s getter and setter, respectively). The Set section
of the procedure is invoked when the application attempts to set the property’s value; the Get
section is invoked when the application requests the property’s value. The value passed to the
property is usually validated in the Set section and, if valid, is stored to a local variable. The same
local variable’s value is returned to the application when it requests the property’s value, from
the property’s Get section. Listing 10.3 shows what the implementation of an Age property would
look like.
Listing 10.3: Implementing Propertieswith Property Procedures
Private m Age As Integer
Property Age() As Integer
Age = m Age
End Get
Set (ByVal value As Integer)
If value < 0 Or value >= 100 Then
MsgBox(”Age must be positive and less than 100”)
m Age = value
End If
End Set
End Property
m Age is the local variable where the age is stored. When a statement such as the following
is executed in the application that uses your class, the Set section of the Property procedure is
obj.Age = 39
Because the property value is valid, it is stored in the m Age local variable. Likewise, when
a statement such as the following one is executed, the Get section of the Property procedure is
invoked, and the value 39 is returned to the application:
The value argument of the Set procedure represents the actual value that the calling code is
attempting to assign to the property. The m Age variable is declared as private because we don’t
want any code outside the class to access it directly. The Age property is, of course, Public, so that
other applications can set it.
Fields versus Properties
Technically, any variables that are declared as Public in a class are called ?elds. Fields behave just like
properties in the sense that you can assign values to them and read their values, but there’s a criti-
cal distinction between ?elds and properties: When a value is assigned to a ?eld, you can’t validate
the value from within your code. If the value is of the wrong type, an exception will occur. PropertiesPetroutsos c10.tex V3 - 01/28/2008 1:32pm Page 358
should be implemented with a Property procedure, so you can validate their values, as you saw in
the preceding example. Not only that, but you can set other values from within your code. Consider
a class that represents a contract with a starting and ending date. Every time the user changes the
starting date, the code can adjust the ending date accordingly (which is something you can’t do with
?elds). If the two dates were implemented as ?elds, users of the class could potentially specify an
ending date prior to the starting date.
Enter the Property procedure for the Age property in the Minimal class and then switch to
the form to test it. Open the button’s Click event handler and add the following lines to the
existing ones:
obj.Age = 39
Debug.WriteLine(”after setting the age to 39, age is ” &
obj.Age = 199
Debug.WriteLine(”after setting the age to 199, age is ” &
The value 39 will appear twice in the Output window, which means that the class accepts the
value 39. When the third statement is executed, a message box will appear with the
error’s description:
Age must be positive and less than 100
The value 39 will appear in the Output window again. The attempt to set the age to 199 failed,
so the property retains its previous value.
Throwing Exceptions
Our error-trapping code works ?ne, but what good is a message box displayed from within a
class? As a developer using the Minimal class in your code, you’d rather receive an exception and
handle it from within your code. So let’s change the implementation of the Age property a little.
The Property procedure for the Age property (Listing 10.4) throws an InvalidArgument exception
if an attempt is made to assign an invalid value to it. The InvalidArgument exception is one of
the existing exceptions, and you can reuse it in your code. Later in this chapter, you’ll learn how
to create and use custom exceptions.
Listing 10.4: Throwing an Exception fromwithin a Property Procedure
Private m Age As Integer
Property Age() As Integer
Age = m Age
End Get
Set (ByVal value As Integer)
If value < 0 Or value >= 100 ThenPetroutsos c10.tex V3 - 01/28/2008 1:32pm Page 359
Dim AgeException As New ArgumentException()
Throw AgeException
M Age = value
End If
End Set
End Property
You can test the revised property de?nition in your application; switch to the test form, and
enter the statements of Listing 10.5 in a new button’s Click event handler. (This is the code behind
the Handle Exceptions button on the test form.)
Listing 10.5: Catching the Age Property’s Exception
Dim obj As New Minimal
Dim userAge as Integer
UserAge = InputBox(”Please enter your age”)
obj.Age = userAge
Catch exc as ArgumentException
MsgBox(”Can’t accept your value, ” & userAge.ToString & VbCrLf &
”Will continue with default value of 30”)
obj.Age = 30
End Try
This is a much better technique for handling errors in your class. The exceptions can be inter-
cepted by the calling application, and developers using your class can write robust applications by
handling the exceptions in their code. When you develop custom classes, keep in mind that you
can’t handle most errors from within your class because you don’t know how other developers
will use your class.
Handling Errors in a Class
When you design classes, keep in mind that you don’t know how another developer may use
them. In fact, you may have to use your own classes in a way that you didn’t consider when you
designed the class. A typical example is using an existing class with a web application. If your class
displays a message box, it will work ?ne as part of a Windows Forms application. In the context of a
web application, however, the message box will be displayed on the monitor of the server that hosts
the application, and no one will see it. As a result, the application will keep waiting for a response to a
message box before it continues; however, there’s no user to click the OK button in the message box,
because the code is executing on a server. Even if you don’t plan to use a custom class with a web
application, never interact with the user from within the class’s code. Make your code as robust as
you can, but don’t hesitate to throw exceptions for all conditions you can’t handle from within your
code (Figure 10.3). In general, a class’s code should detect abnormal conditions, but it shouldn’t
attempt to remedy them.Petroutsos c10.tex V3 - 01/28/2008 1:32pm Page 360
The application that uses your class may inform the user about an error condition and give the user a
chance to correct the error by entering new data, disable some options on the interface, and so on. As
a class developer, you can’t make this decision — another developer might prompt the user for
another value, and a sloppy developer might let his or her application crash (but this isn’t your
problem). To throw an exception from within your class’s code, call the Throw statement with an
Exception object as an argument. To play well with the Framework, you should try to use one of the
existing exceptions (and the Framework provides quite a few of them). You can also throw custom
exceptions by using a statement such as the following:
Throw New Exception(”your exception’s description”)
Figure 10.3
Raising an exception in
the class’s code
Implementing Read-Only Properties
Let’s make our class a little more complicated. Age is not usually requested on of?cial docu-
ments, because it’s valid only for a year after ?lling out a questionnaire. Instead, you must furnish
your date of birth, from which your current age can be calculated at any time. We’ll add a BDate
property in our class and make Age a read-only property.
To make a property read-only, you simply declare it as ReadOnly and supply the code for the
Get procedure only. Revise the Age property’s code in the Minimal class, as seen in Listing 10.6.
Then enter the Property procedure from Listing 10.7 for the BDate property.
Listing 10.6: Implementing a Read-Only Property
Private m Age As Integer
ReadOnly Property Age() As Integer
Age = m Age
End Get
End PropertyPetroutsos c10.tex V3 - 01/28/2008 1:32pm Page 361
Listing 10.7: The BDate Property
Private m BDate As DateTime
Private m Age As Integer
Property BDate() As DateTime
BDate = m BDate
End Get
Set(ByVal value As Date)
If Not IsDate(value) Then
Dim DataTypeException As
New Exception(”Invalid date value”)
Throw DataTypeException
End If
If value > Now() Or
DateDiff(DateInterval.Year, value, Now()) >= 100 Then
Dim AgeException As New Exception
(”Can’t accept the birth date you specified”)
Throw AgeException
m BDate = value
m Age = DateDiff(DateInterval.Year, value, Now())
End If
End Set
End Property
As soon as you enter the code for the revised Age property, two error messages will appear
in the Error List window. The code in the application’s form is attempting to set the value of a
read-only property, and the editor produces the following error message twice: Property ‘Age’ is
‘ReadOnly.’ As you probably ?gured out, we must set the BDate property in the code, instead
of the Age property. The two errors are the same, but they refer to two different statements that
attempt to set the Age property.
There are two types of errors that can occur while setting the BDate property: an invalid date
or a date that yields an unreasonable age. First, the code of the BDate property makes sure that
the value passed by the calling application is a valid date. If not, it throws an exception. If the
value variable is a valid date, the code calls the DateDiff() function, which returns the difference
between two dates in a speci?ed interval— in our case, years. The expression DateInterval.Year
is the name of a constant, which tells the DateDiff() function to calculate the difference between
the two dates in years. You don’t have to memorize the constant names — you simply select them
from a list as you type.
So, the code checks the number of years between the date of birth and the current date. If it’s
negative (which means that the person hasn’t been born yet) or more than 100 years (we’ll assume
that people over 100 will be treated as being 100 years old), it rejects the value. Otherwise, it sets
the value of the m BDate local variable and calculates the value of the m Age local variable.
Calculating Property Values on the Fly
There’s still a serious ?aw in the implementation of the Age property. Can you see it? The person’s
age is up-to-date the moment the birth date is entered, but what if we read it back from a ?le orPetroutsos c10.tex V3 - 01/28/2008 1:32pm Page 362
database three years later? It will still return the original value, which is no longer the correct age.
The Age property’s value shouldn’t be stored anywhere; it should be calculated from the person’s
birth date as needed. If we avoid storing the age to a local variable and calculate it on the ?y,
users will always see the correct age. Revise the Age property’s code to match Listing 10.8, which
calculates the difference between the date of birth and the current date, and returns the correct
person’s age every time it’s called.
Listing 10.8: A Calculated Property
ReadOnly Property Age() As Integer
Age = Convert.ToInt32(DateDiff(DateInterval.Year, m BDate , Now()))
End Get
End Property
Notice also that you no longer need the m Age local variable because the age is calculated on
the ?y when requested, so remove its declaration from the class. As you can see, you don’t always
have to store property values to local variables. A property that returns the number of ?les in a
directory, for example, also doesn’t store its value in a local variable. It retrieves the requested
information on the ?y and furnishes it to the calling application. By the way, the calculations
might still return a negative value if the user has changed the system’s date, but this is a rather
far-fetched scenario.
You can implement write-only properties with the WriteOnly keyword and a Set section only,
but write-only properties are rarely used (in my experience, only for storing passwords).
Our Minimal class is no longer so minimal. It exposes some functionality, and you can easily
add more. Add properties for name, profession, and income, and add methods to calculate insur-
ance rates based on a person’s age and anything you can think of. Experiment with a few custom
members, add the necessary validation code in your Property procedures, and you’ll soon ?nd
out that building and reusing custom classes is a simple and straightforward process. Of course,
there’s a lot more to learn about classes, but you already have a good understanding of the way
classes combine code with data.
Customizing Default Members
As you recall, when you created the Minimal class for the ?rst time, before adding any code,
the class already exposed a few members— the default members, such as the ToString method
(which returns the name of the class) and the Equals method (which compares two objects for ref-
erence equality). You can (and should) provide your custom implementation for these members;
this is what we’re going to do in this section.
Customizing the ToString Method
The custom ToString method is implemented as a Public function, and it must override the
default implementation. The implementation of a custom ToString method is shown next:
Public Overrides Function ToString() As String
Return ”The infamous Minimal class”
End FunctionPetroutsos c10.tex V3 - 01/28/2008 1:32pm Page 363
As soon as you enter the keyword Overrides, the editor will suggest the names of the three
members you can override: ToString, Equals,and GetHashCode. Select the ToString method,
and the editor will insert a default implementation for you. The default implementation returns
the string MyBase.ToString. This is the default implementation. (You’ll see later in this chapter
what the MyBase keyword is; it basically references the default class implementation.) Just
replace the statement inserted by the editor with the one shown in the preceding statement. It’s
that simple.
The Overrides keyword tells the compiler that this implementation overwrites the default
implementation of the class. The original method’s code isn’t exposed, and you can’t revise it. The
Overrides keyword tells the compiler to ‘‘hide’’ the original implementation and use your custom
ToString method instead. After you override a method in a class, the application using the class
can no longer access the original method. Ours is a simple method, but you can return any string
you can build in the function. For example, you can incorporate the value of the BDate property
in the string:
Return(”MINIMAL: ” & m BDate.ToShortDateString)
The value of the local variable m BDate is the value of the BDate property of the current instance
of the class. Change the BDate property, and the ToString method will return a different string.
When called through different variables, the ToString method will report different values.
Let’s say that you created and initialized two instances of theMinimal class by using the following
Dim obj1 As New Minimal()
Obj1.Bdate = #1/1/1963#
Dim obj2 As New Minimal()
Obj2.Bdate = #12/31/1950#
The last two statements will print the following lines in the Output window:
MINIMAL: 1963-01-01
MINIMAL: 1950-12-31
Customizing the Equals Method
The Equals method exposed by most of the built-in objects can compare values, not references.
Two Rectangle objects, for example, are equal if their dimensions and origins are the same. The
following two rectangles are equal:
Dim R1 As New Rectangle(0, 0, 30, 60)
Dim R2 As New Rectangle
R2.X = 0
R2.Y = 0
R2.Width = 30
R2.Height = 60
If R1.Equals(R2) Then
MsgBox(”The two rectangles are equal”)
End IfPetroutsos c10.tex V3 - 01/28/2008 1:32pm Page 364
If you execute these statements, a message box con?rming the equality of the two objects
will pop up. The two variables point to different objects (that is, different instances of the same
class), but the two objects are equal, because they have the same origin and same dimensions. The
Rectangle class provides its own Equals method, which knows how to compare two Rectangle
objects. If your class doesn’t provide a custom Equals method, all the compiler can do is com-
pare the objects referenced by the two variables of the speci?c type. In the case of our Minimal
class, the Equals method returns True if the two variables point to the same object (which is the
same instance of the class). If the two variables point to two different objects, the default Equals
method will return False, even if the two objects are equal.
You’re probably wondering what makes two objects equal. Is it all their properties or perhaps
some of them? Two objects are equal if the Equals method says so. You should compare the objects
in a way that makes sense, but you’re in no way limited as to how you do this. In a very speci?c
application, you might decide that two rectangles are equal because they have the same area, or
perimeter, regardless of their dimensions and origin, and override the Rectangle object’s Equals
method. In theMinimal class, for example, youmight decide to compare the birth dates and return
True if they’re equal. Listing 10.9 is the implementation of a possible custom Equals method for
the Minimal class.
Listing 10.9: ACustomEqualsMethod
Public Overrides Function Equals(ByVal obj As Object) As Boolean
Dim O As Minimal = CType(obj, Minimal)
If O.BDate = m BDate Then
Equals = True
Equals = False
End If
End Function
Notice that the Equals method is pre?xed with the Overrides keyword, which tells the
compiler to use our custom Equals method in the place of the original one. To test the new Equals
method, place a new button on the form and insert the statements of Listing 10.10 in its Click
event handler.
Listing 10.10: Testing the CustomEqualsMethod
Dim O1 As New Minimal
Dim O2 As New Minimal
O1.BDate = #3/1/1960#
O2.BDate = #3/1/1960#
O1.strProperty = ”object1”
O2.strProperty = ”OBJECT2”
If O1.Equals(O2) Then
MsgBox(”They’re equal”)
End IfPetroutsos c10.tex V3 - 01/28/2008 1:32pm Page 365
If you run the application, you’ll see the message con?rming that the two objects are equal,
despite the fact that their strProperty properties were set to different values. The BDate property
is the same, and this is the only setting that the Equals method examines. So, it’s up to you to
decide which properties fully and uniquely identify an object and to use these properties to deter-
mine when two objects are equal. In a class that represents persons, you’d probably use Social
Security numbers, or a combination of names and birth dates. In a class that represents cars, you’d
use the maker, model, and year, and so on.
Know What You Are Comparing
The Equals method shown in Listing 10.10 assumes that the object you’re trying to compare to
the current instance of the class is of the same type. Because you can’t rely on developers to catch
all their mistakes, you should know what you’re comparing before you attempt to perform the
comparison. A more-robust implementation of the Equals method is shown in Listing 10.11. This
implementation tries to convert the argument of the Equals method to an object of the Minimal
type and then compares it to the current instance of the Minimal class. If the conversion fails, an
InvalidCastException is thrown and no comparison is performed.
Listing 10.11: AMore-Robust EqualsMethod
Public Overrides Function Equals(ByVal obj As Object) As Boolean
Dim O As New Minimal()
O = DirectCast(obj, Minimal)
Catch typeExc As InvalidCastException
Throw typeExc
Exit Function
End Try
If O.BDate = m BDate Then
Equals = True
Equals = False
End If
End Function
The Is Operator
The equals ( = ) operator can be used in comparing all built-in objects. The following statement is
quite valid, as long as the R1 and R2 variables were declared of the Rectangle type:
If R1 = R2 Then
MsgBox(”The two rectangles are equal”)
End If
This operator, however, can’t be used with the Minimal custom class. Later in this chapter,
you’ll learn how to customize operators in your class. In the meantime, you can use only the
Is operator, which compares for reference equality (whether the two variables reference thePetroutsos c10.tex V3 - 01/28/2008 1:32pm Page 366
same object), and the Equals method. If the two variables R1 and R2 point to the same object, the
following statement will return True:
If obj1 Is obj2 Then
MsgBox(”The two variables reference the same object”)
End If
The Is operator tells you that the two variables point to a single object. There’s no comparison
here; the compiler simply ?gures out whether they point to same object in memory. It will return
True if a statement such as the following has been executed before the comparison:
obj2 = obj1
If the Is operator returns True, there’s only one object in memory and you can set its properties
through either variable.
Custom Enumerations
Let’s add a little more complexity to our class. Because we’re storing birth dates to our custom
objects, we can classify persons according to their age. Most BASIC developers will see an oppor-
tunity to use constants here. Instead of using constants to describe the various age groups, we’ll
use an enumeration with the following group names:
Public Enum AgeGroup
End Enum
These statements must appear outside any procedure in the class, and we usually place them
at the beginning of the ?le, right after the declaration of the class. Public is an access modi?er (we
want to be able to access this enumeration from within the application that uses the class). Enum
is a keyword: It speci?es the beginning of the declaration of an enumeration and it’s followed by
the enumeration’s name. The enumeration itself is a list of integer values, each one mapped to a
name. In our example, the name Infant corresponds to 0, the name Child corresponds to 1, and
so on. The list of the enumeration’s members ends with the End Enum keyword. You don’t really
care about the actual values of the names because the very reason for using enumerations is to
replace numeric constants with more-meaningful names. You’ll see shortly how enumerations are
used both in the class and the calling application.
As you already know, the Framework uses enumerations extensively, and this is how you
can add an enumeration to your custom class. You should provide an enumeration for any prop-
erty with a relatively small number of predetermined settings. The property’s type should be the
name of the enumeration, and the editor will open a drop-down box with the property’s settings
as needed.
Now add to the class the GetAgeGroup method (Listing 10.12), which returns the name of the
age group to which the person represented by an instance of the Minimal class belongs. The name
of the group is a member of the AgeGroup enumeration.Petroutsos c10.tex V3 - 01/28/2008 1:32pm Page 367
Listing 10.12: Using an Enumeration
Public Function GetAgeGroup() As AgeGroup
Select Case m Age
Case Is < 3 : Return (AgeGroup.Infant)
Case Is < 10 : Return (AgeGroup.Child)
Case Is < 21 : Return (AgeGroup.Teenager)
Case Is < 65 : Return (AgeGroup.Adult)
Case Is < 100 : Return (AgeGroup.Senior)
Case Else : Return (AgeGroup.Overaged)
End Select
End Function
The GetAgeGroup method returns a value of the AgeGroup type. Because the AgeGroup enu-
meration was declared as Public, it’s exposed to any application that uses the Minimal class. Let’s
see how we can use the same enumeration in our application. Switch to the form’s code window,
add a new button, and enter the statements from Listing 10.13 in its event handler.
Listing 10.13: Using the Enumeration Exposed by the Class
Protected Sub Button1 Click(...)
Handles Button1.Click
Dim obj As Minimal
obj = New Minimal()
obj.BDate = InputBox(”Please Enter your birthdate”)
Catch ex As ArgumentException
Exit Sub
End Try
Dim discount As Single
If obj.GetAgeGroup = Minimal.AgeGroup.Infant Or
obj.GetAgeGroup = Minimal.AgeGroup.Child Then discount = 0.4
If obj.GetAgeGroup = Minimal.AgeGroup.Senior Then discount = 0.5
If obj.GetAgeGroup = Minimal.AgeGroup.Teenager Then discount = 0.25
MsgBox(”You age is ” & obj.Age.ToString &
” and belong to the ” &
obj.GetAgeGroup.ToString &
” group” & vbCrLf & ”Your discount is ” &
Format(discount, ”Percent”))
End Sub
This routine calculates discounts based on the person’s age. Notice that we don’t use numeric
constants in our code, just descriptive names. Moreover, the possible values of the enumeration
are displayed in a drop-down list by the IntelliSense feature of the IDE as needed (Figure 10.4),Petroutsos c10.tex V3 - 01/28/2008 1:32pm Page 368
and you don’t have to memorize them or look them up as you would with constants. I’ve used
an implementation with multiple If statements in this example, but you can perform the same
comparisons by using a Select Case statement.
Figure 10.4
The members of an enu-
meration are displayed
automatically in the IDE
as you type.
You could also call the GetAgeGroup method once, store its result to a variable, and then use
this variable in the comparisons. This approach is slightly more ef?cient, because you don’t have to
call the member of the class repeatedly. The variable, as you can guess, should be of the AgeGroup
type. Here’s an alternate code of the statements of Listing 10.13 using a temporary variable, the
grp variable, and a Select Case statement:
Dim grp As AgeGroup = obj.GetAgeGroup
Select Case grp
Case Minimal.AgeGroup.Infant, Minimal.AgeGroup.Child ...
Case Minimal.AgeGroup.Teenager ...
Case Minimal.AgeGroup.Senior ...
Case Else
End Select
You’ve seen the basics of working with custom classes in a VB application. Let’s switch to a
practical example that demonstrates not only the use of a real-world class, but also how classes
can simplify the development of a project.
VB 2008 at Work: The Contacts Project
In Chapter 7, ‘‘Working with Forms,’’ I discussed brie?y the Contacts application. This application
uses a custom Structure to store the contacts and provides four navigational buttons to allow
userstomovetothe?rst,last,previous,and next contact. Now that you have learned how to
program the ListBox control and how to use custom classes in your code, we’ll revise the Contacts
application. First, we’ll implement a class to represent each contact. The ?elds of each contact
(company and contact names, addresses, and so on) will be implemented as properties and they
will be displayed in the TextBox controls on the form.
We’ll also improve the user interface of the application. Instead of the rather simplistic nav-
igational buttons, we’ll place all the company names in a sorted ListBox control. The user can
easily locate the desired company and select it from the list to view the ?elds of the selected com-
pany. The editing buttons at the bottom of the form work as usual, but we no longer need the
navigational buttons. Figure 10.5 shows the revised Contacts application.Petroutsos c10.tex V3 - 01/28/2008 1:32pm Page 369
Figure 10.5
The interface of the
Contacts application is
based on the ListBox
Make a copy of the Contacts folder from Chapter 7 into a new folder. First, delete the decla-
ration of the Contact structure and add a class to the project. Name the new class Contact and
enter the code from Listing 10.14 into it. The names of the private members of the class are the
same as the actual property names, and they begin with an underscore. (This is a good convention
that lets you easily distinguish whether a variable is private, and the property value it stores.) The
implementation of the properties is trivial, so I’m not showing the code for all of them.
Listing 10.14: The Contact Class
<Serializable()> Public Class Contact
Private companyName As String
Private contactName As String
Private address1 As String
Private address2 As String
Private city As String
Private state As String
Private zip As String
Private tel As String
Private email As String
Private URL As String
Property CompanyName() As String
CompanyName = companyName
End Get
Set(ByVal value As String)
If value Is Nothing Or value = ”” Then
Throw New Exception(”Company Name field can’t be empty”)
Exit Property
End IfPetroutsos c10.tex V3 - 01/28/2008 1:32pm Page 370
companyName = value
End Set
End Property
Property ContactName() As String
ContactName = contactName
End Get
Set(ByVal value As String)
contactName = value
End Set
End Property
Property Address1() As String
Address1 = address1
End Get
Set(ByVal value As String)
address1 = value
End Set
End Property
Property Address2() As String
End Property
Property City() As String
End Property
Property State() As String
End Property
Property ZIP() As String
End Property
Property tel() As String
End Property
Property EMail() As String
EMail = email
End Get
Set(ByVal value As String)
If value.Contains(”@”) Or value.Trim.Length = 0 Then
email = Value
Throw New Exception(”Invalid e-mail address!”)Petroutsos c10.tex V3 - 01/28/2008 1:32pm Page 371
End If
End Set
End Property
Property URL() As String
End Get
Set(ByVal value As String)
URL = value
End Set
End Property
Overrides Function ToString() As String
If contactName = ”” Then
Return companyName
Return companyName & vbTab & ”(” & contactName & ”)”
End If
End Function
Public Sub New()
End Sub
Public Sub New(ByVal CompanyName As String,
ByVal LastName As String, ByVal FirstName As String)
Me.ContactName = LastName & ”, ” & FirstName
Me.CompanyName = CompanyName
End Sub
Public Sub New(ByVal CompanyName As String)
Me.CompanyName = CompanyName
End Sub
End Class
The ?rst thing you’ll notice is that the class’s de?nition is pre?xed by the <Serializable()>
keyword. The topic of serialization is discussed in Chapter 16, ‘‘XML and Object Serialization,’’
but for now all you need to know is that the .NET Framework can convert objects to a text or
binary format and then store them in ?les. Surprisingly, this process is quite simple; as you will
see, we’ll be able to dump an entire collection of Contact objects to a ?le with a single statement.
The <Serializable()> keyword is an attribute of the class, and (as you will see later in this
book) there are more attributes you can use with your classes — or even with your methods. The
most prominent method attribute is the <WebMethod> attribute, which turns a regular function
into a web method.
The various ?elds of the Contact structure are now properties of the Contact class. The imple-
mentation of the properties is trivial except for the CompanyName and EMail properties, whichPetroutsos c10.tex V3 - 01/28/2008 1:32pm Page 372
contain some validation code. The Contact class requires that the CompanyName property have a
value; if it doesn’t, the class throws an exception. Likewise, the EMail property must contain the
symbol @.Finally,theclassprovidesitsown ToString method, which returns the name of the
company followed by the contact name in parentheses.
The ListBox control, in which we’ll store all contacts, displays the value returned by the object’s
ToString method, which is why you have to provide your own implementation of the method to
describe each contact. The company name should be adequate, but if there are two companies by
the same name, you can use another ?eld to differentiate them. I used the contact name, but you
can use any of the other properties (the URL would be a good choice).
The ListBox displays a string, but it stores the object itself. In essence, it’s used not only as a nav-
igational tool, but also as a storage mechanism for our contacts. Now, we must change the code of
the main form a little. Start by removing the navigational buttons; we no longer need them. Their
function will be replaced by a few lines of code in the ListBox control’s SelectedIndexChanged
event. Every time the user selects another item on the list, the statements shown in Listing 10.15
display the contact’s properties in the various TextBox controls on the form.
Listing 10.15: Displaying the Fields of the Selected Contact Object
Private Sub ListBox1 SelectedIndexChanged(...)
Handles ListBox1.SelectedIndexChanged
currentContact = ListBox1.SelectedIndex
End Sub
The ShowContact() subroutine reads the object stored at the location speci?ed by the cur-
rentContact variable and displays its properties in the various TextBox controls on the form. The
TextBox controls are normally read-only, except when editing a contact. This action is signaled by
clicking the Edit or the Add button on the form.
When a new contact is added, the code reads its ?elds from the controls on the form, creates
a new Contact object, and adds it to the ListBox control. When a contact is edited, a new Contact
object replaces the currently selected object on the control. The code is similar to the code of the
Contacts application. I should mention that the ListBox control is locked while a contact is being
added or edited, because it doesn’t make sense to select another contact at that time.
Adding, Editing, and Deleting Contacts
To delete a contact (Listing 10.16), we simply remove the currently selected object from the ListBox
control. In addition, we must select the next contact on the list (or the ?rst contact if the deleted
one was last in the list).
Listing 10.16: Deleting an Object fromthe ListBox
Private Sub bttnDelete Click(...) Handles bttnDelete.Click
If currentContact > -1 Then
currentContact = ListBox1.Items.Count - 1
If currentContact = -1 Then
ClearFields()Petroutsos c10.tex V3 - 01/28/2008 1:32pm Page 373
MsgBox(”There are no more contacts”)
End If
MsgBox(”No current contacts to delete”)
End If
End Sub
When you add a new contact, the following code is executed in the Add button’s Click event
Private Sub bttnAdd Click(...) Handles bttnAdd.Click
adding = True
ListBox1.Enabled = False
End Sub
The controls are cleared in anticipation of the new contact’s ?elds, and the adding variable is set
to True. The OK button is clicked to end either the addition of a new record or an edit operation.
The code behind the OK button is shown in Listing 10.17.
Listing 10.17: Committing a New or Edited Record
Private Sub bttnOK Click(...) Handles bttnOK.Click
If SaveContact() Then
ListBox1.Enabled = True
End If
End Sub
As you can see, the same subroutine handles both the insertion of a new record and the editing
of an existing one. All the work is done by the SaveContact() subroutine, which is shown in
Listing 10.18.
Listing 10.18: The SaveContact() Subroutine
Private Function SaveContact() As Boolean
Dim contact As New Contact
contact.CompanyName = txtCompany.Text
contact.ContactName = txtContact.Text
contact.Address1 = txtAddress1.Text
contact.Address2 = txtAddress2.Text
contact.City = txtCity.Text
contact.State = txtState.Text
contact.ZIP = txtZIP.TextPetroutsos c10.tex V3 - 01/28/2008 1:32pm Page 374
contact.EMail = txtEMail.Text
contact.URL = txtURL.Text
Catch ex As Exception
Return False
End Try
If adding Then
ListBox1.Items(currentContact) = contact
End If
Return True
End Function
The SaveContact() function uses the adding variable to distinguish between an add and
an edit operation, and either adds the new record to the ListBox control or replaces the current
item in the ListBox with the values on the various controls. Because the ListBox is sorted, new
contacts are automatically inserted in the correct order. If an error occurs during the operation,
the SaveContact() function returns False to alert the calling code that the operation failed (most
likely because one of the assignment operations caused a validation error in the class’s code).
The last operation of the application is the serialization and deserialization of the items in the
ListBox control. Serialization is the process of converting an object to a stream of bytes for storing
to a disk ?le, and deserialization is the opposite process. To serialize objects, we ?rst store them
into an ArrayList object, which is a dynamic array that stores objects and can be serialized as a
whole. Likewise, the disk ?le is deserialized into an ArrayList to reload the persisted data back to
the application; then each element of the ArrayList is moved to the Items collection of the ListBox
control. ArrayLists and other Framework collections are discussed in Chapter 14, ‘‘Storing Data
in Collections,’’ and object serialization is discussed in Chapter 16. You can use these features
to test the application and examine the corresponding code after you read about ArrayLists and
serialization. I’ll discuss the code of the Load and Save operations of the Contacts sample project
in Chapter 16.
Making the Most of the ListBox Control
This section’s sample application demonstrates an interesting technique for handling a set of data at
the client. We usually need an ef?cient mechanism to store data at the client, where all the process-
ing takes place — even if the data comes from a database. In this example, we used the ListBox con-
trol, because each item of the control can be an arbitrary object. Because the control displays the
string returned by the object’s ToString method, we’re able to customize the display by providing
our own implementation of the ToString method. As a result, we’re able to use the ListBox control
both as a data-storage mechanism and as a navigational tool. As long as the strings displayed on the
control are meaningful descriptions of the corresponding objects and the control’s items are sorted,
the ListBox control can be used as an effective navigational tool. If you have toomany items to display
on the control, you should also provide a search tool to help users quickly locate an item in the list,
without having to scroll up and down a long list of items. Review the ListBoxFind project of Chapter
6 for information on searching the contents of the ListBox control.Petroutsos c10.tex V3 - 01/28/2008 1:32pm Page 375
When data are being edited, you have to cope with another possible problem. The user may edit the
data for hours and forget to save the edits every now and then. If the computer (or, even worse,
the application) crashes, a lot of work will be wasted. Sure the application provides a Save command,
but you should always try to protect users from their mistakes. It would be nice if you could save the
data to a temporary ?le every time the user edits or adds an item to the list. This way, if the computer
crashes, users won’t lose their edits. When the application starts, it should automatically detect the
presence of the temporary ?le and reload it. Every time the user saves the data by using the applica-
tion’s Save command, or terminates the application, the temporary ?le should be removed.
Object Constructors
Let’s switch to a few interesting topics in programming with objects. Objects are instances of
classes, and classes are instantiated with the New keyword. The New keyword can be used with
a number of arguments, which are the initial values of some of the object’s basic properties. To
construct a rectangle, for example, you can use these two statements:
Dim shape1 As Rectangle = New Rectangle()
shape1.Width = 100
shape1.Height = 30
or the following one:
Dim shape1 As Rectangle = New Rectangle(100, 30)
The objects in the Minimal class can’t be initialized to speci?c values of their properties and
they expose the simple form of the New constructor — the so-called parameterless constructor.
Every class has a parameterless constructor, even if you don’t specify it. You can implement param-
eterized constructors, which allow you to pass arguments to an object as you declare it. These
arguments are usually the values of the object’s basic properties. Parameterized constructors don’t
pass arguments for all the properties of the object; they expect only enough parameter values to
make the object usable.
Parameterized constructors are implemented via Public subroutines that have the name New.
You can have as many overloaded forms of the New() subroutine as needed. Most of the built-in
classes provide a parameterless constructor, but the purists of OOP will argue against param-
eterless constructors. Their argument is that you shouldn’t allow users of your class to create
invalid instances of it. A class for describing customers, for example, should expose at least a Name
property. A class for describing books should expose a Title and an ISBN property. If the corre-
sponding constructor requires that these properties be speci?ed before creating an instance of the
class, you’ll never create objects with invalid data.
Let’s add a parameterized constructor to our Contact class. Each contact should have at least a
name; here’s a parameterized constructor for the Contact class:
Public Sub New(ByVal CompanyName As String)
Me.CompanyName = CompanyName
End Sub
The code is trivial, with the exception of the statement that calls the MyBase.New() subroutine.
MyBase is an object that lets you access the members of the base class (a topic that’s discussedPetroutsos c10.tex V3 - 01/28/2008 1:32pm Page 376
in detail later in this chapter). The reason you must call the New method of the base class is that
the base class might have its own constructor, which can’t be called directly. You must always
insert this statement in your constructors to make sure that any initialization tasks that must be
performed by the base class will not be skipped.
The Contact class’s constructor accepts a single argument: the company name (this property
can’t be a blank string). Another useful constructor for the same class accepts two additional
arguments, the contact’s ?rst and last names, as follows:
Public Sub New(ByVal CompanyName As String,
ByVal LastName As String, ByVal FirstName As String)
Me.ContactName = LastName & ”, ” & FirstName
Me.CompanyName = CompanyName
End Sub
With the two parameterized constructors in place, you can create new instances of the Contact
class by using statements such as the following:
Dim contact1 As New Contact(”Around the Horn”)
Or the following:
Dim contact1 As New Contact(”Around the Horn”, ”Hardy”, ”Thomas”)
Notice the lack of the Overloads (or Overrides) keyword. Constructors can have multiple
forms and don’t require the use of Overloads — just supply as many implementations of the
New() subroutine as you need.
One last, but very convenient technique to initialize objects was introduced with Visual Basic
2008. This technique allows you to supply values for as many properties of the new object as you
wish, using the With keyword. The following statements create two new instances of the Person
class, and they initialize each one differently:
Dim P1 As New Person With
{.LastName = ”Doe”, .FirstName = ”Joe”})
Dim P2 As New Person With
{.LastName = ”Doe”, .Email = ””})
This syntax allows you to quickly initialize new objects, regardless of their constructors; in
effect, you can create your own constructor for any class. This technique will be handy when
combining object initialization with other statements, such as in the following example, which
adds a new object to a list:
Persons.Add(New Person With {.LastName = ”Doe”, .FirstName = ”Joe”})
Persons.Add(New Person With {.LastName = ”Doe”})
Using the SimpleClass in Other Projects
The projects we built in this section are Windows applications that contain a Class module. The
class is contained within the project, and it’s used by the project’s main form. What if you want to
use this class in another project?Petroutsos c10.tex V3 - 01/28/2008 1:32pm Page 377
First, you must change the type of the project. AWindows project can’t be used as a component
in another project. Right-click the SimpleClass project and choose Properties. In the project’s Prop-
erty Pages dialog box, switch to the Application tab, locate the Application Type drop-down list,
and change the project’s type from Windows Forms Application to Class Library, as shown in
Figure 10.6. Then close the dialog box. When you return to the project, right-click the TestForm
and select Exclude From Project. A class doesn’t have a visible interface, and there’s no reason to
include the test form in your project.
Figure 10.6
Setting a project’s prop-
erties through the Prop-
erty Pages dialog box
From the main menu, choose Build  Build SimpleClass. This command will compile the
SimpleClass project and create a DLL ?le (the ?le that contains the class’s code and the ?le you
must use in any project that needs the functionality of the SimpleClass class). The DLL ?le will be
created in the \bin\Release folder under the project’s folder.
Let’s use the SimpleClass.dll ?le in another project. Start a new Windows application, open
the Project menu, and add a reference to the SimpleClass. Choose Project  Add Reference and
switch to the Projects tab in the dialog box that appears. Click the Browse button and locate
the SimpleClass.dll ?le (see Figure 10.7). Select the name of the ?le and click OK to close the
dialog box.
The SimpleClass component will be added to the project. You can now declare a variable of the
SimpleClass.Minimal type and call its properties and methods:
Dim obj As New SimpleClass.Minimal
obj.BDate = #10/15/1992#
obj.property2 = 5544
If you want to keep testing the SimpleClass project, add the TestForm to the original project
(right-click the project’s name, choose Add  Add Existing Item, and select the TestForm in the
project’s folder). Change the project’s type back to Windows Forms Application and then change
its con?guration from Release to Debug.Petroutsos c10.tex V3 - 01/28/2008 1:32pm Page 378
Figure 10.7
Adding a reference to an
existing class to a new
Firing Events
In addition to methods and properties, classes can also ?re events. It’s possible to raise events
from within your classes, although not quite as common. Controls have many events because they
expose a visible interface and the user interacts through this interface (clicks, drags and drops, and
so on). But classes can also raise events. Class events can come from three different sources:
Progress events A class might raise an event to indicate the progress of a lengthy process
or indicate that an internal variable or property has changed value. The PercentDone event is
a typical example. A process that takes a while to complete reports its progress to the calling
application with this event, which is ?red periodically. These events, which are called progress
Time events Time events are based on a timer. They’re not very common, but you can imple-
ment alarms, job schedulers, and similar applications. You can set an alarm for a speci?c time
or an alarm that will go off after a speci?ed interval.
External events External events, such as the completion of an asynchronous operation, can
also ?re events. A class might initiate a ?le download and notify the application when the ?le
To ?re an event from within a class, you must do the following:
1. First you must declare the event and its signature in your class. The declaration must
appear in the form, not in any procedure. A simple event, with no arguments, should be
declared as follows (ShiftEnd is the name of the event— an event that signals the end of a
Public Event ShiftEnd()Petroutsos c10.tex V3 - 01/28/2008 1:32pm Page 379
2. Fire the event from within your class’s code with the RaiseEvent method:
RaiseEvent ShiftEnd()
3. That’s all as far as the class is concerned.
4. The application that uses the custom class must declare it with the WithEvents keyword.
Otherwise, it will still be able to use the class’s methods and properties, but the events
raised by the class will go unnoticed. The following statement creates an instance of the
class and listens for any event:
Dim WithEvents obj As New Minimal
5. Finally, the calling application must provide a handler for the speci?c event. Because the
class was declared with the WithEvents keyword, its name will appear in the list of objects
in the editor window and its ShiftEnd event will appear in the list of events (Figure 10.8).
Insert the code you want to handle this event in the procedure obj.ShiftEnd.
Figure 10.8
Programming a custom
class’s event
Events usually pass information to the calling application. In VB, all events pass two arguments
to the application: a reference to the object that ?red the event, and another argument (which is an
object and contains information speci?c to the event).
The arguments of an event are declared just like the arguments of a procedure. The following
statement declares an event that’s ?red when the class completes the download of a ?le. The event
passes three parameter values to the application that intercepts it:
Public Event CompletedDownload(ByVal fileURL As String,
ByVal fileName As String, ByVal fileLength As Long)
The parameters passed to the application through this event are the URL from which the ?le
was downloaded, the path of a ?le where the downloaded information was stored, and the length
of the ?le. To raise this event from within a class’s code, call the RaiseEvent statement as before,
passing three values of the appropriate type, as shown next:
RaiseEvent CompletedDownload(””,
”d:\temp\A90100.txt”, 144329)Petroutsos c10.tex V3 - 01/28/2008 1:32pm Page 380
When coding the event’s handler, you can access these arguments and use them as you wish.
Alternatively, you could create a new object, the DownloadedFileArgument type, and expose
these arguments as properties:
Public Class DownloadedFileArgument
Public FileName as String
Public TempLocation As String
Public FileSize As Long
End Class
Then you can declare the event’s signature by using the DownLoadedFileArgument type in the
argument list:
Public Event CompletedDownload(ByVal sender As Object,
ByVal e As DownloadedFileArgument)
To ?re the CompletedDownload event from within your class’s code, create an instance of
the DownLoadedFileArgument class, set its properties and then call the RaiseEvent method, as
shown here:
Dim DArgument As New DownloadedFileArgument
DArgument.FileName = ””
DArgument.TempLocation = ”d:\temp\A90100.txt”
DArgument.FileSize = 144329
RaiseEvent Fired(Me, DArgument)
To intercept this event in your test application, declare an object of the appropriate type with
the WithEvents keyword and write an event handler for the CompletedDownload event:
Public WithEvents obj As New EventFiringClass
Private Sub obj Fired(ByVal sender As Object,
ByVal e As Firing.DownloadedFileArgument)
Handles obj.CompletedDownload
MsgBox(”Event fired” & vbCrLf &
e.FileName & vbCrLf &
e.TempLocation & vbCrLf &
End Sub
That’s all it takes to ?re an event from within your custom class. In Chapter 12, ‘‘Building
Custom Windows Controls,’’ you will ?nd several examples of custom events.
Instance and Shared Methods
As you have seen in earlier chapters, some classes allow you to call some of their members without
?rst creating an instance of the class. The DateTime class, for example, exposes the IsLeapYear
method, which accepts as an argument a numeric value and returns a True/False value that indi-
cateswhether the year is a leap year. You can call thismethod through the DateTime (or Date) class
without having to create a variable of the DateTime type, as shown in the following statement:
If DateTime.IsLeapYear(1999) Then
{ process a leap year}
End IfPetroutsos c10.tex V3 - 01/28/2008 1:32pm Page 381
A typical example of classes that can be used without explicit instances is the Math class. To
calculate the logarithm of a number, you can use an expression such as this one:
The properties and methods that don’t require you to create an instance of the class before you
call them are called shared methods. Methods that must be applied to an instance of the class are
called instance methods. By default, all methods are instance methods. To create a shared method,
you must pre?x the corresponding function declaration with the Shared keyword, just like a
shared property.
Why do we need shared methods, and when should we create them? If a method doesn’t apply
to a speci?c instance of a class, make it shared. In other words, if a method doesn’t act on the
properties of the current instance of the class, it should be shared. Let’s consider the DateTime
class. The DaysInMonth method returns the number of days in the month (of a speci?c year) that
is passed to themethod as an argument. You don’t really need to create an instance of a Date object
to ?nd out the number of days in a speci?c month of a speci?c year, so the DaysInMonth method
is a shared method and can be called as follows:
DateTime.DaysInMonth(2004, 2)
Think of the DaysInMonth method this way: Do I need to create a new date to ?nd out if a
speci?c month has 30 or 31 days? If the answer is no, then the method is a candidate for a shared
The AddDays method, on the other hand, is an instance method. We have a date to which we
want to add a number of days and construct a new date. In this case, it makes sense to apply the
method to an instance of the class — the instance that represents the date to which we add the
number of days.
If you spend amoment to re?ect on shared and instancemembers, you’ll come to the conclusion
that all members could have been implemented as shared members and accept the data they
act upon as arguments. The idea behind classes, however, is to combine data with code. If you
implement a class with shared members, you lose one of the major advantages of OOP. Building
a class with shared members only is equivalent to a collection of functions, and the Math class of
the Framework is just that.
The SharedMembers sample project is a simple class that demonstrates the differences between
a shared and an instance method. Both methods do the same thing: They reverse the characters
in a string. The IReverseString method is an instance method; it reverses the current instance
of the class, which is a string. The SReverseString method is a shared method; it reverses its
argument. Listing 10.19 shows the code that implements the SharedMembersClass component.
Listing 10.19: A Class with a Shared and an InstanceMethod
Public Class SharedMembersClass
Private strProperty As String
Sub New(ByVal str As String)
strProperty = str
End Sub
Public Function IReverseString() As StringPetroutsos c10.tex V3 - 01/28/2008 1:32pm Page 382
Return (StrReverse(strProperty))
End Function
Public Shared Function SReverseString(ByVal str As String) As String
Return (StrReverse(str))
End Function
End Class
The instance method acts on the current instance of the class. This means that the class must
be initialized to a string, and this is why the New constructor requires a string argument. To test
the class, add a form to the project, make it the Startup object, and add two buttons to it. The code
behind the two buttons is shown next:
Private Sub Button1 Click(...) Handles Button1.Click
Dim obj As New SharedMembersClass(testString)
End Sub
Private Sub Button2 Click(...) Handles Button2.Click
End Sub
The code behind the ?rst button creates a new instance of the SharedMembersClass and calls
its IReverseString method. The second button calls the SReverseString method through the
class’s name and passes the string to be reversed as an argument to the method.
A class can also expose shared properties. There are situations in which you want all instances
of a class to see the same property value. Let’s say you want to keep track of the users currently
accessing your class. You can declare a method that must be called to enable the class, and this
method signals that another user has requested your class. This method could establish a connec-
tion to a database or open a ?le. We’ll call it the Connect method. Every time an application calls
the Connect method, you can increase an internal variable by one. Likewise, every time an appli-
cation calls the Disconnect method, the same internal variable is decreased by one. This internal
variable can’t be private because it will be initialized to zero with each new instance of the class.
You need a variable that is common to all instances of the class. Such a variable should be declared
with the Shared keyword.
Let’s add a shared variable to our Minimal class. We’ll call it LoggedUsers, and it will be
read-only. Its value is reported via the Users property, and only the Connect and Disconnect
methods can change its value. Listing 10.20 is the code you must add to the Minimal class to
implement a shared property.
Listing 10.20: Implementing a Shared Property
Shared LoggedUsers As Integer
ReadOnly Property Users() As Integer
Users = LoggedUsersPetroutsos c10.tex V3 - 01/28/2008 1:32pm Page 383
End Get
End Property
Public Function Connect() As Integer
LoggedUsers = LoggedUsers + 1
{ your own code here}
End Function
Public Function Disconnect() As Integer
If LoggedUsers > 1 Then
LoggedUsers = LoggedUsers - 1
End If
{ your own code here}
End Function
To test the shared variable, add a new button to the form and enter the code in Listing 10.21
in its Click event handler. (The lines with the bold numbers are the values reported by the class;
they’re not part of the listing.)
Listing 10.21: Testing the LoggedUsers Shared Property
Protected Sub Button5 Click(ByVal sender As Object,
ByVal e As System.EventArgs)
Dim obj1 As New SharedMemberClass
Dim obj2 As New SharedMemberClass
End Sub
If you run the application, you’ll see the values displayed under each Debug.WriteLine state-
ment in the Output window. As you can see, both the obj1 and obj2 variables access the same
value of the Users property. Shared variables are commonly used in classes that run on a server
and servicemultiple applications. In effect, they’re the class’s global variables, which can be shared
among all the instances of a class. You can use shared variables to keep track of the total number of
rows accessed by all users of the class in a database, connection time, and other similar quantities.Petroutsos c10.tex V3 - 01/28/2008 1:32pm Page 384
A‘‘Real’’ Class
This section covers a more-practical class that exposes three methods for manipulating strings.
I have used these methods in many projects, and I’m sure many readers will have good use for
them — at least one of them. The ?rst two methods are the ExtractPathName and ExtractFile-
Name methods, which extract the ?lename and pathname from a full ?lename. If the full name of
a ?le is C:\Documents\Recipes\Chinese\Won Ton.txt,the ExtractPathName method will re-
turn the substring C:\Documents\Recipes\Chinese\,andthe ExtractFileName method will
return the substring Won Ton.txt.
You can use the Split method of the String class to extract all the parts of a delimited string.
Extracting the pathname and ?lename of a complete ?lename is so common in programming that
it’s a good idea to implement the corresponding functions as methods in a custom class. You can
also use the Path object, which exposes a similar functionality. (The Path object is discussed in
Chapter 15, ‘‘Accessing Folders and Files.’’)
The thirdmethod,which is called Num2String, converts numeric values (amounts) to the equiv-
alent strings. For example, it can convert the amount $12,544 to the string Twelve Thousand, Five
Hundred And Forty Four dollars. No other class in the Framework provides this functionality,
and any program that prints checks can use this class.
Parsing a Filename
Let’s start with the two methods that parse a complete ?lename. These methods are implemented
as Public functions, and they’re quite simple. Start a new project, rename the form to TestForm,
and add a Class to the project. Name the class and the project StringTools. Then enter the code of
Listing 10.22 in the Class module.
Listing 10.22: The ExtractFileName and ExtractPathNameMethods
Public Function ExtractFileName(ByVal PathFileName As String) As String
Dim delimiterPosition As Integer
delimiterPosition = PathFileName.LastIndexOf(”\”)
If delimiterPosition > 0 Then
Return PathFileName.Substring(delimiterPosition + 1)
Return PathFileName
End If
End Function
Public Function ExtractPathName(ByVal PathFileName As String) As String
Dim delimiterPosition As Integer
delimiterPosition = PathFileName.LastIndexOf(”\”)
If delimiterPosition > 0 Then
Return PathFileName.Substring(0, delimiterPosition)
Return ””
End If
End FunctionPetroutsos c10.tex V3 - 01/28/2008 1:32pm Page 385
A ‘‘REAL’’ CLASS 385
These are two simple functions that parse the string passed as an argument. If the string
contains no delimiter, it’s assumed that the entire argument is just a ?lename.
Converting Numbers to Strings
The Num2String method is far more complicated, but if you can implement it as a regular func-
tion, it doesn’t take any more effort to turn it into a method. The listing of Num2String is shown in
Listing 10.23. First, it formats the billions in the value (if the value is that large); then it formats
the millions, thousands, units, and ?nally the decimal part, which can contain no more than two
Listing 10.23: Converting Numbers to Strings
Public Function Num2String(ByVal number As Decimal) As String
Dim biln As Decimal, miln As Decimal,
thou As Decimal, hund As Decimal
Dim ten As Integer, units As Integer
Dim strNumber As String
If number > 999999999999.99 Then
Return (”***”)
Exit Function
End If
biln = Math.Floor(number / 1000000000)
If biln > 0 Then
strNumber = FormatNum(biln) & ” Billion” & Pad()
miln = Math.Floor((number - biln * 1000000000) / 1000000)
If miln > 0 Then
strNumber = strNumber & FormatNum(miln) & ” Million” & Pad()
thou = Math.Floor((number - biln * 1000000000 - miln * 1000000) / 1000)
If thou > 0 Then
strNumber = strNumber & FormatNum(thou) & ” Thousand” & Pad()
hund = Math.Floor(number - biln * 1000000000 - miln * 1000000 - thou * 1000)
If hund > 0 Then strNumber = strNumber & FormatNum(hund)
If Right(strNumber, 1) = ”,” Then
strNumber = Left(strNumber, Len(strNumber) - 1)
If Left(strNumber, 1) = ”,” Then
strNumber = Right(strNumber, Len(strNumber) - 1)
If number <> Math.Floor(number) Then
strNumber = strNumber &
FormatDecimal(CInt((number - Int(number)) * 100))
strNumber = strNumber & ” dollars”
End If
Return (Delimit(SetCase(strNumber)))
End Function
Each group of three digits (million, thousand, and so on) is formatted by the FormatNum() func-
tion. Then the appropriate string is appended (Million, Thousand,andsoon).The FormatNum()
function, which converts a numeric value less than 1,000 to the equivalent string, is shown in
Listing 10.24.Petroutsos c10.tex V3 - 01/28/2008 1:32pm Page 386
Listing 10.24: The FormatNum() Function
Private Function FormatNum(ByVal num As Decimal) As String
Dim digit100 As Decimal, digit10 As Decimal, digit1 As Decimal
Dim strNum As String
digit100 = Math.Floor(num / 100)
If digit100 > 0 Then strNum = Format100(digit100)
digit10 = Math.Floor((num - digit100 * 100))
If digit10 > 0 Then
If strNum <> ”” Then
strNum = strNum & ” And ” & Format10(digit10)
strNum = Format10(digit10)
End If
End If
Retutn (strNum)
End Function
The FormatNum() function formats a three-digit number as a string. To do so, it calls the For-
mat100() function to format the hundreds, and the Format10() function formats the tens. The
Format10() function calls the Format1() function to format the units. I will not show the code for
these functions; you can ?nd it in the StringTools project. You’d probably use similar functions to
implement the Num2String method as a function. Instead, I will focus on a few peripheral issues,
such as the enumerations used by the class as property values.
To make the Num2String method more ?exible, the class exposes the Case, Delimiter,and
Padding properties. The Case property determines the case of the characters in the string returned
by the method. The Delimiter property speci?es the special characters that should appear before
and after the string. Finally, the Padding property speci?es the character that will appear between
groups of digits. The values each of these properties can take on are members of the appropriate
PaddingEnum DelimiterEnum CaseEnum
paddingCommas delimiterNone caseCaps
paddingSpaces delimiterAsterisk caseLower
paddingDashes delimiter3Asterisks caseUpper
The values under each property name are implemented as enumerations, and you need not
memorize their names. As you enter the name of the property followed by the equal sign, the
appropriate list of values will pop up, and you can select the desired member. Listing 10.25
presents the UseCaseEnum enumeration and the implementation of the UseCase property.
Listing 10.25: The CaseEnum Enumeration and the UseCase Property
Enum CaseEnum
caseCapsPetroutsos c10.tex V3 - 01/28/2008 1:32pm Page 387
A ‘‘REAL’’ CLASS 387
End Enum
Private varUseCase As CaseEnum
Public Property [Case]() As CaseEnum
Return (varUseCase)
End Get
varUseCase = Value
End Set
End Property
Notice that the name of the Case property is enclosed in square brackets. This is necessary
when you’re using a reserved keyword as a variable, property, method, or enumeration member
name. Alternatively, you can use a different name for the property to avoid the con?ict altogether.
After the declaration of the enumeration and the Property procedure are in place, the coding of the
rest of the class is simpli?ed a great deal. The Num2String() function, for example, calls the Pad()
method after each three-digit group. The separator is speci?ed by the UseDelimiter property,
whosetypeis clsPadding.The Pad() function uses the members of the UsePaddingEnum enumer-
ation to make the code easier to read. As soon as you enter the Case keyword, the list of values
that can be used in the Select Case statement will appear automatically, and you can select the
desired member. Here’s the code of the Pad() function:
Private Function Pad() As String
Select Case varUsePadding
Case PaddingEnum.paddingSpaces : Return (””)
Case PaddingEnum.paddingDashes : Return (”-”)
Case PaddingEnum.paddingCommas : Return (”,”)
End Select
End Function
To test the StringTools class, create a test form like the one shown in Figure 10.9. Then enter the
code from Listing 10.26 in the Click event handler of the two buttons.
Listing 10.26: Testing the StringTools Class
Protected Sub Button1 Click(...) Handles Button1.Click
TextBox1.Text = Convert.ToDecimal(
Dim objStrTools As New StringTools()
objStrTools.Case = StringTools.CaseEnum.CaseCaps
objStrTools.Delimiter = StringTools.DelimitEnum.DelimiterNone
objStrTools.Padding = StringTools.PaddingEnum.PaddingCommas
TextBox2.Text = objStrTools.Num2String(Convert.ToDecimal(TextBox1.Text))
End Sub
Protected Sub Button2 Click(...) Handles Button2.ClickPetroutsos c10.tex V3 - 01/28/2008 1:32pm Page 388
Dim objStrTools As New StringTools()
Dim fName as String
fName = OpenFileDialog1.FileName
End Sub
Figure 10.9
The test form of the
StringTools class
In this section you’ll learn about an interesting (but quite optional) feature of class design: how to
customize the usual operators. Some operators in Visual Basic act differently on various types of
data. The addition operator ( + ) is the most typical example. When used with numbers, the addi-
tion operator adds them. When used with strings, however, it concatenates the strings. The same
operator can perform even more complicated calculations with the more-elaborate data types.
When you add two variables of the TimeSpan type, the addition operator adds their durations
and returns a new TimeSpan object. If you execute the following statements, the value 3882 will
be printed in the Output window (the number of seconds in a time span of 1 hour, 4 minutes, and
42 seconds):
Dim TS1 As New TimeSpan(1, 0, 30)
Dim TS2 As New TimeSpan(0, 4, 12)
Debug.WriteLine((TS1 + TS2).TotalSeconds.ToString)
The TimeSpan class is discussed in detail in Chapter 13, ‘‘Handling Strings, Characters, and
Dates,’’ but for the purposes of the preceding example, all you need to know is that variable TS1
represents a time span of 1 hour and 30 seconds, while TS2 represents a time span of 4 minutes
and 12 seconds. Their sum is a new time span of 1 hour, 4 minutes and 42 seconds. So far you saw
how to overload methods and how the overloaded forms of a method can simplify development.
Sometimes it makes sense to alter the default function of an operator. Let’s say you designed a
class for representing lengths in meters and centimeters, something like the following:
Dim MU As New MetricUnits
MU.Meters = 1
MU.Centimeters = 78Petroutsos c10.tex V3 - 01/28/2008 1:32pm Page 389
The MetricUnits class allows you to specify lengths as an integer number of meters and cen-
timeters (presumably, you don’t need any more accuracy). The most common operation you’ll
perform with this class is to add and subtract lengths. However, you can’t directly add two objects
of the MetricUnits type by using a statement such as this:
TotalLength = MU1 + MU2
Wouldn’t it be nice if you could add two custom objects by using the addition operator? For
this to happen, you should be able to overload the addition operator, just as you can overload a
method. Indeed, it’s possible to overload an operator for your custom classes and write statements
like the preceding one. Let’s design a class to express lengths in metric and English units, and then
overload the basic operators for this class.
To overload an operator, you must create an Operator procedure, which is basically a function
with an odd name: the name of the operator you want to overload. The Operator procedure
accepts as arguments two values of the custom type (the type for which you’re overloading the
operator) and returns a value of the same type. Here’s the outline of an Operator procedure that
overloads the addition operator:
Public Shared Operator + (
ByVal length1 As MetricUnits,
ByVal length2 As MetricUnits) As MetricUnits
End Operator
The procedure’s body contains the statements that add the two arguments as units of length,
not as numeric values. Overloading operators is a straightforward process that can help you create
elegant classes that can be manipulated with the common operators.
VB 2008 at Work: The LengthUnits Class
To demonstrate the overloading of common operators, I included the LengthUnits project, which
is a simple class for representing distances in English and metric units. Listing 10.27 shows the
de?nition of the MetricUnits class, which represents lengths in meters and centimeters.
Listing 10.27: TheMetricUnits Class
Public Class MetricUnits
Private Meters As Integer
Private Centimeters As Integer
Public Sub New()
End Sub
Public Sub New(ByVal meters As Integer, ByVal centimeters As Integer)
Me.Meters = meters
Me.Centimeters = centimeters
End Sub
Public Property Meters() As IntegerPetroutsos c10.tex V3 - 01/28/2008 1:32pm Page 390 

Make a Free Website with Yola.