[AccessD] Classes and Events - clsGlobalInterface

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.



More information about the AccessD mailing list