John W. Colby
jwcolby at colbyconsulting.com
Sat Mar 6 16:22:51 CST 2004
We have discussed why we would use classes and seen demos of text box code
running natively in a form, then the equivalent written as a control class
for a text box. We then looked at these classes dimensioned in the form
header and instantiated directly and using class factory functions. The
next step is what I call the supervisor class. Supervisor classes are
classes that load and supervise other classes.
Notice that the forms built in class is doing a lot of work setting up
these control classes. It will only get worse as we add new control type
classes for combo boxes, check boxes etc. and then try to load a class
instance for each of 50 controls. All of the code we saw in frmPeople4 will
have to be replicated over and over in each form you want to build. So why
dont we build a form class of our own and place this code in our class.
Then we just set up and tear down that supervisor class in each form.
Basically all we do is transfer the code we have in the form into a class,
with an Init() and Term() method to call for setting up and tearing down our
new class form.
OK, so the code for the new dclsFrm (form class) can be seen in dclsFrm in
the demo database and looks like this (comments in-line):
The dclsFrm form class (Supervisor)
Option Compare Database
Option Explicit
By now you should be getting used to class headers. Here we dimension the
Event Procedure string, the collection and the form variable. Notice that
we are declaring the form variable WithEvents meaning that this class will
be sinking form events inside this class
Private Const mstrEventProcedure = "[Event Procedure]" 'A constant to hold
the string [Event Procedure]
Private mcolClasses As Collection
Private WithEvents mfrm As Form
We havent seen this yet. All classes may have an Initialize and Terminate
event which are run automatically as the class opens. It is roughly the
equivalent of a forms Open and Close events. I use them to run the set
statements for all objects that the class will use, just to get these
statements grouped together and run.
Private Sub Class_Initialize()
Set mcolClasses = New Collection
End Sub
In the Terminate of the class we call the classes Term() method which the
developer writes.
Private Sub Class_Terminate()
Term
End Sub
The init simply takes the passed in pointer to the form and stores it in our
local variable. We now have a pointer to the form so we can call
FindControls to iterate the controls control collection and load the control
classes. And finally, set the forms OnClose property so that we can sink
the Close event in this class.
Function Init(lfrm As Form)
Set mfrm = lfrm
FindControls
mfrm.OnClose = mstrEventProcedure
End Function
Term calls ClsDestroy which cleans up the control classes, then sets out
pointer to the form to nothing. Forgetting to clean up pointers to
collections and controls is one of the prime reasons for memory leaks and
Access failing to close.
Function Term()
On Error Resume Next
ClsDestroy
Set mfrm = Nothing
End Function
The forms Close event will transfer control to this event sink, where we
call this class term event to clean up our pointers
Private Sub mfrm_Close()
Term
End Sub
The class factory we saw in Classes why would we use them. It simply
creates an instance for the text box passed in and stores the pointer to
that class in the collection.
Function ClassFactory(txt As TextBox)
Dim ldclsCtlTextBox As dclsCtlTextBox
Set ldclsCtlTextBox = New dclsCtlTextBox
ldclsCtlTextBox.Init txt
mcolClasses.Add ldclsCtlTextBox, txt.Name
End Function
Class destroy cleans out the control class collection, calling term of each
control class then destroying the pointer to that class.
Function ClsDestroy()
Dim obj As Object
On Error Resume Next
For Each obj In mcolClasses
obj.Term
Set obj = Nothing
Next obj
Set mcolClasses = Nothing
End Function
FindControls is the major enhancement that the dclsFrm brings to the table.
This function iterates the forms controls collection. It examines each
control for its control type and instantiates a class instance for each
control. The type of control class instantiated depends on the controls
controltype property. So far we only have a control class for text boxes
but we will soon be adding more control classes for combos, check boxes,
lists and so forth. This function will then be used to load those other
control classes as well.
'
'THIS FUNCTION SEARCHES THE FORM FOR CONTROLS OF VARIOUS TYPES.
'
'The framework will build functionality using controls with consistant
naming. We can
'find these controls simply by searching the form's control collection
looking for
'controls named something specific. In other cases we might want to load a
class
'to handle a specific type of control - perhaps a text box class or a
dependent combo
'class.
'
'This function will be used to do the search through the form's control
collection
'looking for controls that we know how to handle and setting up the hooks to
handle
'those controls
'
'Parameters:
'Created by: Colby Consulting
'Created : 4/26/98 10:23:44 AM
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
Case Else
End Select
End With
NextCtl:
Next ctl
Exit_FindControls:
On Error Resume Next
Set ctl = Nothing
Exit Sub
Err_FindControls:
Select Case Err
Case 0 'insert Errors you wish to ignore here
Resume Next
Case Else 'All other errors will trap
Beep
MsgBox Err.Description, , "Error in function Forms.FindControls"
Resume Exit_FindControls
End Select
Resume 0 'FOR TROUBLESHOOTING
End Sub
The forms built-in class
That takes care of the dclsFrm form class. To use this class we modify our
forms built-in class as follows See frmPeople6:
Option Compare Database
Option Explicit
Dimension our new form class
Public fdclsFrm As dclsFrm
Set it and initialize it, passing a pointer to the form
Private Sub Form_Open(Cancel As Integer)
Set fdclsFrm = New dclsFrm
fdclsFrm.Init Me
End Sub
If you remember the first form with the control handling built into the form
s class you can see we have dropped the total code resident in our form by
a huge amount. Additionally, should we have 1 or 100 controls on the form,
the generic processing code on the form doesnt expand by even a single line
of code. Now your form can concentrate on functionality that is specific to
the application rather on functionality that is generic to all applications.
The dclsFrm is one of a handful of supervisor classes we will look at as we
work on the framework.
We now have what could be called a minimal framework. We have a form class
that you can build upon to hang form functionality on, and we have one
control class dctlTextBox that demonstrates the concept of the control
scanner in dclsFrm loading control classes for us automatically. This class
can have additional functionality added to it to allow it to automatically
perform other generic functionality for our applications. In fact we will
be adding more functionality to both of these classes in addition to adding
other controls classes. More importantly though, we need to get a framework
foundation established, which will take the shape of another supervisor
class which I call clsFW.
Note: frmPeople5 uses the forms built-in class Close event to cleanup the
pointer to dclsFrm which causes a page fault. I just wanted to point out
that you cant do this in Access 2K, and that this is a bug in Access that
was fixed in AccessXP.
John W. Colby
www.ColbyConsulting.com