[AccessD] Framework Discussion - Classes - Why would we use them

John W. Colby jwcolby at colbyconsulting.com
Fri Mar 5 21:33:52 CST 2004


Now that I have briefly described a class, lets get down to using them.
First of all, every form has or can have a built in class, one instance of
which is loaded when the form loads.  This class holds events for the form
itself such as the Open, Close and AfterUpdate event.  It can also be used
to set properties of the form itself.  For any class, the ME keyword is used
to manipulate that instance of that class.  In other words, if you need to
set the Caption of a form from code running in the form, ME.Caption = "Some
text" allows you to do so.  Since the "code behind form" resides in the
form's class, any Access developer who writes code in the form is already
using classes.

The fact that the class is part of the form is definitely nice in that you
can export that form to another database and any code in the form's module
(class) goes with it.  Unfortunately it also creates maintenance headaches
if you write very complex code in that class and then you need that same
code in another form.  Many developers simply cut and paste the code into
the next form and be done with it.  What happens though if the code has a
bug?  Now you have to open both forms and fix the bug in both forms.  What
happens if you exported the form to another project?  Now you have to open
the forms in both projects and edit the code to fix the problem.

Recognizing this as an issue, some developers develop libraries and place
such code in their own library.  This works very well (and is what I do).
Now if for example the OnEnter of a text box needs to call a function, the
function is in the library and both forms just call the same function in the
library.  If there's a bug, fix it in the library and everything that uses
that function gets fixed at once.  Much better.

However we still have the issue of the event stub itself.  In order for the
combo's OnEnter to call a method, that event has to cause code to run
somewhere.  That somewhere for 99% of Access programmers is in the form's
class of the form where the combo exists.  There is absolutely nothing wrong
with this approach, but it can get terribly messy when a large form has 10
tabs and 80 controls on it, each control firing 3 or 4 different events,
plus half a dozen events for the form, plus 30 functions for general
processing plus... well I'm sure you get the picture.  Now just trying to
work in the form becomes a nightmare, paging up and down yards of code
trying to find things.

This in fact is one of the major problems with any of the Office
Applications (Word, Excel, Access, Powerpoint) is that controls do not have
their own class like the form does.  Therefore the form's class serves as
the controls class.  Remember I said in the last email that classes should
model one object?  Already we are running into a class that is modeling a
form and also modeling many different types of controls.  Hmmm.....

The following examples demonstrate how to change the background color of
text boxes as the user moves through the controls.  Just open the relevent
form and start hitting the tab key watching the cursor as you do.

For this example see frmPeopleClasseless:

Option Compare Database
Option Explicit

Private Const mclngBackColor As Long = 16777088         'A pretty blue color
to set the text box back color to
Private mlngBackColorOrigFName As Long
Private mlngBackColorOrigLName

Private Sub txtFName_Enter()
    mlngBackColorOrigFName = txtFName.BackColor  'When we enter the text
box, save the original back color
    txtFName.BackColor = mclngBackColor     'Set the back color to our
favorite color
End Sub

Private Sub txtFName_Exit(Cancel As Integer)
    txtFName.BackColor = mlngBackColorOrigFName     'Set the back color to
the original color
End Sub

Private Sub txtLName_Enter()
    mlngBackColorOrigLName = txtLName.BackColor  'When we enter the text
box, save the original back color
    txtLName.BackColor = mclngBackColor     'Set the back color to our
favorite color
End Sub

Private Sub txtLName_Exit(Cancel As Integer)
    txtLName.BackColor = mlngBackColorOrigLName     'Set the back color to
the original color
End Sub

This code when placed in a form with a pair of text boxes called txtFName
and txtLName causes the back color of these controls to switch from whatever
they currently are top cyan when they get the focus (OnEnter) and back to
their original color when they lose the focus (OnExit).  That's kind of cool
but look at what we are already running into.  We need a variable at the top
of the form for EACH control that we want to perform this behavior for, to
save the old back color.  It's ok if we only have one or two controls but
what if we have 20 or 40?  Can you say PITP (Patuty)?  Further, we not only
need the variables to store the stuff, but we also need to set up the
OnEnter and OnExit subs for each control we want to do this.  Multiply this
times 20 or 40 and that's a REAL PITP.

Fortunately we can build a class that models a specific control, lets say a
text box.  I am going to keep this simple so that you can see the workings,
and add in the nice troubleshooting stuff I mentioned later.

This is in dclsCtlTextBox.  The following code represents a class for a text
box, simple but marginally useful.  Explanation IN-LINE.


Option Compare Database
Option Explicit

Notice that we have ONE variable to hold the back color, the constant for
the back color of our choice, and a place to store a pointer to a specific

Private WithEvents mtxt As TextBox                      'Dimension a text
box Withevents
Private Const mstrEventProcedure = "[Event Procedure]"  'A constant to hold
the string [Event Procedure]
Private Const mclngBackColor As Long = 16777088         'A pretty blue color
to set the text box back color to
Private mlngBackColorOrig As Long                       'A place to store
the original back color

Next we have an init function where we are passed in a pointer to a specific
text box.  We save this pointer to the text box to our private variable in
the class header.  We also set the OnEnter and ONExit properties of that
control to the string [Event Procedure].

'The init function of every class "initializes" the class
Function Init(ltxt As TextBox)          'Pass in a pointer to a specific
control
    Set mtxt = ltxt                     'Save that pointer to a private
variable here in the class
    mtxt.OnEnter = mstrEventProcedure   'Set the OnEnter property of the
control to [Event Procedure]
    mtxt.OnExit = mstrEventProcedure    'Do the same for the OnExit
End Function

We have a term function where we release or cleanup the pointer to the text
box control
'
'The term function of every class cleans up all pointers to objects stored
in our class
'
Function Term()
    Set mtxt = Nothing                  'Set the pointer to the control to
nothing
End Function

And finally we have the same event stubs for OnEnter and OnExit for our
specific control

'
'These are the event stubs for the control's OnEnter and OnExit events
'
Private Sub mtxt_Enter()
    mlngBackColorOrig = mtxt.BackColor  'When we enter the text box, save
the original back color
    mtxt.BackColor = mclngBackColor     'Set the back color to our favorite
color
End Sub

Private Sub mtxt_Exit(Cancel As Integer)
    mtxt.BackColor = mlngBackColorOrig  'When we exit the control, set the
back color back to the original color
End Sub

Having done that we are ready to instantiate this class once for each text
box we want to control.  I'm going to use the actual code form my demo
database which handles FOUR text boxes.  This is code in the form
frmPeople2.  The form's header now looks like:

Option Compare Database
Option Explicit

Notice that we dimension the class four times, each with a distinct name.

Private fdclsCtlTextBoxFName As dclsCtlTextBox
Private fdclsCtlTextBoxLName As dclsCtlTextBox
Private fdclsCtlTextBoxAddr1 As dclsCtlTextBox
Private fdclsCtlTextBoxAddr2 As dclsCtlTextBox

The form's Open event initializes each of these class instances:

Private Sub Form_Open(Cancel As Integer)
    Set fdclsCtlTextBoxFName = New dclsCtlTextBox
    fdclsCtlTextBoxFName.Init txtFName

    Set fdclsCtlTextBoxLName = New dclsCtlTextBox
    fdclsCtlTextBoxLName.Init txtLName

    Set fdclsCtlTextBoxAddr1 = New dclsCtlTextBox
    fdclsCtlTextBoxAddr1.Init txtAddr1

    Set fdclsCtlTextBoxAddr2 = New dclsCtlTextBox
    fdclsCtlTextBoxAddr2.Init txtAddr2
End Sub

And the form's Close cleans up behind us:

Private Sub Form_Close()
    fdclsCtlTextBoxFName.Term
    Set fdclsCtlTextBoxFName = Nothing

    fdclsCtlTextBoxLName.Term
    Set fdclsCtlTextBoxLName = Nothing

    fdclsCtlTextBoxAddr1.Term
    Set fdclsCtlTextBoxAddr1 = Nothing

    fdclsCtlTextBoxAddr2.Term
    Set fdclsCtlTextBoxAddr2 = Nothing
End Sub

That's it folks.  Notice that the individual event stubs for the controls
themselves are missing!  The reason of course is that the text box class
itself is sinking those events right inside of that class so we don't need
event stubs here.

Now I hear you already saying "yea, but there's still a lot of code just to
do the init and cleanup".  True, but there are also tricks that we can use.
See FrmPeople3.  The first is to use a class factory function such as:

Private fdclsCtlTextBoxFName As dclsCtlTextBox
Private fdclsCtlTextBoxLName As dclsCtlTextBox
Private fdclsCtlTextBoxAddr1 As dclsCtlTextBox
Private fdclsCtlTextBoxAddr2 As dclsCtlTextBox

Private Sub Form_Open(Cancel As Integer)
    ClassFactory fdclsCtlTextBoxFName, txtFName
    ClassFactory fdclsCtlTextBoxLName, txtLName
    ClassFactory fdclsCtlTextBoxAddr1, txtAddr1
    ClassFactory fdclsCtlTextBoxAddr2, txtAddr2
End Sub

Private Sub Form_Close()
    ClsDestroy fdclsCtlTextBoxFName
    ClsDestroy fdclsCtlTextBoxLName
    ClsDestroy fdclsCtlTextBoxAddr1
    ClsDestroy fdclsCtlTextBoxAddr2
End Sub

Function ClassFactory(ldclsCtlTextBox As dclsCtlTextBox, txt As TextBox)
    Set ldclsCtlTextBox = New dclsCtlTextBox
    ldclsCtlTextBox.Init txt
End Function
Function ClsDestroy(ldclsCtlTextBox As dclsCtlTextBox)
    ldclsCtlTextBox.Term
    Set ldclsCtlTextBox = Nothing
End Function

Even better we could use a collection to hold the pointers to our control
classes which eliminates the "new variable per class" syndrome as well as
much of the cleanup code.  See frmPeople4

Option Compare Database
Option Explicit

Private colClasses As Collection

Private Sub Form_Open(Cancel As Integer)
    Set colClasses = New Collection
    ClassFactory txtFName
    ClassFactory txtLName
    ClassFactory txtAddr1
    ClassFactory txtAddr2
End Sub

Private Sub Form_Close()
    ClsDestroy
End Sub

Function ClassFactory(txt As TextBox)
Dim ldclsCtlTextBox As dclsCtlTextBox
    Set ldclsCtlTextBox = New dclsCtlTextBox
    ldclsCtlTextBox.Init txt
    colClasses.Add ldclsCtlTextBox, txt.Name
End Function
Function ClsDestroy()
Dim obj As Object
    For Each obj In colClasses
        obj.Term
        Set obj = Nothing
    Next obj
    Set colClasses = Nothing
End Function

Even BETTER... we could build a form class that did all this for us!!!

But that is for another day.

Go to my site and click on C2DbFW3G to go to a page I have set up for the
downloads.

John W. Colby
www.ColbyConsulting.com





More information about the AccessD mailing list