[AccessD] Framework Discussion - More classes

John W. Colby jwcolby at colbyconsulting.com
Mon Mar 8 21:12:22 CST 2004


We left off our discussion having built a framework class and a text box
class.  The text box class did nothing more than change the back color of
the text box to cyan as it got the focus and back to the original color as
it lost the focus.  The form class did nothing more than scan the form’s
Control collection looking for controls and instantiating classes for any
controls that we had a class for – just the text box class so far.

For demo code look in C2DbFW3G-DemoCtlClassV2.zip for DemoCtlClassV2.mdb.
This document can be found in there as well.  I am deleting the old forms to
clean up the demo database and let us focus on the new stuff.

Today I am going to add a couple of more classes to our framework and tie
them in to what we are doing.  First I am going to add a combo class.  The
combo class will also do nothing more than change the back color as it gets
/ loses focus.  I know this may be boring but it demonstrates clearly that
the classes are being instantiated by the form’s control scanner and that
the control classes do indeed load, sink events for their respective control
and perform some action.  Believe me, we have come a long way towards
understanding how classes work, how withevents work, and how classes can
play and work together.
The dclsCbo

The combo class is going to start out looking very much like the text box
class.  In fact I took the text box class, and used the editor to replace
text with combo to create a new class.

Option Compare Database
Option Explicit

The header just dimensions the combo control private, withevents, creates a
constant string, a constant back color, and a variable backcolor to store
the original backcolor.

Private WithEvents mcbo As ComboBox                      '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

The init is passed in a pointer to a specific combo and stores that in our
private combo variable.  It also sets up the combo’s  OnEnter and OnExit
properties.

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

The term does nothing more than clean up pointers to objects, in this case
the combo that this class manipulates.

'
'The term function of every class cleans up all pointers to objects stored
in our class
'
Function Term()
    Set mcbo = Nothing                  'Set the pointer to the control to
nothing
End Function

And finally the Enter event of the combo control changes the back color,
storing the original backcolor.

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

And the OnExit changes the combo’s backcolor back to the original color.

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

All of this looks almost identical to the text box class.

dclsFrm Changes
The only change we have to make to dclsFrm is to add a new class to our case
statement in the control scanner to recognize and instantiate classes for
combos.

Private Sub FindControls()
On Error GoTo Err_FindControls
Dim ctl As Control
Dim intIndex As Integer
Dim col As Collection

    Set col = New Collection

    For Each ctl In mfrm.Controls   'Find each control in the form's control
collection
        With ctl
            Select Case .ControlType    'Determine it's type
            Case acTextBox  'Find all text boxes and load class to change
backcolor
                mcolClasses.Add New dclsCtlTextBox, .Name
                mcolClasses(.Name).Init ctl
            Case acSubform
            Case acTabCtl   'tab pages are handled in the tab control
            Case acOptionGroup
            Case acCheckBox
            Case acOptionButton
            Case acCommandButton
            Case acToggleButton
            Case acListBox
            Case acComboBox
                mcolClasses.Add New dclsCtlCbo, .Name
                mcolClasses(.Name).Init ctl
            Case Else
            End Select
        End With
NextCtl:
    Next ctl
Exit_FindControls:
    On Error Resume Next
    Set ctl = Nothing
Exit Sub

Notice the case acComboBox we added in the new dclsCtlCbo.  One thing to
notice about this syntax is that I add the class into the collection:

mcolClasses.Add New dclsCtlCbo, .Name

Notice that I use the new keyword right in the Add method of the collection
to return a pointer to the object – the dclsCbo in this instance.  Then
notice that I use the control’s name as the key into the collection.  This
enables me to look up the class for any control (assuming it has one in
there) just by indexing into the collection with the control’s name.

The next thing to notice is that if an object is stored in a collection, the
methods and properties of the object in the collection can be referenced
directly in the collection, i.e. you don’t need to bring the object back out
of the collection and put it in a variable to get at the object.

mcolClasses(.Name).Init ctl

Here I initialized the class for the object that was stored in
mcolClasses(.name) where .name is the control’s name, .init is the init
method of the class in the collection, and ctl is the control being passed
in to the init method of the class in the collection.

This may seem obtuse but it saves us a lot of dimensioning variables,
setting the variable to the class and using the variable to get at the
class.

So we have built a new class to handle any generic combo functionality we
may desire, and we have added it into dclsFrm with two lines of new code.
I will be adding classes for lists, check boxes, radio buttons etc but for
now combos and text boxes are enough to display the power of the system
without making us wade through classes that we won’t be using (yet).

To see the new dclsCbo function, simply open frmPeopleV2 and tab through the
controls.  The combos are now changing background color as they get and lose
the focus in the same manner that the text boxes do.

I will be adding a new, more useful functionality to the combo class in the
next article.

dclsTimer – A timer class

The timer class neatly demonstrates the reusability and encapsulation
advantages of classes.  The actual code that does the timing was lifted from
(I believe) ADH, but it was a single instance thing, i.e. it only had a
couple of functions and a variable to store the timing stuff.  Thus it could
only time one thing at a time.  By turning it into a class, we encapsulate
the code, the documentation of how it works, and the variable(s) that make
it all work.  It also demonstrates that a class doesn’t have to be complex
to do something useful for us.

In order to use this I will time how long the form is open, and also how
long it takes to do the control scanner stuff.

Option Compare Database
Option Explicit

In the timer class header I declare a function that calls the windows API
getting a long integer representing timer ticks – measured in milliseconds.
I also dimension a long integer variable to hold the start time counter tick

Private Declare Function apiGetTime Lib "winmm.dll" _
                                    Alias "timeGetTime" () As Long

Private lngStartTime As Long

There are only two methods of the class, StartTimer which stores the start
time (tick count) and EndTimer which returns the difference between the
start tick count and the end tick count.

'THESE FUNCTIONS / SUBS ARE USED TO IMPLEMENT CLASS FUNCTIONALITY
'*+Class function / sub declaration
Sub StartTimer()
    lngStartTime = apiGetTime()
End Sub

Function EndTimer()
    EndTimer = apiGetTime() - lngStartTime
End Function
'*-Class function / sub declaration

Just remember that the results are in milliseconds (thousandths of a
second).


And finally, I will add this in to dclsFrm to allow us to time a couple of
things.  First I declare a timer variable in the dclsFrm header to hold a
timer that can be referenced from anywhere in the class.

Option Compare Database
Option Explicit

Private mclsTimer As clsTimer

In the Initialize method of the class I instantiate the class, then start
the timer.

Private Sub Class_Initialize()
    Set mclsTimer = New clsTimer        'Instantiate a timer to time how
long the form was open
    mclsTimer.StartTimer                'Start the timer running
End Sub

In the term() method I debug.print the timer value.  This displays how long
the form was opened in milliseconds.

Function Term()
    'Print the time the form was open
    Debug.Print mfrm.Name & " was open for " & mclsTimer.EndTimer & "
milliseconds"
    Set mclsTimer = Nothing 'Then destroy the timer classEnd Function

Now we are going to use another timer instance to time the control scanner.
To do this we dimension a timer class local to the scanner function.

Private Sub FindControls()
On Error GoTo Err_FindControls
Dim ctl As Control
Dim intIndex As Integer

Dim lclsTimer As clsTimer

	Set lclsTimer = New clsTimer
    lclsTimer.StartTimer
    For Each ctl In mfrm.Controls   'Find each control in the form's control
collection
        With ctl

And in the function’s Exit I debug print the time and destroy the timer
class.

Exit_FindControls:
    On Error Resume Next
    Set ctl = Nothing
    Debug.Print mfrm.Name & "'s control scanner took " & lclsTimer.EndTimer
& " milliseconds to run"
    Set lclsTimer = Nothing
Exit Sub

Note: I ran the form on my 2.5 ghz AMD development machine and the scanner
class took 1 millisecond to complete.  In that time the scanner loaded
classes for nine controls.  From my debug window:

frmPeopleV2's control scanner took 1 milliseconds to run

This timer will be useful to us as we build more and more functionality into
the various control classes.  We want to just monitor how long it takes our
form to load all of its classes so that if that time jumps to an
unreasonable amount of time we are aware of it and can investigate why.

Summary
In this article we have added a new control class to handle future combo box
functionality, as well as a timer class.  These classes demonstrate once
again exactly how easy it is to set up a class and use it.  dclsFrm was
modified to allow instantiating dclsCbo for every combo that the control
scanner found.  We also set up two timer classes in various places, one
global to the form to time the amount of time the form was open, and another
local to the control scanner to time how long it takes to load all of the
control classes.

We now have four classes working together to create a system that allows a
form to find and load a class for all of its controls, the controls can
perform functionality automatically, and timers can tell us how long various
actions take to complete.

John W. Colby
www.ColbyConsulting.com





More information about the AccessD mailing list