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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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
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:
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.
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.
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.
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.
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.
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.
` 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.
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.
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.
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.
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.
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.
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:
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
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.
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."
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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:
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.
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:
© Copyright, Macmillan Computer Publishing. All rights reserved.