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
> ------------