Classes and Class Modules 101a
Concepts for
the novice.
What are they
and what are they good for?
(Firstly, for the purists, for the sake of discussion we
will acknowledge but ignore the fact that many visual
classes are not persistent on access forms but are
instanced only when needed. They are merely 'painted on'
pictures the rest of the time. We are also only
discussing classes as implemented by MS Access and not
those of a more OOP nature.)
The Class
The popular definition for a class is an
'object' that encapsulates code and data and have been
variously described as 'cookie cutters' or templates. Not
very enlightening ... so lets have a look at Standard
module procedures and variables first.
The variable.
Lets look at the plain old integer variable. A small
space of memory that can hold numbers. A=5. Print A. This
looks in the memory and prints the value. Not much to
understand. But imagine, if when setting A=5, it
automatically squared itself! ... or perhaps a string
variable that automatically converts to upper case! Now
we have code in there as well. (code we might
not have written). OK, Lets move on and sideways a bit.
A Standard Module.
Lets say you create a standard module that has two public
variables A and B, and one procedure, Square_It (B = A*A). To use it, you might
set A = 2 and then call Square_It. The result 4 can be read from
variable B. Easy.
Now lets say you have a piece of code that uses this Square_It procedure, but it in turn calls
another piece of code that also uses Square_It. You call Square_It and get your answer, then the code
moves on. If you re-read B
it may not the be same value that it originally was,
because the other later procedure (or form or report)
also used it.
Let's
look at an alternate behaviour. Take the Windows game
Solitaire. You run Solitaire and get half way through
losing the game<g> and stop. Leaving it open you
can run another and another. Each has a different
'state'. You can run many copies of Solitaire (or
calculator..whatever) and they are all in various states.
You can't easily do this sort of thing with code
in a standard module It exists only once, One copy!
Referring back to our Square_It function, you sort of
need to create new modules with new As & Bs on the
fly. This is where class modules come in.
A Class Module
With your newly created Class module, you still declare
your public Variables (A & B) and your public
Procedure (Public Sub) Square_It. You save the class
module as clsMyMath. Now when you use it you dimension a
variable M as type clsMyMath and use the Set and / or New
keywords to create it. You then ... M.A = 2, M.Square_It,
answer = M.B
Looks almost the same...but...see below for the magic.
Dim M1 As New
clsMyMath, M2 As New clsMyMath
M1.A = 2
M1.Square_It
M2.A = 44
M2.Square_It
Debug.Print M1.B
Debug.Print M2.B
So you will see from the above, we now have two
different Square_Its available.
Each with different answers that will be available until
the references M1 and M2 go out of scope. This is where
the 'cookie cutter' or template ideas
come from.
The
Instance.
The cookie cutter metaphore is not a bad one. The key to
understanding this is that the code and attributes of
your class (the class module) are just blue prints. The
class has no reality until it is instantiated.
Dim M1 As New
clsMyMath
or
Set M1 = New
clsMyMath
It is at this point that the bubble of code, the object,
is created in memory. Some prefer to call it an object
now that it is 'real'. Object - class instance, it's all
the same.
Pointers, References and Variables with
class objects.
The difference between Pointers (references) and common
variables are often overlooked in Access / VBA.
The M1 variable is just a 'pointer' or reference
to the object. It is not the object nor does it
contain the object.
Dim M1 As New
clsMyMath
Set MI5 = M1
Set Cls23b = MI5
All the variables above, M1, MI5, Cls23b all point to the
same object. Changing a property on M1 will change the
property on all of them because they only point to the
object. Where as A = 5 and B=A remain two seperate
variables. Changing A or B after the fact will not affect
the other. In fact the correct (although now redundant)
method for assigning A is...
Let A = 5
where as the method for assigning MI5 is
Set MI5 = M1
Set and Let
This difference in assignment syntax is because of the
differences in the fundamental assignment type.
If I give you A, I give you the actual item. If I give
you M1 I give you a map of where to find a particular
instance of clsMyMath. The keywords ByRef and ByVal when used with procedures are very similar
in concept. See Access help on these two terms as they
are crucial to the way variables are handled
when passed to procedures.
Bear in mind, the concepts, terms and sytax mentioned
above apply every bit as much to forms, reports,
controls, recordsets and just about every other class
object you will strike in Access / VBA.

Complexity.
Now it is easy to imagine that the clsMyMath may in fact
be a very complex calculator that works out scaling
commission payments and, not only provides the amount of
the payment, but also supplies percentages, sales totals,
profits etc in various public variables (properties). It
is chock full of code inside. So when ever you need a
'Commission Calculator' you dimension a variable as
clsCommissionCalc, use it ... then throw it away, safe in
the knowledge that any other code running a
calculator will not interfere or be interfered with. It
is the same as loading a new Solitaire - it wont effect
the state of the existing one.
Dim CommCalc As New clsCommissionCalc
CommCalc.Salesman = "Terry"
Debug.Print CommCalc.Commission
Debug.Print CommCalc.SalesTotalMonth
Debug.Print CommCalc.SalesTotalYear
Debug.Print CommCalc.ProfitContributionPercent.
Notice in the
above, we did not actually tell the class to calculate!
This is because we can use a Property Set procedure to
run calc code when the 'Salesman' property is set.
You can also pass a 'live'
class to another procedure.
Dim CommCalc As New clsCommissionCalc
CommCalc.Salesman = "Terry"
Debug.Print CommCalc.Commission
Debug.Print CommCalc.SalesTotal
Call SomeOtherSub (CommCalc)
...
...
Private
Sub SomeOtherSub
(TheCalc As
clsCommissionCalc)
If TheCalc.Salesman =
"John" Then
...dosomething
Digressing a
bit...
So you see, they are just like little applications. You
see this sort of thing all through windows. It's just so
common you don't notice it. Most of the classes we
perceive have a visual aspect to them as well. Take the
command button you put on a form. This is a class. In
this case class CommandButton. It has properties and
methods. Properties are like public variables and the
methods are like public Subs. You can think of even these
button 'objects' or classes as small self-contained
programs as well. When you click the button it tells
Access 'I have been clicked!' via an event procedure.
When you change the font, Access tells the button
to change it's font. This is subtly different from Access
changing the buttons font. Without digressing into the
structure of Windows, take my word for it, you can think
of all these objects on the screen as separate little
programs sending messages (commands and data) to each
other. You can read more about messaging and an overview of the
way Windows works on MSDN. So returning to Access class modules, it
is not hard to imagine, if Access allowed it, and if you
had the API skills, you could draw small objects on the
Access form that could be considered part of your class.
You set properties of the class instance, the code in the
class instance sends and recieves Windows messages and
alters the image / object in the form and maybe does
other stuff as well.
You can see this at work to an extent with much of the
Access code available from Stephen
Lebans's Website.
If you really want to look under the hood into Windows
and classes, and maybe 'roll your own' controls, visit
The Microsoft VB5 Control Creation
Edition site and
download the free version of same.
What else can they do?
In Access 2000 and later you can use the 'RaiseEvent'
keyword in your class. This allowes classes to interact
with your code (code on a form for instance) via event
procedures. Just like buttons or forms whatever. You
declare the events in the class declarations section...
Public Event CalcError(ErrorNumber As Integer, Cancel As Integer)
In your class code, if something happens, you might use
RaiseEvent to call an event procedure on your form code.
The form code can set the cancel to True. Back in your
class you can read intCancel to see if it was changed by
the form code.
RaiseEvent CalcError(intError, intCancel)
How do you get
these 'events' to come up in the form code?
If you are in the code window for a form, and you select
'Form' in the left hand combo at the top of the
code editor it will create a form load event (if it
doesn't exist). The right hand combo lists all
the possible events for the 'Form' object. Selecting one
will create the event procedure. Now!
if you dimension your class in the form module as ...
Dim WithEvents CommCalc As clsCommissionCalc
you will find CommCalc now appears in the left hand
combo. It also has a list of events in the right hand
combo and will create an event procedure when selected.
In this case you will see 'CalcError'.
An easy way to comprehend this if your head is spinning a
bit, it to imagine classes as advertising all their
properties, events, methods and constants to any
application wanting to use the class. This is how the
code on your access form 'knows' about what events are
available from your class. It is also how 'IntelliSense'
knows what to 'pop up' when you are writing code and the
object browser knows just about everything. So imagine a
conversation between VBA and the class as your are
writing the form module code.
VBA...'Ah! a new
class, do you have any events perchance?'
Class...'Yes, I do
in fact. The CalcError event'.
VBA...'Ah good,
I'll list it in my events combo'.
Class...'Yes, do
that, by the way, you will let me know the forms
procedure that I must call when I fire this event?'
VBA...'Of course,
I'll give you the address as soon as Fred here sets the
event and writes the code and I compile up the form
module code. By the way, have you got your list of
properties and types handy?'
Class...'Yes, here
it is ol' boy.'
VBA...'Ah, thank
you. Well the codes written, he's about to open the form,
I better compile...lets get on with it shall we?'
Class..'Righto, no
rest for the wicked!'
Of course all this happens using the OLE 2.0 or Windows
Com framework for dlls and active-x classes, but you get
the picture. :-)
This can be also made to work the other way
around. Setting the event procedure properties
on a form to [Event Procedure] and having a class module
instance that references the form using a WithEvents clause in the declaration, will
cause the event procedure in the class module to fire
when the various form events are activated (Keydown,
MouseMove, Close etc). The combos for object and events
are present when working with your class module as they
are with the form, it's just in this case the form
variable in is the object combo (left hand) and when
selected you see Form! events in the event combo (right
hand). You can even have two or more event procedures for
the one event. One in the form module and another in your
class module.
In light of all this, it pays to think of the form's
code module as just another class module.
Access just hooks up the visual control class to a class
module, sets the GUID and presents it to you as a fait
accompli. (Although setting the 'Has Module' to No will
leave only the visual 'light weight' form). In fact, in
the 'olden days' of Access 1.0 and 1.1, there was
no form code module (let alone class
modules), only standard modules. We had to use
expressions such as =MyFunction() or a macro in the event
procedure property.

See http://www.papwalker.com/public/classsink.mdb for an example of form event
sinking.
A Class also has it's own events, the Initialise and
Terminate events (like the form Open and Close) that
occur within the class module code, so when the class is
first created, the Initialise event fires. Here you can
open recordsets, set up variables etc etc... and when it
dies (goes out of scope) or is set to 'Nothing' the
Terminate event can close off and 'clean up' anything
that needs doing.
BTW. A form is a class. It is not
only possible, but easy to open 100 'Form1's. All 'clones' of
the original but each having a separate life and perhaps
being on different records etc. You open a Customer
Enquiry form and are half way through doing something,
you can open a another 'clone' look at something else,
close it, and return to your original. This is done in
almost the same manner as creating new instances of your
new class module object.
I hope this encourages novices to investigate
the possibilities of using and understanding classes
For forther insights on class modules see...
Custom Collections, Procedure Attributes and other
obtuse things
Variable
Subrange checking using classes
:-)
For further information on this article contact
peter walker papwalker@ozemail.com.au
|