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 forms
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 forms 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 combos 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 combos 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 controls 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 controls 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 dont 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 controls 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 wont 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 doesnt 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 functions 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