Platinum Edition Using Visual Basic 5

Previous chapterNext chapterContents


- 18 -
Introduction to Classes

Classes are used to defi=ne reusable objects in your programs. A class contains data and procedures for manipulating the data.
The Visual Basic controls that you use are classes with a visual interface. These classes contain properties, methods, and events.
Although the classes in Visual Basic are not 100 percent object-oriented, they do provide for encapsulation of data and methods and make it easier to create reusable code.
You will see how to create a class from scratch, and how to use the Class Builder to create new classes from existing ones.
After a class has been defined, you still have to create an instance of the class to be able to use it. You will see how this is done.

The controls that you use in your Visual Basic programs are actually classes from which you create instances on a form. These classes are very useful because they are reusable and relatively self-contained. Starting with Visual Basic version 4, you can create your own classes in code to help make your programs more efficient and maintainable. Visual Basic version 5 has enhanced the use of classes even further.

Introduction to Classes

Programmers have always been concerned with efficiency in their programs. A programmer wants the program to run as quickly as possible, create the smallest possible executable file, and be as easy as possible to create and maintain. This last aspect of efficiency leads to using reusable components--pieces of code that can be used over and over in multiple projects. Most veteran programmers have a library of subroutines and functions that have been developed over time to handle various tasks. These components are then added to a new project as needed.

Since its inception, Visual Basic has supported the notion of reusable components. The controls you use to build the user interface of your programs are components that perform specific functions, such as getting a piece of text input or displaying a picture. These controls contain all the functions necessary to accomplish their tasks. This means that you do not have to add the code for these tasks to each of your programs. The controls are one form of object that can be used in your code. Visual Basic also has supported the use of object linking and embedding (OLE), which lets you access other programs to perform specific tasks.

Use of controls and OLE has greatly benefited programmers, but until version 4, one thing was missing: the ability to create your own objects. Visual Basic now lets you create your own objects through the use of class modules. A class module is very similar to a standard code module in your project. The difference is that instead of just containing a bunch of code, a class module consists of the code that defines a particular class. After the class is defined, you can create objects that are instances of the class, similar to the way you create instances of a TextBox control. In this chapter, you learn about what is in a class module and how to use them in your programs.

Understanding Object-Oriented Programming Fundamentals

You have probably heard the term object-oriented programming (OOP) or read about it in programming books and magazines. Even with the addition of the capability to create classes, Visual Basic is not a fully object-oriented programming language. To understand what Visual Basic can and can't do, you need to start with a review of some OOP basics.

OOP's key element is its use of reusable objects to build programs. These objects must be capable of supporting the following:

Visual Basic, strictly speaking, does not support the concept of inheritance. That is, you can't create a new class based on the definition of another class. However, there are ways around this limitation, such as just copying your class definition from one module to another.

Implementing OOP with Classes in Visual Basic

The classes that you can build in Visual Basic let you encapsulate the data and functions of an object. The classes let you define properties (the information or data in the object) and methods (what the object does with the information). But, as with the controls in Visual Basic, you can't use inheritance to create a new class from an existing one.

Even with this limitation, Visual Basic classes are powerful programming tools, as you will see in the rest of this chapter.

Using Classes in Your Programs

You can use classes in your Visual Basic programs in several ways. Each way provides you with programming capabilities that did not exist in Visual Basic prior to version 4. Visual Basic 5 has further enhanced the programmer's ability to work with classes.

First, you can use classes to encapsulate program segments to make them more easily reusable. The classes you create define objects that can be created anywhere in your program. The advantage of using the objects to handle certain program functions is that it reduces the need for global variables and procedures in your programs. This is helpful because global variables are one of the most frequent causes of program errors, as well as one of the more difficult errors to trace. The classes that you define also can be added to other program projects to handle the same functions. Used this way, you can build up a library of useful classes for your development work. You can also create multiple instances of a class, with each object (instance) having its own set of property values. This modularity is something that can't be accomplished with global variables or routines.

The second use of classes is in building ActiveX DLLs and EXEs. Used in this way, the objects of a class provide functions to any of your programs, as well as to other programs that can serve as an ActiveX client. You can, for example, put all of your business financial rules in an object and compile it as an ActiveX DLL. By referencing the DLL, other programmers can easily incorporate the rules into their own programs. You can also separate the database-access functions into a separate class. This separation of tasks is the foundation of the three-tier client/server model.

Finally, you can use classes to build Visual Basic add-ins. These programs actually let you enhance the Visual Basic development environment itself. Add-ins can be used to build program wizards, such as the Data Form Designer; provide supplemental programs, such as the Visual Data Manager; or provide tools for making your programming easier, such as a routine to automatically reset the tab order of all the controls on a form.

Building a Class in Visual Basic

Classes in Visual Basic are developed by using the Class Module. This module contains only variable declarations and procedure code. There is no user-interface component of a class module. However, a class can take action using a form that is in the program. In the case of an ActiveX DLL or EXE, the forms can be included with the project.


NOTE: You can add properties to forms and code modules using the same principals as those for adding classes.

A class module is a fairly simple program object. A class module has only three built-in properties and two native events. It has no methods of its own. After a class module is created, you can add properties, methods, and events to the class by declaring variables and by programming procedures and functions in the class module.

Creating a New Class Module

You start the process of creating a new class module by selecting Add Class Module from Visual Basic's Project menu. This starts a new class module with the default name of Class1 and opens the Code window for the class, as shown in Figure 18.1.

FIG. 18.1
When creating a new class module, you use the Code window the same way you did when creating a standard module.

After the new class is created for your project, you need to set the values of some key properties that define the class. These properties are Instancing and Name.

Choosing a Name You want to give each of your classes a unique name that is descriptive of the function it will perform. In addition, many developers like to preface the class name with the letter c to indicate in the programs that this is the name of a class. Using this convention, a class that provided improved printer functionality might be named cPrinter.

Creating a Public Class When you add a class module to a Standard EXE project, your class module can be used from other modules or forms in the current project only. It cannot be seen from another program. In other words, it is a private class. To create a public class that can be made available to other programs, you need to use either an ActiveX EXE or an ActiveX DLL project. After it is created, you then register your DLL (or EXE) with Windows. Your ActiveX project then shows up in the References dialog box of Visual Basic. To use the class, you simply add a reference to it in another project.

The way instances of your class are created depends on whether you created a DLL or EXE, as well as the setting of the Instancing property, described in the next section. An ActiveX EXE can be executed like a standard program, but in general is less efficient than a DLL.


NOTE: In Visual Basic version 5.0, ActiveX EXEs and DLLs replace the concept of OLE Automation servers. These new types of projects can still be thought of as servers, because they "serve" up objects to client programs.

Setting the Instancing Property The final property of the class module is the Instancing property. The Instancing property defines the way instances of your class are created. The valid property values vary depending on what type of VB project you are working with. The Instancing property should only concern you if you plan on creating a public class module. If you are working in a Standard EXE project, the Instancing property is not even available. While learning the basics of classes and OOP, you can just forget about this property and use a Standard EXE project. However, this property is usually set during the creation of a class, so I mention the property values here. Table 18.1 describes the possible settings for the Instancing property.

Table 18.1 Controlling Access to Your Class with Instancing Property

Value Name Description
1 Private The class cannot be accessed outside the current project.
2 Public Not Creatable Instances of the class can be created only within the project that defines the class. However, other applications can control the class after it is created.
3 SingleUse Other applications can create instances of the class. However, each time another instance of the class is created, a new copy of the program containing the class is started. (This setting is not available for an ActiveX DLL project.)
4 Global SingleUse Like SingleUse, but makes the class act like a global variable in the client program.
5 MultiUse Other applications can create any number of instances of the class, and only one copy of the program containing the class is started. ActiveX DLL projects generally use this setting.
6 Global MultiUse Like MultiUse, but makes the class act like a global variable in the client program.

The Initialize and Terminate Events In addition to the internal properties of a class module, there are two built-in events: Initialize and Terminate. When you create your class, you might want to include code in these events to cause your programs to take specific actions when an instance of the class is created and destroyed. For example, if your class needs to know the current system date, you can include a code line in the Initialize event to set an internal variable to the system date. An example of use of the Terminate event would be to close an open database that was being used by the class.

Adding Properties to the Class

After the class module has been created, you can start adding your own properties. The properties you add are the properties of the objects created from your class. Think of the class like a template for objects. Each object you create from the class can have a unique set of property values. These properties provide the way for the user of the object to get information into the object. (You can also supply information to the object when you call a method, as you will see shortly.)

There are two ways to add a property to a class:

Creating a Public Variable You create a public variable using a declaration statement with the Public keyword, as shown in the following line of code:

Public str1 As String

The declaration statements can appear in the Declarations section of the class module or in any Sub procedure in the class.


NOTE: You must explicitly declare public variables, but it is also a good practice to declare all variables. Therefore, you should require variable declaration by placing the Option Explicit statement as the first statement in the Declarations section of the class. You can require variable declaration throughout your program by checking the Require Variable Declaration check box on the Environment tab of the Options dialog box. This dialog box is accessible by choosing Options from the Tools menu.

You can create properties for your class in this manner, but it is not the method recommended by most programmers. A public variable is visible to your application as soon as the object is created. Any part of your program can change the value of the variable without performing any checks on the data. Bad data passed to the object can then cause problems with operations done by the object.

Property Procedures A better way to create properties in a class is to use the property procedures. These procedures provide the interface to the properties of the object, but they let you write code to verify that the proper data is passed to the class and perform other processing of the data. This protects the functions of your class from crashing as a result of bad data. Property procedures also provide you with the ability to create read-only properties, something that is not possible when using public variables. There are three types of property procedures available: Property Let, Property Get, and Property Set. These procedures are defined in Table 18.2.

Table 18.2 Three Property Procedures Set and Retrieve the Values of Properties

Procedure Type What It Does
Property Let Accepts the value of a property from the calling program. Used to set the value of the property.
Property Get Sends the value of the property to the calling program. Used to retrieve the value of the property.
Property Set Special case of the Let procedure. Used if the type of variable being set is an object.

To create a property procedure, you need to be in the Code window for the class with which you are working. Then choose Add Procedure from the Tools menu. This displays the Add Procedure dialog box, as shown in Figure 18.2.

FIG. 18.2
Create a property procedure by using the Add Procedure dialog box.

In the Add Procedure dialog box, enter a name for the procedure. (This name will be the name of the property for your object.) Next, choose the Property Option button on the dialog box and then click OK. This creates a Property Let and a Property Get procedure in your class module, as shown in Figure 18.3.

FIG. 18.3
Both the Property Let and Property Get procedures are created with the Insert Procedure dialog box.

Setting a Property's Value otice that in Figure 18.3, the Property Let procedure automatically adds an argument to the procedure. This argument is the value that is passed from the calling program by the property-assignment statement. You can change the name of this argument to any valid variable name. You also can--and should--define the type of the variable by using the As VarType clause of the argument. The code you place in the Property Let procedure typically takes the argument's value, performs data validation if necessary, and assigns the value to a private variable in the class module. When you use the private variable for any tasks in the class module, the information is protected from being inadvertently changed by other parts of the program. An example of a Property Let procedure follows:

Private m_lmarg As Integer
Public Property Let LMargin(LMarg As Integer)
    If LMarg < 0 Then
        m_lmarg = 0
    Else
        m_lmarg = LMarg
    End If
End Property

The preceding property procedure simply makes sure the LMargin property of a class is never less than 0. For example, consider the following incorrect use of the property:

Myobj.Lmargin = -1234

The property procedure would store a 0 in the private variable, instead of the negative value in this line of code. If you defined the property with a public variable, this type of data validation would be much harder to accomplish.

Retrieving a Property's Value The Property Get procedure lets your program retrieve the value of the property. This is done by assigning the value to be returned to the name of the property, as shown here:

Public Property Get LMargin() As Integer
    LMargin = m_lmarg
End Property

You can also place other code in the procedure when a value for the property needs to be calculated or other actions need to be taken in returning the property's value. Notice that the Property Get procedure just shown includes the As Integer clause in the first line. This is optional, but if it is included, the type should be the same as that used for the argument passed to the Property Let procedure.

You can create a read-only property for a class by including only the Property Get procedure for the property. If the Property Let procedure is excluded, there is no way for the property value to be set other than by code within the class module.

Objects Are a Special Case For some of your properties, you will want to be able to pass a database object, a control, or some other object to the class module. In this case, you must use the Property Set procedure instead of the Property Let procedure to give the property a value. When passing an object, the private variable you're using in the class module must be of the object type. This and the Property Set procedure are shown in the following code:

Private OutputTo As Object
Public Property Set OutputDev(inDev As Object)
    Set OutputTo = inDev
End Property

Notice in the code that the Set statement is used to assign the value of the object to the private variable. The Set statement is required any time you are setting the value of an object.

Methods Let the Class Take Actions

Because a class that could not perform actions would be practically useless, you need a way to create methods for your class. This is done by writing public procedures for the class. These procedures are just like the ones you write for other parts of your program. The procedures in a class can even have arguments passed to the procedures by the calling routines, just like other procedures. Passing arguments provides the final way to get information into the class. (Remember, public variables and property procedures were the other ways.)

See "Using Procedures and Functions," Chapter 17

Like all other procedures, the procedure in a class module starts with the declaration statement. If this statement uses the Public keyword, then the procedure becomes a method of the class and is accessible to any program that creates an object based on the class. If the procedure is prefaced by the Private keyword, the procedure can be called only from within the class itself. The following code shows a simple procedure in a class that prints a line of text to the printer in bold font. Because the procedure is public, it becomes a method of the class module:

Public Sub PrintBold(InText As String)
    curBold = Printer.Font.Bold
    Printer.Font.Bold = True
    Printer.Print InText
    Printer.Font.Bold = curBold
End Sub

Adding Events to Your Class

In Chapter 4, "Working with Forms and Controls," you learned what events were and how to handle them in your code. An event is triggered when the user takes an action, such as a keystroke, or when a change has taken place, such as having a specific amount of time elapsing. Your classes can also initiate events as they are running. This is a new feature of Visual Basic 5 that enhances the capability of your classes.

See "A First Look at Methods and Events," Chapter 4

To create an event in your class, you need to do two things:

1. Declare the event in the class.

2. Use the RaiseEvent statement to trigger the event.

To declare an event, you simply supply the name of the event and the variable passed by the event in a statement like the following:

Public Event QueryStatus(ByVal Completion As Single, _
ByRef Cancel As Boolean)

This statement is placed in the declarations section of the class in which you want the event. The Public keyword is necessary to allow programs using the objects created from the class to respond to the event. The variables allow the event to pass information to the program using the class and to receive information back from the program. The event declared in the preceding code could be used to keep the user informed of the status of a long query and to allow the user to cancel the query prior to completion.

After the event is declared, you can use the RaiseEvent statement to trigger the event anywhere in the code of your class. For the QueryStatus event, you might want to trigger the event after every 100 records that have been processed as shown in Listing 18.1.

Listing 18.1 TRIGGER.FRM--Triggering an Event in Your Class

Public Sub ProcessData()
Dim MaxRecords As Long, RecordsProcessed As Long
Dim blnCancel As Boolean
If ClsRset.RecordCount = 0 Then Exit Sub
ClsRset.MoveLast
MaxRecords = ClsRset.RecordCount
RecordsProcessed = 0
blnCancel = False
ClsRset.MoveFirst
Do While Not ClsRset.EOF
    If RecordsProcessed Mod 100 = 0 Then
        RaiseEvent QueryStatus(RecordsProcessed / MaxRecords, blnCancel)
        If blnCancel Then Exit Sub
    End If
    ClsRset.MoveNext
    RecordsProcessed = RecordsProcessed + 1
Loop
End Sub

To write code for an object event in your program, see the section titled "Handling an Object's Events" later in this chapter.

Creating Public Constants as Enumerations

You can't declare public constants in a public class module, so how do you make constants available to other applications through a class? You use an enumeration, or, in Visual Basic, Enum.

Public enumerations in a public class are available to other applications and viewable through the Object Browser. In the following code, which shows the definition for an enumeration, you need only assign the actual value of the first member (in this case, One). The remaining members are automatically assigned their values sequentially.

Public Enum Numbers
    One = 1
    Two = 2
    Three = 3
    Four = 4
    Five = 5
End Enum

After including this declaration in your program, you can refer to the constants One, Two, Three, Four, and Five, which will return the values 1, 2, 3, 4, and 5, respectively. Figure 18.4 shows how Enum looks in the Object Browser from another application.

FIG. 18.4
The Enum name appears in the Classes list, and the items appear in the Members list of the Object Browser.

Accessing a Class from a Program

After creating a class, you will of course want to use it in your program. In fact, you will probably want to use it in several programs. After all, this is why programmers create reusable components in the first place.

Using a class is relatively easy, but it's a little different than using other program pieces with which you might be familiar. The main thing to remember is that a class cannot be used by itself. You must create an object from the class and then manipulate the object by setting its properties and using its methods. The creation of the object is the key part using the object, because you are already familiar with setting properties and using methods of other objects such as controls.

Creating the Object There are two ways to create an object from a class that you have developed--using a declaration statement or using a Set statement. After the object is created using either of these ways, you have access to its properties and methods and can use the object in your program. When the object is created using either method, the code in the Initialize event of the class module is run for the object.

Using a Declaration Statement The first way to create an object from a class is to do it directly, with a declaration statement such as the following:

Dim EhnPrint As New cPrint

The declaration defines an object variable as being a new instance of the class (in this case, the cPrint class, shown in earlier code segments). Notice the use of the New keyword in the declaration. This keyword is required when you create an instance of an object.

Using the Set Statement The second way to create an object from a class is by using the Set statement. In this case, an object variable is declared, and then the Set statement is later used to create the instance of the object. The following code illustrates this method of creating an object:

Dim EhnPrint As Object
Set EhnPrint = New cPrint

Again, after the Set statement is used to create the object variable, the object's properties and methods are available to your program. The only difference from the previous method is that with this method, the object is not actually created until the Set statement is used. This is known as late binding, because the object type is not set until the Set statement executes. The preceding code can also be written as follows:

Dim EhnPrint As cPrint
Set EhnPrint = New cPrint

In this case, the type of object to be created is defined in the declaration statement, but because the New keyword is not present in the statement, the object is not actually created until the Set statement is issued.

If the object you are creating has events associated with it, you need to use the Set method to create the object. You also need to add one more keyword to the variable declaration statement. This is the WithEvents keyword. This keyword tells your code that the object will contain custom events that are available to be handled by your program. The use of the WithEvents keyword is shown in the following statement:

Private WithEvents mDataProc As DataProc

Setting and Retrieving the Property Values After the object has been created, you have access to its properties. You can set and retrieve values of the properties in your code using assignment statements, just as you would for the properties of a control. For example, the following code sets the left margin of the EhnPrint object:

EhnPrint.LMargin = 500

Because I previously defined LMargin in the class using a property procedure, this assignment statement causes the Property Let procedure of the class to be run. Similarly, to retrieve the value of the left-margin property, you use a statement like the following:

curmarg = EhnPrint.LMargin

As with properties of controls, you need to make sure that variables and literal values used in accessing the object's properties are of the same type as was defined for the property. Also, if you are going to be retrieving values of the property, it is a good idea to set a default value in the Initialize event of the class module.

When the property of an object is itself an object, you cannot use a standard assignment statement to set or retrieve the value. In this case, you must use the Set statement. The following code shows how the value of the OutputDev property of the EhnPrint object is set and retrieved:

`Setting the value of the property
Set EhnPrint.OutputDev = Printer
`Retrieving the value of the property
Set objvar = EhnPrint.OutputDev

As with any other properties, the variable or literal used in assigning or retrieving a property value must be of the same type as the property. In this case, the objvar variable must be defined as an object variable.

Using the Methods Using the methods of the objects you create is like using the methods of Visual Basic's internal objects and controls. To execute a method, you supply the name of the object and the name of the method and provide any values that are required by the method as arguments. For the methods of your class, these arguments are defined in the argument list of the class module's procedure. The following line of code runs the Output method of the EhnPrint object:

EhnPrint.Output "This is a test."

Handling an Object's Events Handling the events of an object you create is the same as handling the events of a form, control, or other object in your program. Basically, you write code for the events you want to handle and don't write code for the events you want to ignore. After you have declared an object using the WithEvents keyword, the object is added to the list of objects in the Code window of your program. To handle an object's events, you select the object from the left-hand drop-down list, select its event from the right-hand drop-down list, and then write code for the event.

Getting Rid of an Object After you've finished using an object in your program, you want to get rid of it to recover the resources that it was using. You accomplish this by literally setting the object to nothing, as shown in the following code:

Set EhnPrint = Nothing

Note that an object created within a form or a procedure is usually released when the procedure terminates or when the form is unloaded. However, it's good practice to release the object when you're finished with it. When the object is released, any code that was in the Terminate event of the class is run.

Using the Class Builder

When you create a ew class, Visual Basic presents the option of creating an empty class or starting the Class Builder (see Figure 18.5).

FIG. 18.5
Double-clicking Class Builder starts a tool to help you organize classes in your application.

The Class Builder can be used to generate outlines for new classes or modify and reorganize existing classes. Figure 18.6 shows the Class Builder with the Math class loaded.

FIG. 18.6
The Class Builder provides a graphic interface to the classes, properties, methods, constants, and events in your application.

To add an item to your project, click one of the toolbar buttons; the Class Builder displays a dialog box where you can enter all the attributes of the new item. Figure 18.7 shows the dialog box presented for a new method.

FIG. 18.7
The Method Builder dialog box presents all options available for a method.

When you close the Class Builder, it updates your project with any changes or additions that you entered. You still have to fill in the working code, but the Class Builder generates the appropriate declarations and supporting code.

Creating Classes that Contain Collections

A collection is a group of objects that is itself a type of object. Visual Basic has two built-in collection objects: the Forms collection and the Controls collection. You can use collections with the For Each...Next statement to perform actions on all the objects they contain. The MinimizeAll procedure, shown in Listing 18.2, minimizes each loaded form in an application.

Listing 18.2 MINIMIZE.FRM--Using the For Each Structure to Handle All the Objects in a Collection

` Minimizes all loaded forms.
Sub MinimizeAll()
    Dim frmElement As Form
    ` For each loaded form.
    For Each frmElement In Forms
        ` Minimize the form.
        frmElement.WindowState = vbMinimized
   Next frmElement
End Sub

Collections solve three problems faced by most programmers when working with objects:

The following sections describe each of these aspects of using collections when creating object-oriented applications in Visual Basic.

Standard Collection Properties and Methods

Collections share a common set of properties and methods. Some collections might have additional properties and methods, but all collections have at least the set described in Table 18.3.

Table 18.3 Properties and Methods Common to All Collections

Item Purpose
Count property Finds the number of objects in a collection
Item method Gets a single object from a collection

In addition to the items in Table 18.3, collections usually provide two more methods. The methods in Table 18.4 are common to most collections.

Table 18.4 Methods Common to Most Collections

Method Purpose
Add Adds an object to a collection
Remove Deletes an object from a collection

The Add and Remove methods provide programmers a standard way to create and delete items in a collection. The Visual Basic Forms and Controls collections are maintained by Visual Basic, so they don't support these methods. Add and Remove are very common in object libraries, such as those provided by Microsoft Excel and Project.

Creating a New Collection for Grouped Actions

You can create ew collections to contain forms, controls, and ActiveX objects. Use the Collection object data type when creating a new collection. The following declaration creates a new collection named colSelected:

Dim colSelected As New Collection

Declaring a variable as a Collection object gives you four built-in properties and methods, as shown in Table 18.5.

Table 18.5 Collection Object Built-In Properties and Methods

Item Purpose
Count property Returns the number of objects in the collection
Add method Adds an object to the collection
Item method Gets a single object from the collection
Remove method Deletes an object from the collection

The code in Listing 18.3 creates a new collection named colTextBoxes and adds all the text boxes on a form to the new collection.

Listing 18.3 CREATECOL.FRM--Creating the colTextBoxes Collection

Option Explicit
` Create a new collection to contain all the
` text boxes on a form
Dim colTextBoxes As New Collection
Private Sub Form_Initialize()
    ` Variable used in For Each to get controls.
    Dim cntrlItem As Control
    ` Loop through the controls on the form.
    For Each cntrlItem In Me.Controls
        ` If the control is a text box, add it to the
        ` collection of text boxes.
        If TypeName(cntrlItem) = "TextBox" Then
            colTextBoxes.Add cntrlItem
        End If
   Next cntrlItem
End Sub

The code in Listing 18.4 uses the collection colTextBoxes to clear all the text entered on the form:

Listing 18.4 Using For Each to Handle the Collection

Sub cmdClear_Click()
    ` Variable used in For Each to get controls.
    Dim cntrlItem As Control
    ` Clear each of the text boxes in the collection.
    For Each cntrlItem In colTextBoxes
        cntrlItem.Text = ""
   Next cntrlItem
End Sub

Using Collections to Organize Objects

Object hierarchies are necessary when an application defines a large number of classes that relate to each other. The hierarchy defines an understandable way for users to choose from among the many objects. You use collections to create a hierarchical organization of classes. The Excel object library is a good example of a large class hierarchy. You can use Excel collections to find individual objects. For example, the following line makes a cell in a worksheet boldfaced:

Application.Workbooks("stock.xls").Sheets("Portfolio).Range(1,1).Font = xlBold

Table 18.6 describes the action taken by each method or property in the preceding line of code.

Table 18.6 Description of Items in the Excel Boldface Object Example

Item Purpose
Application Returns the top-level object in Excel.
Workbooks The Application object's Workbooks method returns the collection of all loaded workbooks in Excel.
("stock.xls") The default method for the Workbooks collection is the Item method, so "stock.xls" returns the STOCK.XLS Workbook object within the Workbooks collection. An index number would also work here.
Sheets The Workbook object's Sheets method returns a collection of all the sheets in a workbook. This includes worksheets, dialog sheets, chart sheets, and so on.
("Portfolio") Again, the implicit Item method returns a single Sheet object from within the Sheets collection.
Range The Worksheet object's Range method returns the collection of cells on a worksheet.
(1,1) The Range object's Item method returns a single cell from the Range collection.
Font = xlBold The Range object's Font property sets the cell's font to appear bold.


NOTE: Before you can use Excel's objects from Visual Basic, you need to add a reference to Excel's object library.

There are a few important points to notice about the example shown in the preceding Excel object code sample and Table 18.6:


NOTE: For more information about collections, refer to Chapter 10, "Using the Windows Common Controls."

Classes in Your Programs

Now that I've covered the basics of developing and using a class module, take a look at a couple of examples of using class modules in actual programs. These examples are relatively simple, but they demonstrate the power of classes. You can enhance these examples to provide powerful tools for your own programs.

A Better Printer

One of the biggest problems with sending data to the printer is that you don't know how long the line of print will be when you send it, and the printer itself isn't smart enough to perform word wrapping. The cPrint class shows a way to handle this problem by providing you with an object that does word wrapping. The object also provides properties that let you set the desired margins for the output. As an added bonus, the object lets you choose where to print the output of the code--to the printer, a form, or a picture box. Figure 18.8 shows the form used to run the example program.

The form has a text box for you to enter the string to be printed, as well as a picture box that is one of the output choices. In addition, three command buttons are placed on the form to let you determine where to direct the output. Also, text boxes and a command button are used to set new margin values for the output device. The code for this program is located in the CLASSEX.VBP file.

Setting Up the Class The first step in setting up the class for the enhanced printer object is to create the class module. You can do this by choosing Class Module from the Insert menu. You then give the class module the name cPrint by setting the Name property. The Public property of the class module is set to False, because this class is used only inside the current program. At this point, all the properties of the class module have been set, and you're ready to input code for the class.

FIG. 18.8
The form pictured here is an interface used to test our sample print class.

Defining the Internal Variables The next step is to define the internal variables that will be used by the class module to store the values of the module's properties. These variables are declared as private, so that they cannot be directly manipulated by the calling program. The code in Listing 18.5 shows the variable declaration for the cPrint class.

Listing 18.5 PRNTDEC.FRM--Declaring the Private Variables of the Class

Option Explicit
Private OutputTo As Object
Private m_lmarg As Integer, m_rmarg As Integer
Private m_tmarg As Integer, m_bmarg As Integer
Private objwid As Integer, objhit As Integer
Private txtht As Integer, endpos As Integer, txtlen As Integer
Private strtpos As Integer, endps2 As Integer
Private prntln As String

The first line of the code, the Option Explicit statement, indicates that all variables must be declared prior to their use in the code. The next three lines declare the variables that will be used with the object's properties. The OutputTo variable contains the object that is the destination of the printout. This variable is set to another object, such as the printer or the name of a form or picture box. The four variables ending in marg will contain the values of the page margins input by the user. The other declaration statements set up the other variables that are used in the processing of the printout.

Creating the Properties The cPrint class has an interface that lets the user set the desired output device and the page margins for the printout. This interface consists of the properties defined for the class. These properties are defined by the Property Let, Property Get, and Property Set procedures of the class. Listing 18.6 shows the procedures for the four margin properties and the output-device property.

Listing 18.6 PRNTPROP.FRM--Properties of a Class Are Defined by Property Procedures

Public Property Get LMargin() As Integer
    LMargin = m_lmarg
End Property
Public Property Let LMargin(LMarg As Integer)
    If LMarg < 0 Then
        m_lmarg = 0
    Else
        m_lmarg = LMarg
    End If
    objwid = OutputTo.Width - m_lmarg - m_rmarg
End Property
Public Property Get RMargin() As Integer
    RMargin = m_rmarg
End Property
Public Property Let RMargin(RMarg As Integer)
    If RMarg < 0 Then
        m_rmarg = 0
    Else
        m_rmarg = RMarg
    End If
    objwid = OutputTo.Width - m_lmarg - m_rmarg
End Property
Public Property Get TMargin() As Integer
    TMargin = m_tmarg
End Property
Public Property Let TMargin(TMarg As Integer)
    If TMarg < 0 Then
        m_tmarg = 0
    Else
        m_tmarg = TMarg
    End If
    objhit = OutputTo.Height - m_tmarg - m_bmarg
End Property
Public Property Get BMargin() As Integer
    BMargin = m_bmarg
End Property
Public Property Let BMargin(BMarg As Integer)
    If BMarg < 0 Then
        m_bmarg = 0
    Else
        m_bmarg = BMarg
    End If
    objhit = OutputTo.Height - m_tmarg - m_bmarg
End Property

Public Property Set OutputDev(inDev As Object)
    Set OutputTo = inDev
    objhit = OutputTo.Height - m_tmarg - m_bmarg
    objwid = OutputTo.Width - m_lmarg - m_rmarg
End Property

Notice that to set the output-device property, a Property Set procedure must be used. This is because the output device is an object.

Supplying an Output Method ext, because the object needs to be able to perform some function, a method must be created for the class. You can do this by creating a Public procedure in the class module. The method for the cPrint class performs the word-wrapping of the input text. This method is defined by the code in Listing 18.7.

Listing 18.7 PRNTWRP.FRM--A Public Procedure Defines the Output Method of the cPrint Class

Public Sub Output(prntvar As String)
txtht = OutputTo.TextHeight("AbgWq")
    Do
        endpos = 0
        txtlen = 0
        prntln = ""
        Do
            strtpos = endpos + 1
            endpos = InStr(strtpos, prntvar, " ")
            prntln = Left$(prntvar, endpos)
            txtlen = OutputTo.TextWidth(prntln)
        Loop Until txtlen > objwid Or endpos = 0
        If endpos = 0 Then
            prntln = prntvar
            endps2 = InStr(1, prntln, vbCrLf)
            If endps2 > 0 Then
                prntln = Left$(prntvar, endps2 - 1)
                prntvar = LTrim$(Mid$(prntvar, endps2 + 2))
            Else
                prntvar = ""
            End If
        Else
            prntln = Left$(prntvar, strtpos - 1)
            endps2 = InStr(1, prntln, vbCrLf)
            If endps2 > 0 Then
                prntln = Left$(prntvar, endps2 - 1)
                prntvar = LTrim$(Mid$(prntvar, endps2 + 2))
            Else
                prntvar = LTrim$(Mid$(prntvar, strtpos))
            End If
        End If
        OutputTo.CurrentX = m_lmarg
        OutputTo.Print prntln
    Loop While Len(prntvar) > 0
End Sub

Initializing the Class Finally, because the user might call the method without first setting the properties of the class, it's a good idea to set initial values for the internal variables. This is done in the Initialize event of the class, as shown in Listing 18.8.

Listing 18.8 Setting the Initial Value of Variables

Private Sub Class_Initialize()
    m_lmarg = 0
    m_rmarg = 0
    m_tmarg = 0
    m_bmarg = 0
    Set OutputTo = Printer
End Sub

Using the Class As I said earlier, to use a class in a program, you must create an instance of the object defined by the class and then set the properties of the object and use its methods. For the cPrint class example, this is all done in the form that supplies the user interface for the example code. First, the object is defined in the Declarations section of the form using a declaration statement, as shown here:

Dim EhnPrint As New cPrint

If the user chooses to set page margins for the output, values can be entered in the text boxes for the appropriate margins. The text-box values are then assigned to the properties of the object, using the code in the Click event of the Set Margins command button. This code is shown in Listing 18.9.

Listing 18.9 Setting the Margins of the Output Device

Private Sub cmdSetMargin_Click()
  EhnPrint.LMargin = Val(txtMargin(0).Text)
  EhnPrint.RMargin = Val(txtMargin(1).Text)
  EhnPrint.TMargin = Val(txtMargin(2).Text)
  EhnPrint.BMargin = Val(txtMargin(3).Text)
End Sub

As you can see, the properties are set using simple assignment statements. The Val function is used in the event that the user accidentally enters a text string instead of a number in the text box.

Finally, after the user has entered some text to be printed, the Output method of the object can be used to print the text. The following code shows how this is done to print the text to the picture box:

Private Sub cmdPicture_Click()
  Set EhnPrint.OutputDev = picPrint
  PrntStr = txtInput.Text
  EhnPrint.Output (PrntStr)
End Sub

The code first uses the Set statement to tell the EhnPrint object to direct the output to the picPrint picture box. Next, the text to be printed is retrieved from the text box. Finally, the text string is passed to the object's Output method. The results of this operation are shown in Figure 18.9.

FIG. 18.9
The cPrint class can be used to output text to different output devices.

Database Access

Another use of classes is in database access. The sample class shown in this section is used to simply open a database and return the database object to the calling program. You're probably wondering why you wouldn't just use the database objects directly to perform this operation. The answer is that using a class lets you encapsulate the OpenDatabase method and all the associated error-handling code that is required for it.

By using a class, you don't have to repeat this code multiple places in your program or in multiple programs. You simply create it once in a class module and then create an instance of the class any time that you need to open the database in your program. You can also easily create an ActiveX DLL from the class module, which keeps you from having to add the class module to other programs. The final advantage is that if you find additional things that you need your open database routine to handle, you have to change the code in only one place--the class module. Then all your programs have the benefit of the changes. The example case is contained in the file CLSDBEX.VBP.

The cDataAccess Class The cDataAccess class is fairly simple. The class consists of one method and one read-only property. To use the class, the name of a database is passed to the OpenDb method, and then the database object is retrieved using the OpenData property. The code for the class is shown in Listing 18.10.

Listing 18.10 DATAACC.FRM--The cDataAccess Class Property and Method

Private m_ClsDb As Database
Public Property Get OpenData() As Database
Set OpenData = m_ClsDb
End Property
Public Sub OpenDb(dbName As String)
On Error GoTo DBErrHandle
Set m_ClsDb = DBEngine.Workspaces(0).OpenDatabase(dbName, _
False, False)
On Error GoTo 0
Exit Sub
DBErrHandle:
errnum = Err
Select Case errnum
    Case 3049
        `Corrupt database, attempt to repair
        msgstr = "Your database has been damaged.  Do you wish the "
        msgstr = msgstr & "program to attempt to repair it?"
        msgrtn = MsgBox(msgstr, vbYesNo + vbExclamation, "Database Problem")
        If msgrtn = vbNo Then Exit Sub
        RepairDatabase (dbName)
        Resume
    Case 3056
        `Couldn't repair database
        msgstr = "Your database could not be repaired.  You will "
        msgstr = msgstr & "need to restore the database from your "
        msgstr = msgstr & " latest backup!"
        MsgBox msgstr, vbExclamation, "Database Problem"
        Exit Sub
    Case Else
        `Show any other messages
        msgstr = "The following error occurred while trying to open "
        msgstr = msgstr & "the database: "
        msgstr = msgstr & Error$(errnum)
        MsgBox msgstr, vbExclamation, "Database Problem"
        Exit Sub
End Select
End Sub

Notice that in the Property Get procedure, the property is defined as a Database object. This is to match the object that will receive the value of the property.

Using cDataAccess The case used as a sample calls the object to open the BIBLIO.MDB database that comes with Visual Basic. The code then opens the Authors table of the database and displays a list of authors in a list box. The code for the example is shown in Listing 18.11.

Listing 18.11 ACCOBJ.FRM--Accessing the cDataAccess Object

Dim db As Database 
Dim rs As Recordset
Dim objData As Object
Private Sub cmdAuthors_Click()
   Set objData = New cDataAccess
objData.OpenDb "C:\VB5\BIBLIO.MDB"
   Set db = objData.OpenData
   Set objData = Nothing
   Set rs = db.OpenRecordset("Authors", dbOpenDynaset)
   Do Until OldRc.EOF
      lstAuthors.AddItem rs("Author")
      rs.MoveNext
    Loop
    Db.Close
End Sub

Figure 18.10 shows the results of the sample program.

FIG. 18.10
The cDataAccess object is used to handle the opening of the database.

Documenting Objects, Properties, and Methods

One great advantage of using classes is that you can easily distribute them to other programmers. However, unless there is some type of documentation included, understanding how to use your objects could be a major challenge. For example, the programmer needs to know the purpose of each parameter in a function. Fortunately, you can document your objects, properties, and methods at two levels:

To document the object's properties and methods in a project, follow these steps:

1. From the Project menu, choose Project Properties. Visual Basic displays the Project Properties dialog box.

2. Enter the name of the project's help file in the Help File Name text box. The user interface items in your project share the same help file with the project's more technical aspects, such as programming with objects, properties, and methods.

3. From the Tools menu, choose Procedure Attributes. Visual Basic displays the Procedure Attributes dialog box.

4. Select the method or property that you want to document. Type the description that you want to appear in the Object Browser in the Description text box. Type the help context ID for the method or property in the Help Context ID text box.

5. Repeat Step 4 for each item that you want to document.

Help for a project's objects, properties, and methods resides in the same help file as for the rest of the project. When designing your help file, be careful not to confuse users by including highly technical programming topics in the same table of contents used by people seeking help on your application's user interface.


TROUBLESHOOTING: If you have the Professional Edition of Visual Basic but get an error message when you try to create a Public class module, your installation might be corrupt. Try reinstalling the Visual Basic development environment.

If your ActiveX object doesn't recognize methods and properties that you've just defined, check to make sure that you are running the correct version of the object. When debugging, it is easy to accidentally load the compiled DLL or EXE rather than the new version that hasn't yet been compiled. To avoid this, be sure to start the ActiveX application in the other instance of Visual Basic before calling it from the client application. Also, make sure the client application's reference points to the correct class in the References dialog box.

If you encounter a Duplicate Definition error when trying to add an object to a collection, make sure that the key argument is unique within the collection.


From Here...

This chapter provided you with an introduction to the creation of class modules, which allow you to implement the principles of object-oriented programming. Class modules, when compiled into a separate ActiveX DLL or EXE, provide a powerful tool for encapsulating program functions. In this chapter, we created a sample class and used objects from it in a program. To learn more about some of the topics covered in this chapter, see the following chapters:


Previous chapterNext chapterContents


Macmillan Computer Publishing USA

© Copyright, Macmillan Computer Publishing. All rights reserved.