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