[AccessD] Building a control class

Charlotte Foust cfoust at infostatsystems.com
Mon Mar 9 10:45:49 CDT 2009


>> One thing I found useful is to have a parent object and a grandparent
object in every class. 

John,

I invite you to also consider the usefulness of a child object in some
classes.  We've found that concept extremely useful in .Net, where we
might have a parent object that is related to reports (for example),
making those reports a child of the class.  So merely setting the
parameters of the instance of the parent automatically gives us an
object reference to all the reports for that instance.  I'm probably
explaining it badly, but it's something I played with in Access as well
in my previous life.

Charlotte Foust 

-----Original Message-----
From: accessd-bounces at databaseadvisors.com
[mailto:accessd-bounces at databaseadvisors.com] On Behalf Of jwcolby
Sent: Monday, March 09, 2009 8:31 AM
To: Access Developers discussion and problem solving
Subject: Re: [AccessD] Building a control class

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





More information about the AccessD mailing list