[AccessD] Building a control class

jwcolby jwcolby at colbyconsulting.com
Mon Mar 9 10:31:21 CDT 2009


A.D.

WOW.

I have never done side by side comparisons of this stuff in different versions of Access.  Good job 
on that.

 >     1 - Hooking events:
 >     There should be no harm in hooking all events available in a control, by assigning "[Event 
Procedure]" wherever the value is found blank. As and when particular events are required to be 
sunk, pertinent stubs can be inserted in the class module, without having to worry about matched 
hooking.

In fact having the event "hooked" and not having it hooked cause different actions by the object 
itself.  If the property is set to [Event Procedure] the object actually raises the event, whereas 
if the property is blank, then the object does not even raise the event.  Thus by implication there 
is some small overhead for each event "hooked" because that code has to run back in the object itself.

Obviously if there is no event stub then nothing actually happens, however the event is still being 
raised.  The only case where I personally believe this might matter is in the mouse move events. 
Each object (and the form) raises mouse move events whenever the mouse is over the control or form. 
  Thus events would be raises constantly even when there is no code that you want to run.

In my form I actually created an event stub for every event and placed a debug.Print in the event 
bracketed by conditional compilation statements so that I could study the event sequence.  The 
MouseMove was the only event that I did not hook, simply because it caused hundreds of debug.print 
statements, obscuring what I wanted to see.

 >     2 - Precautions for ensuring proper termination of all classes:

My gut feeling is that Access 2000 had a bug having to do with the execution thread not terminating 
properly in all cases.

If the clsFrm has code control and the form's unload event fires, AND the code in the Code Behind 
Form Close event sets the pointer to clsFrm to nothing then bad things happen.

Remember that even though VBA is single threaded, events can preempt running code.  So code can be 
running in the clsFrm and the OnClose event in the main form's Code Behind Form can fire.  If CBF 
then set the clsFrm pointer to nothing, when the OnClose event in  CBF tries to return to the 
previously running code... that code was in the class that you just set the pointer to nothing.  My 
guess is that in Access 2000, the garbage collector unloaded the code as SOON AS the pointer was set 
to nothing and there was no place for the code execution to return to.

Kind of a sticky situation.

It appears that they fixed that bug in Access 2002 ("XP" version).

 >     2.2 - Forms with subforms:
 >     2.2.1 - In Access 2000, if the parent form as well as the form serving as its subform are 
both set to the form class (even though these are different instances of the same class), 
application freeze / crash can take place on eventual closing of parent form. (No such problem in 
Access 2003).
 >     2.2.2 - The above problem can be overcome by off-loading the SourceObject of subform 
controls, BEFORE setting the form object variable (pointer to real form) to nothing.

As you no doubt know, subform load / unload is backwards from what you would expect.  Subforms load 
before the main form.  This means that clsFrm in every subform loads before the main form's clsFrm. 
  This can cause issues if you want to reach up into the main form for some reason as the subform 
loads... it can't be done at that point.

In general though, you need to unload ALL CLASSES that hold pointers to objects on the form before 
actually closing the form itself... and I understand you were not discussing that specifically but 
it is an important point to make.  A form cannot correctly close if a pointer to any object on the 
form is still set.  The form APPEARS to unload but doesn't.  It becomes invisible, but is in fact 
still loaded.  Did they fix that in XP?  No se!

Part of my problem is than one of my major clients still uses 2000 and shows no sign of EVER moving 
on.  So I try to program to that platform, which means I have to handle scenarios that I would not 
if I could program to 2002.

 >     2.3.3 - In conclusion, it seems preferable not to set form class variable to nothing in real 
form's module.

Correct, IIRC it can cause page faults.  Clean up well inside of your classes, then let the garbage 
collector actually set the clsFrm pointer to nothing as the form closes.

 >     3.2 - Use of general module as global interface would minimize the extent of interference to 
existing code in client classes. The interface module could be named in such a manner as to reflect 
its association with the given set of classes. As an added reminder, name of this module could be 
mentioned as part of the comments at beginning of each control class.

Yes, except that the use of a class allows storage of VARIABLES specific to the class, whereas the 
use of a module ONLY ALLOWS common code.  As an example in my real framework I maintain a pointer to 
the label for a control so that I can manipulate the label in the class (turn it different colors). 
  This requires a variable to point to the label, as well as code to iterate the control's control 
collection and find the label (get it into the label pointer).  This is common code, but it is also 
common data!  By having a clsGlobalInterface for this stuff, I can place the label pointer VARIABLE 
in that class, whereas if I use a module, then I have to put a pointer variable to the label in each 
control class.  There are enough things like this that come along where a common variable is 
required, that it soon becomes apparent that the class wins.

This is experience talking!  I moved to clsGlobalInterface precisely because I noticed a growing set 
of common variables in the header of every (or most) of my control classes.  I was adding these 
kinds of behaviors to my framework and found myself opening every control class and copying and 
pasting the code and variables into each class.  YUK!

With a clsGlobalInterface, all you do is set the variables and any code in clsGlobalInterface and 
every control "inherits" the new capabilities automatically.  OK I use the work "inherits" very 
loosely but you get my meaning.

One thing I found useful is to have a parent object and a grandparent object in every class.  As an 
example the parent of a control might be a BUSINESS CLASS, the parent of that business class might 
be the clsFrm or even the framework.  In any event it is sometimes useful to be able to reach up 
into the parent, and occasionally even the grandparent.  Obviously you can walk the chain to get 
from parent to parent to parent but I just found (for my purposes) that establishing the grandparent 
is useful enough to just do it.  So in my clsGlobalInterface I have a parent and a grandparent 
variable.  EVERY class that uses clsGlobalInterface just "inherits" these variables.  I expose them 
with properties and any class that uses clsGlobalInterface can now get at its parent and grandparent.

 >     3.3 - Use of general module as global interface would also avoid the need for setting up 
parent/child linkage, leading to improved robustness. Tests show that if a class is used as global 
interface, additional precaution becomes necessary for proper termination (in the context of Access 
2000). In form class module, setting the parent to nothing, for each instance of global interface 
class, BEFORE setting the parent to nothing for each instance of control class has to be done, which 
in turn has to be carried out BEFORE setting the form variable (pointer to real form) to nothing in 
its close event.

True but once the code is written to do the cleanup it just works.  You will find that no matter 
WHAT you do, working in 2K is less robust.  I actually built a tool to record / track every single 
instance of every single class loaded and unloaded.  It is a class that I instantiate in the 
framework Init() and then everything logs in / out of that class.  This allowed me to watch the tree 
built, but more importantly to see things unload and NOT UNLOAD.  I can turn off the logging but 
turn it on every once in awhile to check that everything unloads as objects close.

John W. Colby
www.ColbyConsulting.com


A.D.Tejpal wrote:
> John, Charlotte,
> 
>     Thanks for your kind advice. I intend to follow your recommendation for adhering to one class per control type. Just for internal study and comparison, I would also try to build a single omnibus class covering all control types. Any specific instance of this class will  function just as a single control type, depending upon the argument passed to it.
> 
>     Your views are requested on certain additional points mentioned below (Access 2000 / 2003 desktop on Win XP):
> 
>     1 - Hooking events:
>     There should be no harm in hooking all events available in a control, by assigning "[Event Procedure]" wherever the value is found blank. As and when particular events are required to be sunk, pertinent stubs can be inserted in the class module, without having to worry about matched hooking.
> 
>     2 - Precautions for ensuring proper termination of all classes:
>     2.1 - Parent-Child linkage:
>     2.1.1 - As each control class has the form class as its parent, it is found that mere clearing of local object variable holding the parent property (setting it to nothing) in terminate event of control class, is by itself not adequate and does not ensure proper termination. 
>     2.1.2 - It is observed that in the form class, if parent property of each instance of child control class is set to nothing, BEFORE setting the form object variable (pointer to real form) to nothing, it ensures proper termination.
>     2.1.3 - In case of Access 2000, failure to comply with 2.1.2 above, can lead to application freeze or crash. (No such problem in Access 2003).
> 
>     2.2 - Forms with subforms:
>     2.2.1 - In Access 2000, if the parent form as well as the form serving as its subform are both set to the form class (even though these are different instances of the same class), application freeze / crash can take place on eventual closing of parent form. (No such problem in Access 2003).
>     2.2.2 - The above problem can be overcome by off-loading the SourceObject of subform controls, BEFORE setting the form object variable (pointer to real form) to nothing.
> 
>     2.3 - Role of form class object variable in real form's module:
>     2.3.1 - In Access 2000, setting the form class variable to nothing, in close event of real form's module, while the form object variable is also set to nothing in close event of this variable in module of form class, can lead to application freeze / crash on eventual closing of form. Apparently, the phenomenon is related to circular reference that exists between the form and form class. (No such problem in Access 2003). 
>     2.3.2 - On the other hand, if form class variable is set to nothing in close event of real form's module, while the form object variable is NOT set to nothing in close event of this variable in module of form class, proper termination of child control classes fails to take place.
>     2.3.3 - In conclusion, it seems preferable not to set form class variable to nothing in real form's module.
> 
>     3 - Global Interface for use in conjunction with control classes:
>     3.1 - Performance-wise, both the alternatives suggested in JC's post appear to be equally effective. In either case (whether a class or general module), the developer has to ensure that the interface is bundled along with the set of control classes.
>     3.2 - Use of general module as global interface would minimize the extent of interference to existing code in client classes. The interface module could be named in such a manner as to reflect its association with the given set of classes. As an added reminder, name of this module could be mentioned as part of the comments at beginning of each control class.
>     3.3 - Use of general module as global interface would also avoid the need for setting up parent/child linkage, leading to improved robustness. Tests show that if a class is used as global interface, additional precaution becomes necessary for proper termination (in the context of Access 2000). In form class module, setting the parent to nothing, for each instance of global interface class, BEFORE setting the parent to nothing for each instance of control class has to be done, which in turn has to be carried out BEFORE setting the form variable (pointer to real form) to nothing in its close event.
> 
>     Note - In order to verify inherent stability, the above tests have been conducted without resorting to deletion of individual objects in various collections. With the stated measures, simple setting of collection object to nothing in terminate event is found adequate. For regular use, step-wise deletion from collection, could be included as abundant precaution.
> 
> Best wishes,
> A.D. Tejpal
> ------------




More information about the AccessD mailing list