In this chapter, we’re going to explore some of the key programming techniques that
will take us to the heart of object-oriented development with Visual Basic.
We saw, in the last chapter, just how badly things can go wrong without the good
principles of object-oriented programming behind us. And we’re familiar, now, with some of
the key benefits of working with objects – so the techniques we learn here will be prepare
us well for our future programming careers.
We’re actually going back to another company, but this time I think you’ll find that
DataDamage Inc. are a much more aware bunch of people, and that they appreciate the value
of the object-oriented techniques available to today’s Visual Basic programmers.
In particular, in this chapter we’ll be looking at:
- Developing a real-world object-oriented application
- Object Interfaces
- Multiple interfaces
- The Implements keyword
- Containment and Delegation
With Visual Basic 6, objects are here to stay. Love them or hate them, we can’t get
away from them. Without exception, everything we deal with in Visual Basic is an object.
The forms we draw our user interfaces on, the controls that we put on to the form, and
even most of the system objects that we use, from time to time, to complete the shine on
our applications – they are all objects.
Visual Basic’s support for object-oriented development has come a long way since
previous versions. We now have the ability to expose more than one interface from within
an object, thanks to the Implements keyword (more on that later), and we can create our
own custom events at runtime. Visual Basic has a lot to offer real-world programming
The problem with the whole thing, though, is that the object-oriented programming world
is still populated by PhD-toting gangsters. These guys would have you believe that objects
represent a different world, as opposed to just a neat way to design and develop
applications. We covered a lot of the theory in my previous book (Beginning Visual Basic
6, also published by Wrox Press), but this time around we’re going to see it used in
anger, see some real-world development done with object-oriented programming.
Let me tell you: OOP isn’t that hard. It doesn’t need that much in the way of
background training, or patterns of deep thought. It’s all a matter of common sense. Let’s
take a look at a real problem.
Our client, DataDamage Inc, has decided that something needs to be done to keep abreast
of their Biblio database, which is a database containing a vast array of information on
programming books, authors and publishers. They’ve decided to break down the development
of this project into phases, with Phase 1 covering nothing more than the maintenance of
the Authors table in the database, as well as simple browsing of the Titles table.
Sounds simple enough – nothing a few well-placed Data controls and some bound Text
boxes won’t handle. But wait a minute. Although the database is currently in Access
format, a quick peek at the long-term plans for the system reveals some worrying elements.
Not only are DataDamage going to want to migrate this system from an Access database up to
a SQL Server database at some point in time, but they’re also going to be using the code
we write as the foundation for other developers to get started on the system. Anything we
do now must remain useful under the new architecture.
In addition, it’s quite likely that they will want to split the application itself
across multiple machines on the network to improve the performance of the system. A few
data controls and a couple of bound Text boxes aren’t going to be much help here.
Normally, at this point, we’d hit the paper and produce a full design of the
application. This design would be from the point of view of the way things are done now
(user requirements), the way they should be done in the new system (system requirements),
and the way that it’s all going to actually work (functional requirements).
My goal within this chapter, however, is to concentrate first and foremost on getting
us through the technical aspects of OO development with Visual Basic. As we saw in Chapter
1, program design is a crucial part of object-oriented development, which is why I’ve
devoted the whole of Chapter 9 to design techniques.
In this chapter, I’m going to speed us through the design phase so we can focus on the
practical aspects of object-oriented development. If you’re madly keen to learn about
object-oriented program design, then take a peek at Chapter 9.
So the simple design I’ve prepared for us in this chapter looks like this:
We have a data source (the Biblio database), which is going to be accessed by two
classes. As we saw in the last chapter, the beauty of classes is that we can hide the data
source in a neat black box with a simple interface.
Remember, an interface in object-oriented terms is the collection of methods,
properties and events that go towards unleashing the power of our objects in a way which
is easy to get to grips with.
These classes are then used in two forms to provide the Title Browser and Author
Maintenance functions that we’ll require.
Our next step (and this is really where we swing back into the design process), is to
come up with the interface for these objects: the properties and methods that will be used
to provide access to the underlying data. In the interest of keeping things real simple
for the moment, let’s just take a look at the Titles class:
|MoveFirst||Moves to the first title in the data source|
|MoveNext||Moves to the next title in the data source|
|MovePrevious||Moves to the previous title in the data source|
|MoveLast||Moves to the last title in the data source|
Pretty clear I think: we’re going to provide some very similar functionality to
something we might find in a recordset object itself, albeit without the ability to update
the underlying data. Ah, the data… we’re going to need some properties in there to get
at that underlying data:
|BOF||Boolean to indicate if we’re at the beginning of the Titles table|
|EOF||Boolean to indicate if we’re the end of the Titles table.|
|Title||The Title field from the Titles table|
|Year_Published||The Year Published field from the Titles table|
|ISBN||The ISBN field – Titles table|
|PubID||The PubID field – Titles table|
|Description||The Description field – Titles table|
|Notes||The Notes field – Titles table|
|Subject||The Subject field – Titles table|
|Comments||The Comments field – Titles table|
So there we have it. A simple interface that exposes the underlying data as well as the
functionality to manipulate that data.
Now we’ve designed our interface, let’s go ahead and translate all of these ideas into
some real code with some real functionality behind it. Once we’ve created our class, we’ll
build a test framework to see it in action.
Try It Out – Creating a Class for DataDamage Inc
1 Start up a new project and, when it appears, add a new class module in the usual way.
If you can’t remember how to do that, then now might be a good time to re-read Chapter 1,
or consult my other book, Beginning Visual Basic 6.
2 The next step is to start adding code into our new class module to implement all the
methods and properties that we’ve just discussed. For now, just key in the following code
so that your class module looks like mine. By typing this lot in you’re sure to come up
with some questions of your own as to how things work, and I can go ahead and start
answering them when the code is done.
There is a lot of code here, but the interface is real simple, and the bulk of it is
quite repetitive and easy to follow. We’ll take a closer look at how it works once you’ve
typed it in.
Option Explicit ' clsTitles - wraps up the Biblio Titles table. By keeping the ' interface consistent it should be possible to move the class ' to point at some other data source, such as an RDO Resultset, ' or flat file, without users of the class ever noticing. Public Event DataChanged() ' Public members to implement properties Public strTitle As String Public intYear_Published As Integer Public strISBN As String Public lngPubID As Long Public strDescription As String Public strNotes As String Public strSubject As String Public strComments As String ' Private members to hold connection to database, ' and to the recordset itself Private m_recTitles As Recordset Private m_dbDatabase As Database ' Class event handlers Private Sub Class_Initialize() ' When an instance of the class is created, we want to ' connect to the database and open up the Titles table. On Error GoTo Class_Initialize_Error Set m_dbDatabase = Workspaces(0).OpenDatabase _ (App.Path & "Biblio.mdb") Set m_recTitles = m_dbDatabase.OpenRecordset("Titles", _ dbOpenTable) Exit Sub Class_Initialize_Error: MsgBox "There was a problem opening either the Biblio " & _ "database, or the Titles table.", vbExclamation, "Problem" Exit Sub End Sub Private Sub Class_Terminate() ' When the instance of the class is destroyed, we need to ' close down the recordset, and also the connection to the ' database. Error handling is needed since there could have ' been a problem in the Initialize routine making these ' connections invalid On Error Resume Next m_recTitles.Close m_dbDatabase.Close End Sub ' Generic Data management methods Private Sub Reload_Members() ' Reloads the member variables (properties) with the field ' values of the current record On Error GoTo Reload_Members_Error With m_recTitles strTitle = "" & .Fields("title") intYear_Published = .Fields("Year Published") strISBN = "" & .Fields("ISBN") lngPubID = .Fields("PubID") strDescription = "" & .Fields("description") strNotes = "" & .Fields("Notes") strSubject = "" & .Fields("subject") strComments = "" & .Fields("Comments") End With RaiseEvent DataChanged Reload_Members_Error: Exit Sub End Sub ' Class methods Public Sub MoveNext() If Not m_recTitles.EOF Then m_recTitles.MoveNext Reload_Members End If End Sub Public Sub MovePrevious() If Not m_recTitles.BOF Then m_recTitles.MovePrevious Reload_Members End If End Sub Public Sub MoveLast() m_recTitles.MoveLast Reload_Members End Sub Public Sub MoveFirst() m_recTitles.MoveFirst Reload_Members End Sub
3 Don’t forget to give the class a name when you are done typing. In order for your
code to work the same as mine, it makes sense that you set the name property for your new
class to clsTitles (reminding us that this is a class related to Titles).
4 You’ve probably noticed that there’s quite a lot of database code in there. In order
for this code to compile properly, we need to let Visual Basic know that we intend to use
a database, and that it’s going to involve setting a reference to VB6’s Data Access
You may recall, from Chapter 1, that DAO in Visual Basic 6 is an ActiveX component. As
such, the DAO doesn’t reside within Visual Basic itself, which is why we have to build a
reference to it now.
From the Project menu in Visual Basic, select References, and the references dialog
will appear. This provides us with a way to show Visual Basic which external objects
(objects that live in DLLs or separate EXE files from your own app) that we intend to use.
Scroll down the list of items until you see Microsoft DAO 3.51 Object Library and select
it, just as in the screenshot below. Then close the dialog down by clicking the OK button:
Now that everything is in place for our new clsTitles class to work, we’ll take a look
at the code and how it works.
How It Works
The first thing that any class needs is a way for its users to get at the important
elements of the underlying data. We do this by declaring properties for the class.
Designing an interface for a class involves declaring a set of properties in very much
the same way as how we interact with a set of properties when we design a user interface
for our applications.
Since this class is supposed to wrap up a lot of functionality to deal with the Titles
table in the Biblio database, it makes sense for us to have some properties in there to
represent the fields in the underlying database. Then, no matter what format of database
we use in the future, programmers using our clsTitles class will know that they can use
these field properties to get at the underlying data.
Rather than spend ages typing in Property Let and Property Get routines, we’ve gone for
a simpler approach this time:
‘ Public members to implement properties
Public strTitle As String Public intYear_Published As Integer Public strISBN As String Public lngPubID As Long Public strDescription As String Public strNotes As String Public strSubject As String Public strComments As String
You don’t need to key all this in again – we’re reviewing the code we just entered for
our clsTitles class.
As you can see here, what we’re doing here is declaring a set of Public variables
within our clsTitles class. Public variables declared in a class in this way are, as far
as Visual Basic is concerned, read and write properties. At runtime, a user of our
clsTitles class can get directly at the Title property of the object, and at that point
they will be reading from, or writing to, these Public variables.
We still have scope though to expand and write full-blown property handlers later,
should the need arise.
Having variables acting as properties is great, but we still need code to get data from
the database into those variables in the first place. That’s where the Reload_Members
routine comes in, reading data from the underlying recordset and storing it in our
Private Sub Reload_Members() ' Reloads the member variables (properties) with the field
' values of the current record On Error GoTo Reload_Members_Error With m_recTitles strTitle = "" & .Fields("title") intYear_Published = .Fields("Year Published") strISBN = "" & .Fields("ISBN") lngPubID = .Fields("PubID") strDescription = "" & .Fields("description") strNotes = "" & .Fields("Notes") strSubject = "" & .Fields("subject") strComments = "" & .Fields("Comments") End With RaiseEvent DataChanged Reload_Members_Error: Exit Sub End Sub
All we’re doing here is pulling values from the fields in the m_recTitles recordset and
putting them into our member variables. At the end of this, we raise a custom event, which
we’ve called DataChanged, since the user interface needs some way of knowing when these
member variables have changed and it needs to update itself.
The m_recTitles recordset itself is opened up when the class is first turned into an
object. Take a look at the class initializer: first
Private Sub Class_Initialize() ' When an instance of the class is created, we want to ' connect to the database and open up the Titles table. On Error GoTo Class_Initialize_Error Set m_dbDatabase = Workspaces(0).OpenDatabase _ (App.Path & "Biblio.mdb") Set m_recTitles = m_dbDatabase.OpenRecordset("Titles", _ dbOpenTable) Exit Sub Class_Initialize_Error: MsgBox "There was a problem opening either the Biblio " & _ "database, or the Titles table.", vbExclamation, "Problem" Exit Sub End Sub
This is pretty neat. Our clsTitles class is designed to deal with one specific table in
a very specific database, Biblio.mdb. We therefore code the Class_Initialize event to make
the class automatically connect to that database:
Set m_dbDatabase = Workspaces(0).OpenDatabase _ (App.Path & "Biblio.mdb") Set m_recTitles = m_dbDatabase.OpenRecordset("Titles", _ dbOpenTable)
This, in turn, reduces the workload for a user of the class: how many of us have
written a class, for example, where we require a user of that class to pass in the
pre-connected database object ready to use?
A point worth noting, though, is that the code prefixes the name of the database with
App.Path. What this means, in English, is that the clsTitles class will expect to find a
copy of the Biblio database in the directory the project lives in, or where the compiled
program lives when we’re finished.
So now might be a good time to run up the Windows Explorer and make sure that there’s a
copy of the Biblio.mdb database in the same directory that you intend to save this
For any programs you write in the future that access databases, if you know the
location of those databases then you can specify a particular path within your code,
instead of using the App.Path method. In some circumstances, it may even be appropriate to
ask the user to enter a pathname for any databases they want your program to work with.
The rest of our code for clsTitles provides an interface to support the common Move
commands that we’d find on a recordset; in our case, this means just passing the call down
to the recordset itself, before updating the member variables with the new information.
Take a look at the MoveNext method, for example:
Public Sub MoveNext() If Not m_recTitles.EOF Then m_recTitles.MoveNext Reload_Members End If End Sub
This code first makes a quick check to see if the end of the table has been reached. If
we haven’t reached the end of the table, we can move on to the next record – by calling
the MoveNext method on the recordset itself. After that, our old friend the Reload_Members
method is run, which will move the information from the underlying fields in the new
recordset to our clsTitles member properties.
Ok, let’s get some forms up so we can actually use our clsTitles class.
Try It Out – Using Our clsTitles Class
1 Our application is going to be a traditional MDI (Multiple Document Interface)
Go ahead and add an MDI form to your project. Once again, if any of this form/module
adding stuff seems a little strange then you’d do well to check out my last book,
Beginning Visual Basic 6.
Since this is an MDI form, make it big enough to be able to hold the other windows that
it’s going to contain. If you make it too small, its child windows won’t be completely
visible within it – which isn’t great for our users.
2 Set the name of this form to mdiMain. Then select Properties from the Project
menu and use the General tab dialog to set the start-up form to be this MDI form, as in
the screenshot here:
For the sake of completeness, you should also set up the project name to BiblioTech,
just as I have done here.
3 Okay, so we have an MDI form in the application, and we have the clsTitles
class that neatly wraps up the Titles table. We now need to add a menu to our MDI form
that will allow us to access the other forms that we’ll be adding to the application.
Add the following menu headings to the MDI form: File, Titles and Authors. Remember,
you can do this in Visual Basic by selecting the MDI form in design mode, and then
pressing Ctrl and E to bring up the menu editor. There’s no need to worry about giving
these things neat names since we aren’t going to add any code to respond to events on them
– it’s the submenu items that we’re more concerned with. I called mine mnuFile, mnuTitles,
and mnuAuthors, respectively:
4 Use the menu editor again to add a menu item under the File menu. Set this new
menu option caption to Exit and then name it mnuFExit. Underneath the Titles heading, put
a Browse item in there and call it mnuTBrowse. Finally, underneath the Authors heading,
put a Maintain item on there and call it mnuAMaintain:
5 The next step is to get the actual Title Browsing form up and ready, with code
to start it up from the MDI.
As you’ve probably noticed, there’s a lot here for us to do in this phase of our
application development – such are the toils of creating a user interface. I’m not an
unsympathetic guy! Take a break for this step, and just read what I have to say about user
interfaces and object interfaces.
User Interfaces and Object Interfaces
While we’re in the middle of building our user interface for DataDamage Inc, I’d like
to point out to you how closely related user interfaces and object interfaces are to one
another. This is really interesting.
Object-oriented programming does, in fact, fit in really well with the way Windows
works. At the root of any programming problem there’s a hideous mess of data and an
equally unnerving set of code to manipulate that data. Dump that code in a class, though,
and we can design a nice interface to that code and data – something more meaningful and
more in tune with the real-world problem. Now in terms of the user interface of a Windows
application, we usually provide graphical controls on a form that hook into some or all of
the interfaces in our classes and objects. This is a case of a user interface marrying an
object interface, if you like.
Our Title Browsing form is going to provide users of the application with much the same
interface as we can deal with in our objects. For Title Browsing, the interface will of
course be in the form of buttons and text boxes, while our classes (such as clsTitles) and
then our objects will interface through properties and methods.
6 Let’s get that Title Browsing form up and running. Add a standard form to the
project, call it frmTitle, and set the MDIChild property on the form to True. By default,
at runtime Windows will create this form in a default size, which can totally screw up our
Rather than write a lot of resize code to make the controls and the form appear as we
intended at runtime, simply set the Borderstyle property to Fixed Dialog.
7 When you’ve done these things, go ahead and drop some controls on the form so
that it looks like this:
Set the names of the text boxes to txtTitle, txtYear_Published, txtISBN, txtPubID,
txtDescription, txtNotes, txtSubject, txtComments – names that fit quite nicely with the
actual properties on the class. Likewise, set the names of the buttons to cmdFirst,
cmdPrevious, cmdNext and cmdLast. Notice that I’ve also set the Caption property of the
form to Title Browser – it would be rude not to, after all.
8 In order to get this working, we need to add a little code. Now the important
thing to note here is that the hard work is done. We have a class with a very neat
interface, and all we now need to do is to literally link the user interface elements to
the class interface elements.
Click on the new form to select it, then hit F7 on the keyboard to open up its code
window. When it appears, key this code in:
Option Explicit Private WithEvents Titles As clsTitles Private Sub cmdFirst_Click() Titles.MoveFirst End Sub Private Sub cmdLast_Click() Titles.MoveLast End Sub Private Sub cmdNext_Click() Titles.MoveNext End Sub Private Sub cmdPrevious_Click() Titles.MovePrevious End Sub Private Sub Form_Load() Set Titles = New clsTitles End Sub Private Sub Titles_DataChanged() ' When the data changes, we need to load up the text ' boxes on the form With Titles txtTitle = .strTitle txtYear_Published = .intYear_Published txtISBN = .strISBN txtPubID = .lngPubID txtDescription = .strDescription txtNotes = .strNotes txtSubject = .strSubject txtComments = .strComments End With End Sub
Incredibly simple huh? We just call single methods from the click events on those
buttons. Take the First button, for example:
Private Sub cmdFirst_Click() Titles.MoveFirst End Sub
The important part of this code, though, is the event handling.
It is most fortunate that Visual Basic 6 lets us create our own custom events. In the
early days of VB, we could write an application that utilized classes, but we would still
need to write nasty blocks of code to wait for conditions on our objects to occur. In
Visual Basic 6 we can just raise a custom event.
We declared the DataChanged event, if you recall, right at the beginning of our
clsTitles class like this:
Public Event DataChanged()
which is just a little note telling VB that there’s a new event that can occur, and
that it’s called DataChanged. This event is triggered by the programmer with the
RaiseEvent command; in our case, in the Reload_Members method within the clsTitles class:
No great problems there I hope. However, dealing with the events in code elsewhere in
the project does require a little thought. We need to declare the object WithEvents; this
means that when we declare an instance of our Titles object, we just do the following (as
you’ll see right at the top of the code we just added to our Title Browsing form):
Private WithEvents Titles As clsTitles
At this point, the Titles object (which is derived from the clsTitles class) is
available in the object drop down at the top of the code window, just as if it were a
control on the form.
Using events like this greatly reduces the amount of code in the application. No more
endless loops waiting for a specific condition to occur! There is a catch though, as we
can see in the form’s Form_Load event: when we declare an object WithEvents, we can’t
create the object at that point in time! So, where we would normally say :
Dim MyObject As New clsMyClass
we actually have to drop the New keyword when we use WithEvents. This isn’t a big
problem, but it can cause us problems if we forget to actually create the object! That’s
where the Form_Load event comes in:
Private Sub Form_Load() Set Titles = New clsTitles End Sub
Here the object is actually created, and stored in our Titles object variable, which we
The beauty of objects and events is that they let us deal with the flow of our program
in the same way that we might deal with the flow of events in everyday life. Very few
people wait specifically for the phone to ring day in, day out: they prefer instead to
respond to the phone’s ring event.
9 Finally, we need to add some code to get the MDI form to load up the Titles
browsing form. From the Project explorer window, double click on the MDI form, mdiMain,
and select the Browse option from underneath the Titles menu. The code window will appear.
Now code the event so that it looks like this:
Private Sub mnuTBrowse_Click() Load frmTitle End Sub
10 At this stage, our program works just as we would expect it to. Run it up and
have a play with the Title Browsing form to get a feel for what it’s doing. There’s a lot
going on behind the scenes, however, that demonstrates some of the benefits of the object
way of working. Just to recap:
The application is now independent of its data
Black-box development simplifies GUI code
Custom events reduce code size at front end
But there’s more good news: Visual Basic’s support for object-orientation goes far
beyond writing custom events, properties and methods. Let’s take a look…
We’ve discussed interfaces a few times, now, when we’ve been talking about objects,
classes, and black boxes; but what exactly is an interface, and why do we care?
Class Interfaces and Object Interfaces
Well, we already know that an interface is really nothing more than the name given to
the collection of properties and methods within a class. That sounds simple enough, but
it’s worth looking at interfaces in some more detail now.
A class interface is the collection of properties and methods within a class: that’s
the public stuff, the public variables, property routines, subroutines and functions that
we put into our classes.
The private variables and routines within our class, on the other hand, are typically
called Member routines.
It’s worth noting that since we create objects from classes in Visual Basic, there is a
clear relationship between class interfaces and object interfaces. Once we’ve created an
instance of a class, our object will have an object interface as defined by its underlying
The Implements Command
Visual Basic 6 comes with a special command that lets us do some neat things with class
interfaces, and this is the Implements command.
Placed at the top of a class module, the Implements command forces us to write (or
implement) the same methods and properties as those implemented in another class module.
In effect, this forces us to implement the same class interface for one class as that
of another class. Consider this: if the class interface for Class1 consists of three
methods and four properties, and we use the following command at the top of a second class
then this forces us to implement the same three methods and four properties within
Class2 as those defined in Class1.
However, the methods, properties, etc. that make up a class interface do not have to be
implemented using the precise same lines of code that were present in the original class
The methods in the original class interface must all be defined within the implemented
class interface, it’s true, but the actual lines of code within those methods may be quite
different from the original class interface that’s being implemented. This takes us back
to the theme of the black box: just as long as the buttons (or methods) on the outside of
the black box work, we don’t have to worry about what’s going on behind the scenes.
Not every television, for instance, has the same internal workings inside it: all that
matters is that the key functions we expect from a television are available to us. And so
it is with our class interfaces: the lines of code behind each implementation of a class
interface may be quite different.
If you are still a bit unsure about this idea of implementing, then you could take a
quick peek at the section on interface inheritance that appears in a few pages time. It
contains a good example of the use of the Implements command.
Right, I hope you’ve returned to this point, as we are about to consider the subject of
multiple interfaces and see how handy the Implements command can be.
This concept is real simple: a class can support more than one interface.
Let’s stay with our Class1 and Class2 example for a moment. Assuming we’d just
implemented the interface for Class1 within our code for Class2, we could then proceed to
add a new set of methods and properties to Class2 that were above and beyond the methods
and properties implemented from Class1.
At which point, Class2 happens to support more than one interface: first, the default
class interface for Class1; and second, the class interface defined by the new methods and
properties that belong to Class2 itself:
By arranging for a number of classes to implement the same interface, we’re forcing the
developers of those classes to conform to a particular set of standards. This makes life a
lot easier for the people using those classes, since they can expect a certain interface
to always be available.
Think about a car, a small van, and a large semi-truck. All have a common interface:
press the gas pedal to go faster, press the brake to slow down, and turn the steering
wheel to change direction. Each has at least one door, requires fuel, and contains a set
of dials and gauges to show your current speed, fuel remaining, oil pressure and so on.
All of these classes implement a common interface: a Vehicle interface, if you like.
Aside from implementing that Vehicle interface, however, each class has its own unique
interface – which it bolts on to the base Vehicle interface. A car has rear seats, for
example; a van has the ability to carry more goods than a car, but has less seats; and a
semi has a detachable freight carrying device (its trailer), possibly a bed (if it’s a
long haul truck), and so on.
Let’s carry this line of thought on for a little longer. A car and van could
conceivably have the same chassis, the same engine, and to all intents and purposes they
could be identical in every way if we ripped the bodywork off. A semi, on the other hand,
is completely different underneath the surface. Now since all these cars implement the
same interface as a basic Vehicle, we could take any qualified driver and tell them to
Drive each of the vehicles from one point to another. Drive is a common method that most
drivers know how to use, and the chances are that most drivers could get all three
vehicles moving as requested. So despite the differences, and indeed the similarities,
between these three vehicles, the Drive method will get us driving – whichever car we’re
Visual Visual Basic works the same way. If we have a Vehicle class defined with a Drive
method (among others), we can implement the Vehicle interface within new Car, Van and Semi
classes. Then, if we want to get our Car, Van and Semi objects moving, Visual Basic will
allow us to pass each of these objects to a routine that simply expects a Vehicle type
object and then calls its appropriate Drive method.
Implementing Interfaces in Visual Basic 6
In our example application for DataDamage Inc, multiple interfaces will be very useful.
So far, we have a neat title browser. It works quite well, and this browser interface
represents something that we would ideally like to see in the Authors class a little
So although we will ultimately need to produce both a clsTitles class (done that) and a
distinct clsAuthors class, these classes will have some common methods. For instance, each
class will allow the browsing of data. At the same time, however, each class will have its
own unique properties, and the clsAuthors class will need to have its own unique methods
of changing the underlying Author data (since Author data will be arranged differently in
the database than the Title data).
So here’s our plan: we’ll define the common browsing interface between these two
classes – that is, the interface that contains the MoveFirst, Next, Previous and Last
methods. We’ll then implement that common browsing interface in both the clsTitles class
and the clsAuthors class.
Anyone who later modifies or deals with the code in our application will be grinning
from ear to ear when they see that all data access objects in our application actually
have a common object interface.
So how do we define a common interface between two classes? Well, the best way to do
this is to start to think in terms of class frameworks.
The framework of a class is actually the bare bones of the interface for that class. A
framework is therefore just a list of the public routines and properties that form the
interface for a class or object, without any real code to actually implement that
Frameworks are therefore the briefest possible description of a class interface or
object interface, since they contain none of the actual code behind that interface – just
empty routines and properties. Frameworks are useful things to know about, so let’s get on
with some code and see it all in action.
Try It Out – Using Implements
1 Since our clsTitles and clsAuthors classes are going to share a common
interface, the first thing we must do is move the basic framework of the Titles interface
out into another object.
Add a new class module to your project and call it clsRecordset. When that’s done, add
the following code.
Option Explicit ' clsRecordset object - a generic object designed to wrap ' up the functionality of a recordset in a generic re-useable ' object. Private Sub Class_Initialize() ' End Sub Private Sub Class_Terminate() ' End Sub ' Class methods Public Sub MoveNext() ' End Sub Public Sub MovePrevious() ' End Sub Public Sub MoveLast() ' End Sub Public Sub MoveFirst() ' End Sub Public Sub AddAsNew() ' End Sub Public Sub SaveChanges() ' End Sub Public Sub DeleteCurrent() ' End Sub
Be careful when you define your own interfaces like this though. The Visual Basic
compiler has a real nasty habit of wiping out any empty routines that you might have in
your project, hence the reasoning behind putting a single quote (comment) in each of the
Notice that all these routines are just bare definitions – with no real code in them.
Also notice that we’re adding three routines to this framework that weren’t present within
our original clsTitles class (AddAsNew, SaveChanges and DeleteCurrent) which are
specifically concerned with adding, saving and deleting data. These are of little
relevance for clsTitles, but will feature in our clsAuthors class.
2 With our clsRecordset framework interface defined, the next step is to
actually let Visual Basic start enforcing it on our code – which is a perfect job for the
Bring up the code for your clsTitles class and add a line just underneath the Option
Explicit statement so that your code now reads like this:
Option Explicit ' clsTitles - wraps up the Biblio Titles table. By keeping the interface ' consistent it should be possible to move the class to point at some ' other data source, such as an Rdo Resultset, or flat file, without ' users of the class ever noticing. Implements clsRecordset Public Event DataChanged() : :
If we now try to compile this program and run it, we find that it no longer works:
Visual Basic instead gives us a host of new error messages:
I wasn’t kidding before, when I said that using Implements tells the compiler to force
you to write certain routines! But this is just what we want, in our case, since we need
to produce a consistent interface across the clsTitles class and the clsAuthors class that
we’re going to create shortly!
You may also notice how useful this could be in a team development environment where
you would need to manage a group of developers and make sure that they were all developing
to certain interface standards.
3 The previous error message is telling us that, in order for the clsTitles
class to implement the interface of the clsRecordset class, we need to write a method
But hang on a second! We did write a MoveNext method; it does exist! If it didn’t
exist, then the application as it existed before we put that new clsRecordset class in
would not have worked at all. The truth of the matter is that Visual Basic is just
expecting a little more than we have already done. Time to find out just what.
Try going back to the code editor for the clsTitles source and drop down the Objects
Combo box at the top of the editor:
Notice how the list now includes a clsRecordset object, just as if we had dropped a
control on to a form and we were editing the form’s code.
Select clsRecordset from this drop down list, just as I have done here.
4 Now drop down the Events list Combo box, which sits at the top right of the
code window. You’ll see a list of all the clsRecordset methods that we can implement in
the CTitles class:
What does all this tell us? Something quite simple really: that our clsTitles class has
two interfaces: the original clsTitles class interface, and now also the clsRecordset
class interface that we added to clsTitles (we used Implements to do that).
The upshot is clear enough: all the routines in this drop down list for the
clsRecordset interface will need to be defined within our clsTitles class.
As we noted before, it’s perfectly true that there are routines by these names already
present in our clsTitles class; but those routines are currently part of the default class
interface for clsTitles. We now need to implement the clsRecordset class interface within
clsTitles, which we committed ourselves to with the Implements clsRecordset command.
Next question: how do we implement the clsRecordset class interface within the
clsTitles class? Take a look at this routine (but don’t type it in yet):
Public Sub clsRecordset_MoveNext() If Not m_recTitles.EOF Then m_recTitles.MoveNext Reload_Members End If End Sub
This is our MoveNext routine all right, but notice that it’s prefixed with
clsRecordset_ which tells Visual Basic that this routine belongs to the clsRecordset
interface. This prefix method is how we tell Visual Basic which routines and properties
belong to which interface.
5 Okay, in order to get the clsTitles class working again, go through and change
all the Public method names in clsTitles so that they’re prefixed with clsRecordset.
Be careful though: you only need to do this for Public methods that are also in the
You should end up with all your Public routines, which looked something like this:
Public Sub MoveFirst() m_recTitles.MoveFirst Reload_Members End Sub
now looking more like this:
Public Sub clsRecordset_MoveFirst() m_recTitles.MoveFirst Reload_Members End Sub
6 Since we added three new methods to the clsRecordset class, to create an
interface for updating data (AddAsNew, SaveChanges and DeleteCurrent), we’ll also need to
code these routines into our clsTitles class.
Therefore add the following routines to clsTitles:
Public Sub clsRecordset_AddAsNew() ' Do nothing End Sub Public Sub clsRecordset_SaveChanges() ' Do nothing End Sub Public Sub clsRecordset_DeleteCurrent() ' Do nothing End Sub
Notice that these routines are commented out again. We don’t want them to actually do
anything, at this stage, since our clsTitles class is just to allow browsing. We still
need to implement them, however, since we’ve committed ourselves to implementing the
entire clsRecordset interface within clsTitles.
7 When you’re done, try running the application again, and you’ll quickly find
that it doesn’t work. If you’re in any doubt, try pressing the First button on our
It’s actually very interesting why things aren’t working yet. Take a look at the code
behind the First button on the browsing form:
Private Sub cmdFirst_Click() Titles.MoveFirst End Sub
Visual Basic looks at this code and says "Ok, the programmer wants me to run the
MoveFirst method on the Titles object". It then takes a peek at the default interface
for the Titles object (which is the clsTitles interface) and sees that it doesn’t actually
have a MoveFirst method. There’s one on the clsRecordset interface, which clsTitles
implements, but not on the clsTitles interface itself anymore (we changed it, remember?).
The net result: Visual Basic gives you an error.
As we’ve already seen, if we need to make calls to code that lives in an implemented
interface, then we must prefix the method name with the name of the interface. Therefore,
the code above should actually read:
Private Sub cmdFirst_Click() Titles.clsRecordset_MoveFirst End Sub
Also prefix the method name with clsRecordset_ in the cmdLast_Click, cmdNext_Click and
Okay, let’s move on to the Author class of the DataDamage Inc application.
Try It Out – Adding the Author Class
1 On the whole, the Author class is very similar to the Titles
class, with the simple addition that we’ll allow the user to add, edit and delete records.
Given the code that we have, this should be pretty easy to do.
Put a new class module into the application, and copy and paste the entire clsTitles
class code into it. Change the name of this new class to clsAuthors.
Something called containment provides us with a workaround to save us doing this, but
more on that later.
2 Once we have the clsTitles code pasted into our new class, there are a few
routines that we obviously need to change: the Authors table in the Biblio database, for
example, has a totally different set of fields. I’ve listed the entire class below, with
the relevant changes highlighted:
Option Explicit ' clsAuthors - wraps up the functionality to browse and ' maintain the authors table in the Biblio database Implements clsRecordset Public Event DataChanged() ' Public members to implement properties Public lngAu_ID As Long Public strAuthor As String Public intYear_Born As Integer ' Private members to hold connection to database, ' and to the recordset itself Private m_recAuthors As Recordset Private m_dbDatabase As Database ' Class event handlers Private Sub Class_Initialize() ' When an instance of the class is created, we want to ' connect to the database and open up the Authors table. On Error GoTo Class_Initialize_Error Set m_dbDatabase = Workspaces(0).OpenDatabase _ (App.Path & "Biblio.mdb") Set m_recAuthors = m_dbDatabase.OpenRecordset("Authors", _ dbOpenTable) Exit Sub Class_Initialize_Error: MsgBox "There was a problem opening either the Biblio " & _ "database, or the Authors table.", vbExclamation, "Problem" Exit Sub End Sub Private Sub Class_Terminate() ' When the instance of the class is destroyed, we need to ' close down the recordset, and also the connection to the ' database. Error handling is needed since there could have ' been a problem in the Initialize routine making these ' connections invalid On Error Resume Next m_recAuthors.Close m_dbDatabase.Close End Sub ' Generic Data management methods Private Sub Reload_Members() ' Reloads the member variables (properties) with the field ' values of the current record On Error GoTo Reload_Members_Error With m_recAuthors lngAu_ID = .Fields("au_id") strAuthor = "" & .Fields("author") If Not IsNull(.Fields("Year Born")) Then intYear_Born = .Fields("Year Born") Else intYear_Born = 0 End If End With RaiseEvent DataChanged Reload_Members_Error: Exit Sub End Sub Private Sub Save_Members() ' Assumes that the recordset is in either Edit or Addnew mode. On Error GoTo Save_Members_Error With m_recAuthors .Fields("Author") = "" & strAuthor .Fields("Year Born") = intYear_Born End With Save_Members_Error: Exit Sub End Sub ' Class methods Public Sub clsRecordset_MoveNext() If Not m_recAuthors.EOF Then m_recAuthors.MoveNext Reload_Members End If End Sub Public Sub clsRecordset_MovePrevious() If Not m_recAuthors.BOF Then m_recAuthors.MovePrevious Reload_Members End If End Sub Public Sub clsRecordset_MoveLast() m_recAuthors.MoveLast Reload_Members End Sub Public Sub clsRecordset_MoveFirst() m_recAuthors.MoveFirst Reload_Members End Sub Private Sub clsRecordset_SaveChanges() ' With m_recAuthors .Edit Save_Members .Update End With End Sub Private Sub clsRecordset_AddAsNew() ' With m_recAuthors .AddNew Save_Members .Update End With End Sub Private Sub clsRecordset_DeleteCurrent() ' m_recAuthors.Delete On Error Resume Next m_recAuthors.MoveFirst Reload_Members End Sub
As you can see, it’s really pretty similar to the Titles class. The obvious differences
are the names of the member properties to get at the field values, as well as the name of
the member recordset. Finally, we added some code this time around to handle the Add, Edit
and Delete methods, as well as a new private routine, Save_Members, to copy the values of
the member variables out to the corresponding fields.
3 Just as before, we now need to create a browsing and maintenance form. Add a
new form to the project and place controls on the form so that it looks like this:
Once again, make the form an MDI child, and name it frmAuthor. Next up, name the two
text boxes txtAuthor and txtYearBorn respectively, and the command buttons cmdFirst,
cmdPrevious, cmdNext, cmdLast, cmdAdd, cmdUpdate and cmdDelete.
4 Now we’re ready to add code. Because of the way we’ve designed the code so
far, the code under this form should be quite familiar to you, and as brief as ever. Take
a look at this and add it all to frmAuthor:
Option Explicit Private WithEvents Authors As clsAuthors Private Sub Authors_DataChanged() ' When the data in the Authors object changes (by moving to a new ' record, update the controls on the form with the revised data txtAuthor = Authors.strAuthor txtYearBorn = Authors.intYear_born End Sub Private Sub cmdAdd_Click() Update_Authors Authors.clsRecordset_AddAsNew End Sub Private Sub cmdDelete_Click() Authors.clsRecordset_DeleteCurrent End Sub Private Sub cmdFirst_Click() Authors.clsRecordset_MoveFirst End Sub Private Sub cmdLast_Click() Authors.clsRecordset_MoveLast End Sub Private Sub cmdNext_Click() Authors.clsRecordset_MoveNext End Sub Private Sub cmdPrevious_Click() Authors.clsRecordset_MovePrevious End Sub Private Sub cmdUpdate_Click() Update_Authors Authors.clsRecordset_SaveChanges End Sub Private Sub Form_Load() Set Authors = New clsAuthors End Sub Private Sub Update_Authors() ' Updates the members in the Authors object with the data currently ' on the form Authors.strAuthor = txtAuthor Authors.intYear_born = txtYearBorn End Sub
The big difference between this and the code we have on the Titles form is that this
has code to update the data in the class and ultimately the Authors database itself. When
the application is running, we can enter data into the form and then click on the Add
button, for example. This will trigger the Update_Authors routine to copy the fields out
to the class members, and then run the AddAsNew method on the class to copy the
information it holds out to a new record. The Update button works in exactly the same way,
calling SaveChanges instead of AddAsNew.
Something for you to consider about this code: the DeleteCurrent routine that we’ve
written will only be successful at deleting Authors that we’ve entered into the database
ourselves. Other Authors in the database have further records in related databases, which
prevents the simple deletion of an Author. A nice extension to this code would therefore
be an error trapping routine so that our application doesn’t crash out with an error
message every time this occurs. We discuss error trapping at various points through this
5 Toe To run this full application, you’ll need to add some code to the MDI form
to load the Author Browser form up.
Bring up the MDI form now, and click on the Maintain item under the Authors menu
heading. Just as before, the code window will come into view for the click event of that
menu item. Just add a line of code so that it looks like this:
Private Sub mnuAMaintain_Click() Load frmAuthor End Sub
Now run the program, and you’ll be able to see both browser forms in action. DataDamage
are clearly on their way to a great application.
The most important point to note, in all this, is that even though we have quite a lot
of code in the form, it’s all very simple stuff, really easy to understand and follow, and
therefore really easy to maintain.
This is the benefit of the object-oriented design of the application. Although it took
some effort at the start to get the code into the clsTitles and clsRecordsets class
modules, and then to provide a neat interface to them from the code module, the result is
that we have an application that is extremely easy to extend, should the need arise.
What is Inheritance?
With inheritance, a class gains all the properties and methods that make up the
interface of the base class, and it can then extend the interface by adding properties and
methods of its own. The new class can also extend or change the implementation of each of
the properties and methods from the original base class.
Inheritance, in a nutshell, would mean that we could create an object and then use it
(plus all its properties and functionality) as the basis for a new object.
For instance, if we had a Vehicle class that had all the properties and methods that
apply to all types of vehicle, then inheritance would make it very easy to create classes
for Car, Van and Semi. We wouldn’t need to recreate all the code for a Car to be a
Vehicle: it would inherit that automatically from the original Vehicle class. This
technique could be very useful for comparing say, properties of Cars and Vans (such as
color, number of wheels etc) – even though cars and vans are fundamentally different
Inheritance at DataDamage Inc.
Consider our application for DataDamage Inc. If our application were a little bigger,
then at some point we would reach a worrying situation. Given that the place where we
stored the data would sooner or later change, we would eventually have to go through and
change all our data access code to deal with that change. We would have work to do in both
in the clsAuthors class and the clsTitles class – not to mention any other classes that
may have been added over time which access the database directly. Not a pleasant
situation, especially if you scale up the size of our application.
Inheritance would solve our dilemma here. We could simply put all the common
functionality for accessing the database in one object, and then build on that object with
new ones that inherited the old. This would be ideal in our case – we could have a
clsRecordset object holding a lot of code to talk to the database, and then we could
inherit that object into clsAuthors and clsTitles, just adding any appropriate code
specific to those tables.
It seems we don’t have that luxury yet, however. Maybe in a future version of Visual
Basic we will find inheritance. For now, is all this just wishful thinking? Read on.
As we’ve seen, VB6 comes complete with a fantastic command: Implements. When people
first saw this command, the immediate reaction from a lot of them was to jump up and down
proclaiming that Microsoft had at last brought inheritance to Visual Basic. This was not
We can, however, gain interface inheritance by using the Implements keyword. The
Implements keyword just tells Visual Basic that we would like to implement code for
another object’s interface – we don’t inherit the original object’s data or behavior.
However, the big drawback is that we can’t extend an interface that has been created with
the Implements keyword, so with this method we don’t gain one of the key benefits of
So how do we use interface inheritance? Take a look at this example of a Vehicle class:
Option Explicit Public Property Get Wheels() As Integer End Property Public Property Get NumberOfSeats() As Integer End Property Public Property Get Color() As String End Property
Notice that there isn’t any code in these routines. Remember that interface inheritance
allows the inheritance of interfaces only. Any code in these routines would not be used
Now we can create classes based on the Vehicle class by using the Implements keyword to
inherit the interface. Take a look at this Van class:
Option Explicit Implements Vehicle Private Property Get Vehicle_Wheels() As Integer Vehicle_Wheels = 4 End Property Private Property Get Vehicle_NumberOfSeats() As Integer Vehicle_NumberOfSeats = 2 End Property Private Property Get Vehicle_Color() As String Vehicle_Color = "White" End Property
We could do the same with a Car class:
Option Explicit Implements Vehicle Private Property Get Vehicle_Wheels() As Integer Vehicle_Wheels = 4 End Property Private Property Get Vehicle_NumberOfSeats() As Integer Vehicle_NumberOfSeats = 5 End Property Private Property Get Vehicle_Color() As String Vehicle_Color = "Red" End Property
Notice that we have to prefix the properties Wheels, NumberOfSeats and Color with
Vehicle_ so that Visual Basic knows that these routines belong to the Vehicle interface.
Unfortunately we can’t extend the interface, so if we want to add a property to the Van
class for StorageCapacity we’ll have to use a different technique.
It’s possible to simulate inheritance by using a combination of two techniques,
containment and delegation. So what do these terms mean?
The idea behind containment is that, in object-oriented analysis, an object can have a
private instance of another object inside itself. For instance, a old Television object
could contain a Valve object, but that Valve object would be private to the Television
object, since no code outside of that Television object would be likely to interact with
Delegation refers to the situation where one object delegates a task to another object
rather than doing the work itself. Now when we elect not to call the methods in the base
class, we are choosing not to delegate tasks down to the base class – and we write methods
within the current class to perform the task instead.
Let’s see how we can simulate inheritance with our Vehicle example:
Option Explicit Public Property Get Wheels() As Integer Wheels = 4 End Property Public Property Get NumberOfSeats() As Integer NumberOfSeats = 5 End Property Public Property Get Color() As String End Property
This time we’ve put some code into our Wheels and NumberOfSeats routines. With
simulated inheritance these properties will be available in any new classes we create from
Now let’s create a new Car class.
Option Explicit Private objVehicle As Vehicle Public Property Get Wheels() As Integer Wheels = objVehicle.Wheels End Property Public Property Get NumberOfSeats() As Integer NumberOfSeats = objVehicle.NumberOfSeats End Property Public Property Get Color() As String Color = "Red" End Property
Notice that this time we don’t use Implements and that we create a Private Vehicle
object inside our new Car class. Our Car class contains an instance of the Vehicle class.
Private objVehicle As Vehicle
In our Wheels and NumberOfSeats routines, the Car class is delegating the work down to
the private Vehicle object.
NumberOfSeats = objVehicle.NumberOfSeats
Now let’s create a new Van class in the same way:
Option Explicit Private objVehicle As Vehicle Public Property Get Wheels() As Integer Wheels = objVehicle.Wheels End Property Public Property Get NumberOfSeats() As Integer NumberOfSeats = 2 End Property Public Property Get Color() As String Color = "White" End Property Public Property Get StorageCapacity() As String StorageCapacity = "A Lot" End Property
Now in this case the NumberOfSeats for a Van is different, so there is no delegation
down to the Vehicle object. Instead, the Van overrides this functionality by implementing
the routine itself.
NumberOfSeats = 2
We’ve also extended our interface for the Van object by adding a StorageCapacity
property – something we were unable to do with interface inheritance.
Public Property Get StorageCapacity() As String StorageCapacity = "A Lot" End Property
Containment at DataDamage Inc.
In our application for DataDamage, the combination of containment and delegation allows
us to move the basic functionality of the clsTitles and clsAuthors classes back into
clsRecordset. We can then call that functionality from the methods in clsTitles and
Try It Out – Using Containment in Our BiblioTech Project
1 Let’s begin by moving the basic functionality that was in the clsTitles and
clsAuthors into clsRecordset. Change the clsRecordset class so that it looks like this:
Option Explicit ' clsRecordset object - a generic object designed to wrap ' up the functionality of a recordset in a generic re-useable ' object. ' Class methods Public Sub MoveNext(recRecords As Recordset) If Not recRecords.EOF recRecords.MoveNext End If End Sub Public Sub MovePrevious(recRecords As Recordset) If Not recRecords.BOF Then recRecords.MovePrevious End If End Sub Public Sub MoveLast(recRecords As Recordset) recRecords.MoveLast End Sub Public Sub MoveFirst(recRecords As Recordset) recRecords.MoveFirst End Sub
2 Now we come to the interesting part! We’re going to use containment in clsTitles, so
that a private instance of clsRecordset is contained within our clsTitles object.
The top of our clsTitles class thus becomes:
Option Explicit ' clsTitles - wraps up the Biblio Titles table. By keeping the ' interface consistent it should be possible to move the class ' to point at some other data source, such as an RDO Resultset, ' or flat file, without users of the class ever noticing. Public Event DataChanged() ' Public members to implement properties Public strTitle As String Public intYear_Published As Integer Public strISBN As String Public lngPubID As Long Public strDescription As String Public strNotes As String Public strSubject As String Public strComments As String Private m_objRecordset As New clsRecordset ' Private members to hold connection to database, ' and to the recordset itself Private m_recTitles As Recordset Private m_dbDatabase As Database
Notice we’ve removed the Implements command from this code now: we no longer need to
implement clsRecordset in clsTitles, since we can access the clsRecordset routines through
our new m_recRecordset private member variable, which contains the private instance of the
3 Now we’re able to remove the basic functionality from our clsTitles class –
remember, we’re delegating the recordset navigation tasks to our clsRecordset class. The
end of your clsTitles class should look like this:
' Class methods Public Sub MoveNext() m_objRecordset.MoveNext m_recTitles Reload_Members End Sub Public Sub MovePrevious() m_objRecordset.MovePrevious m_recTitles Reload_Members End Sub Public Sub MoveLast() m_objRecordset.MoveLast m_recTitles Reload_Members End Sub Public Sub MoveFirst() m_objRecordset.MoveFirst m_recTitles Reload_Members End Sub
All we’re doing here is delegating the navigation of the recordset held in the
m_recTitles member variable, to our private instance of clsRecordset (i.e.
Notice also that we’ve deleted the following routines from our class. As we’re using a
combination of containment and delegation to simulate inheritance, instead of the
Implements keyword, we’re now able to extend the interface of our clsAuthors class.
Therefore, these routines are no longer needed in our clsTitles class.
Public Sub clsRecordset_AddAsNew() ' Do nothing End Sub Public Sub clsRecordset_SaveChanges() ' Do nothing End Sub Public Sub clsRecordset_DeleteCurrent() ' Do nothing End Sub
4 Now we must repeat the process with the clsAuthors class. Change the code at
the beginning of clsAuthors so that it looks like this:
Option Explicit ' clsAuthors - wraps up the functionality to browse and ' maintain the authors table in the Biblio database Public Event DataChanged() ' Public members to implement properties Public lngAu_ID As Long Public strAuthor As String Public intYear_Born As Integer Private m_objRecordset As New clsRecordset ' Private members to hold connection to database, ' and to the recordset itself Private m_recAuthors As Recordset Private m_dbDatabase As Database
Once again, the Implements command has been removed and we’ve declared a member
variable m_objRecordset to hold our private instance of the clsRecordset object. No
5 Now move to the bottom of the clsAuthors class and make the following changes:
' Class methods Public Sub MoveNext() m_objRecordset.MoveNext m_recAuthors Reload_Members End Sub Public Sub MovePrevious() m_objRecordset.MovePrevious m_recAuthors Reload_Members End Sub Public Sub MoveLast() m_objRecordset.MoveLast m_recAuthors Reload_Members End Sub Public Sub MoveFirst() m_objRecordset.MoveFirst m_recAuthors Reload_Members End Sub Public Sub SaveChanges() With m_recAuthors .Edit Save_Members .Update End With End Sub Public Sub AddAsNew() With m_recAuthors .AddNew Save_Members .Update End With End Sub Public Sub DeleteCurrent() m_recAuthors.Delete On Error Resume Next m_recAuthors.MoveFirst Reload_Members End Sub
Just as with our clsTitles class, we can remove the clsRecordset_ prefix from our
method names. In addition, as the code for navigating the recordset is held within the
clsRecordset class we just need to delegate these tasks to the base object,
Finally, we’ve extended the interface of our clsAuthors class with the AddAsNew,
SaveChanges and DeleteCurrent routines. These routines need to be Public as they’re being
called from our frmAuthor form.
Now all that remains to be done is to make some minor alterations to our browsing
6 Open the code window for frmTitle and change your code so that it looks like
Option Explicit Private WithEvents Titles As clsTitles Private Sub cmdFirst_Click() Titles.MoveFirst End Sub Private Sub cmdLast_Click() Titles.MoveLast End Sub Private Sub cmdNext_Click() Titles.MoveNext End Sub Private Sub cmdPrevious_Click() Titles.MovePrevious End Sub
7 Now repeat the process with the frmAuthor form – it should look like this:
Option Explicit Private WithEvents Authors As clsAuthors
Private Sub Authors_DataChanged()
‘ When the data in the Authors object changes (by moving to a new
‘ record, update the controls on the form with the revised data
txtAuthor = Authors.strAuthor
txtYearBorn = Authors.intYear_born
Private Sub cmdAdd_Click()
Private Sub cmdDelete_Click()
Private Sub cmdFirst_Click()
Private Sub cmdLast_Click()
Private Sub cmdNext_Click()
Private Sub cmdPrevious_Click()
Private Sub cmdUpdate_Click()
8 Now run the project! You shouldn’t notice any difference as a user – but
there’s a whole world of difference in how the application is structured under the hood.
For the DataDamage application on its present scale, there wouldn’t really be much code
benefit to us at this stage in using these techniques. In the real world, though, should
the database ever need to be moved to somewhere other than a nice simple Access database,
these techniques would take on a new significance. Our trump card would be that we could
easily redesign that base object in such a way that nothing else in the application would
have to change.
Naturally, the code in the base class would have to change to reflect the change in
database, but those changes would ripple up to any other classes which contained its
functionality – and from there the changes would ripple up to the forms that used those
classes. Now THAT is a maintainable system!
Here’s a thought for you. Moving all that code out of the original class and into a new
one might seem like a lot of work if the classes we were dealing with were a little larger
and more complex. So what’s the point? Well, the point does depend on your project and
your needs. That’s why we’ve looked at the different ways you can implement objects in
Visual Basic. But I do have a real-life example that might help you make your mind up…
I did some consultancy for a large company that had a team of 10-20 inexperienced
Visual Basic developers. In order to reduce the burden on them (they were all newbies
after all) the Management decided that we would develop boilerplate code, which was
pre-built blocks of code that the other developers would just fill in the blanks on. My
suggestion was "Hey, why don’t we do something with interface inheritance and
containment of objects – that’s going to save us a lot of time and effort, and the Visual
Basic compiler can even help us enforce it all." The management answer was the age
old "We don’t have time to do that… it’s too new."
The end result then was that we got on with the management’s "quicker"
solution. The rest of the team then took our boilerplate forms, added their minor changes,
and then added them into the grand project. There were more than 90 forms in that project,
all of which were based on the boilerplate code. Every time that boilerplate changed, the
programmers had to stop and make the changes to their working forms by hand. Then they had
to retest the forms for errors. This process took about an hour per form, each and every
time the base code changed, which averaged out at once a month.
So, the management decision that we couldn’t afford 2 days at the start to get it right
resulted in them spending approximately 45 man days retro-fitting their code with
The moral of my story is this: containment may take some extra time in the short term,
but in the long term it’s usually worth every minute.
You have been warned.
Our time at DataDamage, in this chapter, has taken us over some of most essential
object-oriented development issues within Visual Basic. We’ve been busy with plenty of
program code, and we’ve taken a real-world programming scenario to explore the
Although there’s a lot more to come in our journey through object-oriented programming
in Visual Basic, we’ve covered some of the hardest areas already. In particular, we looked
Class and Object Interfaces
In the next chapter, we’ll have a look at a few more design aspects of object-oriented
programming as we discuss object hierarchies.
Why Not Try…
1 In the exercise in this chapter where we created a class to browse the Titles
table, what was the purpose of raising the event DataChanged in the Reload_Members
2 When does the procedure Reload_Members take place in the class?
3 In the exercise in the chapter where we created ‘properties’ for each field in
the Titles table, did you find anything unusual about the way we created the properties?
4 In the exercise in this chapter where we designed a class to browse the Titles
table, when is the recordset created?
5 In the second exercise in the chapter, where we ‘instantiate’ an object based
on our class, when and where is the object instantiated?
6 In the second exercise in the chapter, where we ‘instantiate’ an object based
on our class, how do we make use of the event DataChanged raised out of the class, and how
is it used?
7 Prove to yourself that the code that was written in exercise 2 is really
independent of the data source. Is there any way that you can tell whether the underlying
data source for the form is an Access table, an Oracle table, a SQL table, or even a flat
8 On a related note, if we were to change the location of the Titles data from a
location on a Local Area Network to a remote Database Server, perhaps in the United
Kingdom, what would we need to change in order to accomplish this feat?
9 I want to give you a chance to work with the Implements statement here, so
that you can see how the Implements statement allows you to implement interface
inheritance. In this exercise, you’ll create a class, clsPerson, from which you will
later, in our next exercise in fact, implement another class called clsCustomer. Create
that new class called clsPerson now, with two properties called Name and Address. Then
create a method called ShowInfo (sound familiar?).
10 Now create a new class, clsCustomer, that implements the properties and
methods of clsPerson.