jwcolby
jwcolby at colbyconsulting.com
Thu Mar 5 07:18:30 CST 2009
clsGlobalInterface
It is often the case that we need common code in a set of classes. As an example I have found
instances where classes contained in a collection were not destroyed properly by the garbage
collector when the pointer to the collection was simply set to nothing. I have run into places
where I had to have a term event of a class, and specifically call the term event in order for the
cleanup in that term to run to allow the class to be destroyed.
So one “common code” thing I do is to build a function that accepts a collection as a parameter, and
then iterates the collection getting a pointer to whatever is in the collection, calling the term
event (if any) and then deleting that object from the collection.
The fact that Access does not have inheritance poses problems when trying to add such common code to
all classes. In languages with inheritance we would simply back up the chain to the class that is
the parent to all of the classes we want to have the common functionality, and add the functionality
in that parent class. That functionality would then be available to all of the descendents.
In Access, where we do not have inheritance, there are several strategies for dealing with this
“common code” issue. One is to simply embed the code in each class. This method has the obvious
“fix the bug in one place” problem. Another method is to place the common code in a module where it
is called by all classes. This is a workable method but one of the precepts of class programming is
that the class should be portable, and preferably stand-alone. Having code for the classes in
modules immediately creates the issue of “what module has to go with this class”.
Another method is to use a “helper class” which is referenced by every class that needs the helper
code. While this violates the “stand-alone” concept it does at least start to centralize all of the
helper code into a specific place, which soon becomes engrained in the developer’s mind. Over time
I have used all of the strategies mentioned above, and in fact still do use all of the strategies.
However I want to discuss the helper class strategy in more detail in this lecture.
Create the Class
• Open the demo database.
• In the menu, click Insert / Class Module.
• Immediately save the module as clsGlobalInterface.
• In the header of the module insert the following code:
Header
Private mobjParent As Object
This provides clsGlobalInterface a method of manipulating the parent class if desired.
Private mcolPreviouslyHookedEvents As Collection
Provides a collection to hold the event property value for previously hooked events
Private Const cstrEvProc As String = "[Event Procedure]"
The constant to be placed in the event property.
Initialization
• In the body of clsGlobalInterface insert the following code:
Private Sub Class_Initialize()
Set mcolPreviouslyHookedEvents = New Collection
End Sub
Private Sub Class_Terminate()
Set mcolPreviouslyHookedEvents = Nothing
End Sub
Function mInit(lobjParent As Object)
Set mobjParent = lobjParent
End Function
The class event stubs initialize and destroy the collection. mInit stores the passed in pointer to
the parent object into a private object variable.
Properties
• Immediately below mInit() place the following code:
Property Get colPreviouslyHookedEvents() As Collection
Set colPreviouslyHookedEvents = mcolPreviouslyHookedEvents
End Property
This property allows the parent class to access the collection of functions found in its properties
(if any).
Events
Because this class does not sink events for any objects, there will be no event sinks.
Methods
'
'Allows hooking a property by passing back "[Event Procedure]".
'If the property already has a hook using the old style =SomeFunction()
'Then we don't hook the property but instead pass back the value of the property
Function mHookPrp(prp As Property) As String
If Left(prp.Value, 1) = "=" Then
mHookPrp = prp.Value
mcolPreviouslyHookedEvents.Add prp.Name & prp.Value, prp.Name
Else
prp.Value = cstrEvProc
End If
End Function
This code is going to provide common functionality for all classes which use clsGlobalInterface. A
subset of all the classes we will design will be wrappers for forms and controls. These classes
will likely sink events, and as such they must hook the events that they want to sink. This code
performs this common event hooking process, and makes sure that if an event is already hooked with a
call to a function, that call is not deleted in favor of our event hook. It also stores the values
of all properties which are already hooked with functions using the =MyFunction() method common in
early Access databases.
'
'Empties out a collection containing class instances
'
Public Function ColEmpty(col As Collection)
On Error GoTo Err_ColEmpty
Dim obj As Object
On Error Resume Next
For Each obj In col
obj.mTerm
Next obj
On Error GoTo Err_ColEmpty
While col.Count > 0
col.Remove 1
Wend
exit_ColEmpty:
Exit Function
Err_ColEmpty:
Select Case Err
Case 91 'Collection empty
Resume exit_ColEmpty
Case Else
MsgBox Err.Description, , "Error in Function clsSysVars.colEmpty"
Resume exit_ColEmpty
End Select
Resume 0 '.FOR TROUBLESHOOTING
End Function
This function simply iterates a collection calling the mTerm event. Once all objects in the
collection have had their mTerm method called, the collection is emptied.
• Compile and save clsGlobalInterface
So, we now we have a class specifically to hold code that may be used in other classes. In order to
use the class we simply dimension a private variable at the top of each class that will use
clsGlobalInterface, initialize it, and start calling the methods as desired.
Modifications to clsFrm
In order to use clsGlobalInterface we have to modify each class that will use it. We will start
with clsFrm.
• Open clsFrm
• In the header of clsFrm insert the following code:
Header
Private mcolCtls As Collection
Private mclsGlobalInterface As clsGlobalInterface
This simply adds a variable to hold a pointer to an instance of clsGlobalInterface to the already
existing header code..
Properties
Next, add the following property code immediately below the existing mInit().
Property Get cGI() As clsGlobalInterface
Set cGI = mclsGlobalInterface
End Property
This gives us a property to return a pointer to our new clsGlobalInterface instance. I placed this
code here in the doc because in the Initialization you will refer to this property.
Initialization
• Add the following code to the body of clsFrm
Private Sub Class_Initialize()
Set mcolCtls = New Collection
Set mclsGlobalInterface = New clsGlobalInterface
End Sub
Private Sub Class_Terminate()
Set mcolCtls = Nothing
Set mclsGlobalInterface = Nothing
End Sub
Here we added code to create and destroy an instance of clsGlobalInterface.
The next thing we are going to do is modify the code in the mInit of clsFrm. Originally we used the
following code which you should still see in clsFrm.mInit:
mfrm.BeforeUpdate = cstrEvProc
mfrm.OnClose = cstrEvProc
Now we want to change this to:
Function mInit(lfrm As Form)
cGI.mInit Me
Set mfrm = lfrm
With mfrm
cGI.mHookPrp .Properties("BeforeUpdate")
cGI.mHookPrp .Properties("OnClose")
End With
The first thing we do is to initialize cGI by calling .mInit and passing the init code a reference
to Me. Notice that Me is a reference to the current class, in this case clsFrm. You are probably
accustomed to using Me as a reference to a form however in fact even there it is really a reference
to the form’s class.
We set up a “with end with” construct to manipulate the mfrm.Properties collection, then pass in the
same two properties we had previously hooked. The difference now is that the cGI.mHookPrp will
handle the hook and handle leaving the property alone if it already has a function hooking the property.
Summary
In this lecture we have discussed the problem with reusable code and classes, and a couple of
different strategies for handling the problem. We then examined the strategy of a helper class
which is used by every class that needs some common functionality. We created clsGlobalInterface,
designed to implement a helper class, and provided a couple of methods, one for a consistent method
of hooking object events as well as a method for emptying a collection.
While there is no optimum solution to the common code problem in Access, the helper class is one
solution that works well for staying with the class encapsulation strategy. It gives you a place to
put common code and more importantly variables that are required for every class.