Programming With Objects
Programming With Objects
Here Goes
Objects are encapsulated — that is, they contain both their code and their data,
making them more easier to maintain than traditional ways of writing code.
Visual Basic objects have properties, methods, and events. Properties are data that
describe an object. Methods are things you can tell the object to do. Events are
things the object does; you can write code to be executed when events occur.
Objects in Visual Basic are created from classes; thus an object is said to be an
instance of a class. The class defines an object’s interfaces, whether the object is
public, and under what circumstances it can be created. Descriptions of classes are
stored in type libraries, and can be viewed with object browsers.
To use an object, you must keep a reference to it in an object variable. The type of
binding determines the speed with which an object’s methods are accessed using the
object variable. An object variable can be late bound (slowest), or early bound.
Early-bound variables can be DispID bound or vtable bound (fastest).
A set of properties and methods is called an interface. The default interface of a
Visual Basic object is a dual interface which supports all three forms of binding. If
an object variable is strongly typed (that is, Dim … As classname), it will use the
fastest form of binding.
In addition to their default interface, Visual Basic objects can implement extra
interfaces to provide polymorphism. Polymorphism lets you manipulate many
different kinds of objects without worrying about what kind each one is. Multiple
interfaces are a feature of the Component Object Model (COM); they allow you to
evolve your programs over time, adding new functionality without breaking old
code.
—2
—4
—5
—6
—7
—8
—9
—10
Tip If you’re going to invoke more than one property or method of an object in a collection,
copy the object reference to a strongly typed object variable first. Using an object reference
while it’s still in a collection is slower than using it after placing it in a strongly typed object
variable (for example, Dim woCurrent As WorkOrder), because the Collection object stores
items in Variants. Object references in Variants are always late bound.
35
For More Information The Collection object is also a useful alternative to arrays for
many ordinary programming tasks. See “Using Collections as an Alternative to
Arrays” in “More About Programming.” Visual Basic provides a number of built-in
collections. To compare them with the Collection object, see “Collections in Visual
Basic.”
36
—11
The reason for this is that the Collection class and the Forms collection are not
polymorphic; that is, you can’t exchange one for the other, because they were
developed from separate code bases. They don’t have the same methods, store object
references in the same way, or use the same kinds of index values.
This makes the Collection class’s name seem like an odd choice, because it really
represents only one of many possible collection implementations. This topic explores
some of the implementation differences you’ll encounter.
—12
—13
—14
—15
1
To display the Object Browser
· From the View menu, choose Object Browser.
4– or –
5Press F2.
6– or –
7Click the Object Browser button on the toolbar.
2
—16
3
· Click on a class in the Classes list to view its description in the description pane
at the bottom. The class’s properties, methods, events, and constants will
appear in the Members list on the right. The classes available are drawn from
—17
5
Right-clicking on the Object Browser brings up the context menu. In addition to the
functions mentioned above, the context menu controls the contents of the Classes list
and the Members list.
—18
—19
7
You can select an item in the Search Results list, and view its description in the
description pane at the bottom of the Object Browser. Clicking on the underlined
jumps in the description pane selects the indicated library or navigates to the object
or member.
You can restrict the search to items that exactly match the string in the Search box
by checking Find Whole Word Only on the context menu.
—20
—21
—22
11
When the user-defined type udtAccount becomes the Account class, its data become
private, and the procedures that access them move inside the class and become
properties and methods. This is what’s meant by the term encapsulation — that is,
an object is a unit (a capsule, if you will) containing both code and data.
When you create an Account object from the class, the only way you can access its
data is through the properties and methods that make up its interface. The following
code fragment shows how the procedures inside the Account class support
encapsulation:
' The account balance is hidden from outside code.
Private mdblBalance As Double
—23
—24
—25
—26
—27
—28
—29
—30
—31
—32
—33
—34
—35
Shown
Once a form becomes visible, the user can interact with it. Thereafter, the form may
be hidden and shown as many times as you like before finally being unloaded.
—36
—37
—38
Press Command1, which calls the global procedure and passes a reference to the first
Class1 object. The global procedure sets the Comment property, and Command1
then calls the ShowComment method to display the object’s data.
As Figure 9.6 shows, the resulting message box demonstrates that the global
procedure CallableAnywhere set the Comment property of the object that was passed
to it, and that the global string is visible from within Class1.
—39
17
Press Command2, which simply calls the ShowComment method of the second
instance of Class1.
As Figure 9.7 shows, both objects have access to the global string variable; but the
Comment property of the second object is blank, because calling the global
procedure CallableAnywhere only changed the Comment property for the first
object.
18
Important Avoid making the code in your classes dependent on global data — that is, public
variables in standard modules. Many instances of a class can exist simultaneously, and all of
these objects share the global data in your program.
Using global variables in class module code also violates the object-oriented programming
concept of encapsulation, because objects created from such a class do not contain all their
data.
102
Static Class Data
There may be occasions when you want a single data item to be shared among all
objects created from a class module. This is sometimes referred to as static class
data.
You cannot implement true static class data in a Visual Basic class module.
However, you can simulate it by using Property procedures to set and return the
value of a Public data member in a standard module, as in the following code
fragment:
' Read-only property returning the application name.
Property Get CommonString() As String
' The variable gstrVisibleEverywhere is stored in a
' standard module, and declared Public.
CommonString = gstrVisibleEverywhere
—40
Property Procedures
Data hiding wouldn’t be much use if the only way you could create properties was
by declaring public variables. How much good would it do you to give the Account
class a Type property, if any code that had a reference to an Account object could
blithely set the account type to any value at all?
Property procedures allow you to execute code when a property value is set or
retrieved. For example, you might want to implement the Type property of the
Account object as a pair of Property procedures:
Public Enum AccountTypes
atSavings = 1
atChecking
atLineOfCredit
End Enum
—42
When the code acct.Type = atChecking is executed, the Property Let is invoked. If the
Account object is brand new, mbytType will be zero, and any valid account type can
be assigned. If the current account type is atSavings, the account will be upgraded.
However, if the current account type is atLineOfCredit, the Property Let will raise an
error, preventing the downgrade. Likewise, if the code acct.Type = 0 is executed, the
Select statement in the Property Let will detect the invalid account type and raise an
error.
In short, property procedures allow an object to protect and validate its own data.
For More Information Are public variables good for anything, then? “Property
Procedures vs. Public Variables” outlines the appropriate uses of both. The
capabilities of property procedures are explored further in “Putting Property
Procedures to Work for You.”
112
—43
—44
—45
—46
22
The most common use for property procedures with multiple arguments is to create
property arrays.
Read-Only Properties
To create a read-only property, simply omit the Property Let or (for object
properties) the Property Set.
Object Properties
If you’re creating a read-write object property, you use a Property Get and a Property
Set, as here:
Private mwdgWidget As Widget
—47
Write-Once Properties
There are many possible combinations of property procedures. All of them are valid,
but some are relatively uncommon, like write-only properties (only a Property Let,
no Property Get). And some depend on factors other than the kinds of property
procedures you combine.
For example, when you organize the objects in your program by creating an object
model, as described in “Object Models” later in this chapter, you may want an object
to be able to refer back to the object that contains it. You can do this by
implementing a Parent property.
You need to set this Parent property when the object is created, but thereafter you
may want to prevent it from being changed — accidentally or on purpose. The
following example shows how the Account object might implement a Parent
property that points to the Department object that contains the account.
' Private data storage for Parent property.
Private mdeptParent As Department
—48
—49
Is It a Property or a Method?
In general, a property is data about an object, while a method is an action the object
can be asked to perform. Some things are obviously properties, like Color and Name,
and some are obviously methods, like Move and Show.
—50
- or -
—51
—52
—53
—54
—55
—56
—57
27
Selecting an event will display the corresponding event procedure, with the prefix
mWidget_. All the event procedures associated with a WithEvents variable will have
the variable name as a prefix. Add the following code to the mWidget_PercentDone
event procedure.
Private Sub mWidget_PercentDone(ByVal Percent As _
Single, Cancel As Boolean)
lblPercentDone.Caption = CInt(100 * Percent) & "%"
DoEvents
If mblnCancel Then Cancel = True
End Sub
157
—58
—59
A WithEvents variable can only contain one object reference at a time, so if you
assign a different Widget object to mWidget, the previous Widget object's events will
no longer be handled. If mWidget is the only object variable containing a reference to
the old Widget, the object will be destroyed.
Note You can declare as many WithEvents variables as you need, but arrays of WithEvents
variables are not supported.
162
Terminating Event Handling for a WithEvents Variable
As long as there is a Widget object assigned to the variable mWidget, the event
procedures associated with mWidget will be called whenever the Widget raises an
event. To terminate event handling, you can set mWidget to Nothing, as shown in the
following code fragment.
—60
—61
—62
—63
Polymorphism
Polymorphism means that many classes can provide the same property or method,
and a caller doesn’t have to know what class an object belongs to before calling the
property or method.
For example, a Flea class and a Tyrannosaur class might each have a Bite method.
Polymorphism means that you can invoke Bite without knowing whether an object is
a Flea or a Tyrannosaur — although you’ll certainly know afterward.
For More Information With the Professional and Enterprise editions of Visual Basic,
Polymorphism becomes a powerful mechanism for evolving systems of software
components. This is discussed in “General Principles of Component Design.”
170
—64
—65
End Sub
End Sub
174
Notice that there’s no code in these methods. Animal is an abstract class, containing
no implementation code. An abstract class isn’t meant for creating objects — its
purpose is to provide the template for an interface you add to other classes.
(Although, as it turns out, sometimes it’s useful to implement the interface of a class
that isn’t abstract; this is discussed later in this topic.)
Now you can add two more class modules, naming one of them Flea and the other
Tyrannosaur. To implement the Animal interface in the Flea class, you use the
Implements statement:
Option Explicit
Implements Animal
175
As soon as you’ve added this line of code, you can click the left-hand (Object) drop
down in the code window. One of the entries will be Animal. When you select it, the
right-hand (Procedure) drop down will show the methods of the Animal interface.
Select each method in turn, to create empty procedure templates for all the methods.
The templates will have the correct arguments and data types, as defined in the
—66
Multiple Interfaces
The Flea class now has two interfaces: The Animal interface you’ve just
implemented, which has two members, and the default Flea interface, which has no
members. Later in this example you’ll add a member to one of the default interfaces.
You can implement the Animal interface similarly for the Tyrannosaur class:
Option Explicit
Implements Animal
—67
—68
End Function
181
When you implement this method in the Tyrannosaur class, you assign the return
value to the procedure name, just as you would for any other Function procedure:
Private Function Animal_Move(ByVal Distance _
As Double) As Double
Dim dblDistanceMoved As Double
' Code to calculate how far to pounce (based on
' age, state of health, and obstacles) is omitted.
' This example assumes that the result has been
' placed in the variable dblDistanceMoved.
Debug.Print "Tyrannosaur moved"; dblDistanceMoved
Animal_Move = dblDistanceMoved
End Function
182
To assign the return value, use the full procedure name, including the interface
prefix.
For More Information The interfaces you implement can have properties as well as
methods. “Implementing Properties” discusses some differences in the way
properties are implemented.
183
Implementing Properties
This topic continues the code example begun in “Creating and Implementing an
Interface,” adding properties to the Animal interface that was implemented in the
Flea and Tyrannosaur classes. You may find it helpful to read that topic before
beginning this one.
Suppose we give the Animal class an Age property, by adding a Public variable to
the Declarations section:
Option Explicit
Public Age As Double
184
The Procedure drop downs in the code modules for the Tyrannosaur and Flea classes
now contain property procedures for implementing the Age property, as shown in
Figure 9.10.
—69
38
This illustrates a point made in “Adding Properties to a Class” earlier in this chapter.
Using a public variable to implement a property is strictly a convenience for the
programmer. Behind the scenes, Visual Basic implements the property as a pair of
property procedures.
You must implement both procedures. The property procedures are easily
implemented by storing the value in a private data member, as shown here:
Private mdblAge As Double
—70
End Property
187
Now the Procedure drop downs in the code windows for the Tyrannosaur and Flea
classes contain only a single entry, Age [PropertyGet]. You might implement this for
the Tyrannosaur as follows:
Private mdblBirth As Double
—71
—72
Set obj = ty
obj.Move 42 ' Fails
obj.Growl ' Succeeds
End Sub
195
Fortunately, there’s very little reason to use the slower, late-bound Object data type
with objects that have multiple interfaces. One of the main reasons for using multiple
interfaces is to gain the advantage of early binding through polymorphism.
—73
—74
—75
—76
—77
Object Models
Once you’ve defined a class by creating a class module and giving it properties and
methods, you can create any number of objects from that class. How do you keep
track of the objects you create?
The simplest way to keep track of objects is to declare an object variable for each
object you plan to create. Of course, this places a limit on the number of objects you
can create.
You can keep multiple object references in an array or a collection, as discussed in
“Creating Arrays of Objects” and “Creating Collections of Objects” earlier in this
chapter.
In the beginning, you’ll probably locate object variables, arrays, and collections in
forms or standard modules, as you do with ordinary variables. As you add more
classes, though, you’ll probably discover that the objects you’re using have clear
relationships to each other.
—78
39
You can define four class modules, named SmallBusiness, Employee, Customer, and
Product, and give them each appropriate properties and methods, but how do you
make the connections between objects? You have two tools for this purpose: Object
properties and the Collection object. The following code fragment shows one way to
implement the hierarchy in Figure 9.11.
' Code for the Declarations section of the
' SmallBusiness class module.
Public Name As String
Public Product As New Product
Public Employees As New Collection
Public Customers As New Collection
205
The first time you refer to the Product property, the object will be created, because it
was declared As New. For example, the following code might create and set the
name and price of the SmallBusiness object’s Product object.
' Code for a standard module.
Public sbMain As New SmallBusiness
Sub Main
sbMain.Name = "Velociraptor Enterprises, Inc."
' The first time the Product variable is used in
' code, the Product object is created.
sbMain.Product.Name = "Inflatable Velociraptor"
sbMain.Product.Price = 1.98
.
. ' Code to initialize and show main form.
.
End Sub
206
Note Implementing an object property with public variables is sloppy. You could inadvertently
destroy the Product object by setting the property to Nothing somewhere in your code. It’s
better to create object properties as read-only properties, as shown in the following code
fragment.
207
' Code for a more robust object property. Storage for
' the property is private, so it can't be set to
' Nothing from outside the object.
—79
—80
40
What’s wrong with this picture? The way you get rid of objects when you’re done
with them is to release all references to them. Assuming the reference to the
SmallBusiness object is in a variable named sbMain, as earlier in this topic, you
might write the following code:
Set sbMain = Nothing
213
Unfortunately, there’s still a reference to the SmallBusiness object — in fact, there
may be many references, because each Employee object’s Parent property will hold
a reference to the SmallBusiness object.
Since the SmallBusiness object’s Employees collection holds a reference to each
Employee object, none of the objects ever get destroyed.
TearDown Methods
One solution is to give the SmallBusiness object a TearDown method. This could set
all of the SmallBusiness object’s object properties to Nothing, and also set all the
Collection objects (Employees, Customers) to Nothing.
When a Collection object is destroyed, Visual Basic sets all the object references it
was holding to Nothing. If there are no other references to the Employee and
Customer objects that were contained in the Employees and Customers collections,
they’ll be destroyed.
Of course, if the Employee object is made up of finer objects, it will have the same
circular reference problem its parent does. In that case, you’ll have to give the
Employee class a TearDown method. Instead of just setting the Employees
—81
—82
—83
42
The following table lists the property values you need to set for this example.
Object Property Setting
Class module Name Employee
Class module Name SmallBusiness
Form Caption Employees Collection
First command button Caption Add
Name cmdAddEmployee
Second command button Caption Delete
Name cmdDeleteEmployee
Third command button Caption Refresh List
Name cmdListEmployees
Fourth command button Caption Trouble
Name cmdTrouble
Fifth command button Caption Close
Name cmdClose
First label control Caption Name
Second label control Caption Salary
First text box Name txtName
Text (blank)
Second text box Name txtSalary
Text (blank)
List Box Name lstEmployees
217
—84
—85
—86
—87
—88
—89
—90
—91
—92
—93
—94
—95