John W. Colby
jwcolby at colbyconsulting.com
Sat Mar 6 16:22:51 CST 2004
We have discussed why we would use classes and seen demos of text box code running natively in a form, then the equivalent written as a control class for a text box. We then looked at these classes dimensioned in the form header and instantiated directly and using class factory functions. The next step is what I call the supervisor class. Supervisor classes are classes that load and supervise other classes. Notice that the forms built in class is doing a lot of work setting up these control classes. It will only get worse as we add new control type classes for combo boxes, check boxes etc. and then try to load a class instance for each of 50 controls. All of the code we saw in frmPeople4 will have to be replicated over and over in each form you want to build. So why dont we build a form class of our own and place this code in our class. Then we just set up and tear down that supervisor class in each form. Basically all we do is transfer the code we have in the form into a class, with an Init() and Term() method to call for setting up and tearing down our new class form. OK, so the code for the new dclsFrm (form class) can be seen in dclsFrm in the demo database and looks like this (comments in-line): The dclsFrm form class (Supervisor) Option Compare Database Option Explicit By now you should be getting used to class headers. Here we dimension the Event Procedure string, the collection and the form variable. Notice that we are declaring the form variable WithEvents meaning that this class will be sinking form events inside this class Private Const mstrEventProcedure = "[Event Procedure]" 'A constant to hold the string [Event Procedure] Private mcolClasses As Collection Private WithEvents mfrm As Form We havent seen this yet. All classes may have an Initialize and Terminate event which are run automatically as the class opens. It is roughly the equivalent of a forms Open and Close events. I use them to run the set statements for all objects that the class will use, just to get these statements grouped together and run. Private Sub Class_Initialize() Set mcolClasses = New Collection End Sub In the Terminate of the class we call the classes Term() method which the developer writes. Private Sub Class_Terminate() Term End Sub The init simply takes the passed in pointer to the form and stores it in our local variable. We now have a pointer to the form so we can call FindControls to iterate the controls control collection and load the control classes. And finally, set the forms OnClose property so that we can sink the Close event in this class. Function Init(lfrm As Form) Set mfrm = lfrm FindControls mfrm.OnClose = mstrEventProcedure End Function Term calls ClsDestroy which cleans up the control classes, then sets out pointer to the form to nothing. Forgetting to clean up pointers to collections and controls is one of the prime reasons for memory leaks and Access failing to close. Function Term() On Error Resume Next ClsDestroy Set mfrm = Nothing End Function The forms Close event will transfer control to this event sink, where we call this class term event to clean up our pointers Private Sub mfrm_Close() Term End Sub The class factory we saw in Classes why would we use them. It simply creates an instance for the text box passed in and stores the pointer to that class in the collection. Function ClassFactory(txt As TextBox) Dim ldclsCtlTextBox As dclsCtlTextBox Set ldclsCtlTextBox = New dclsCtlTextBox ldclsCtlTextBox.Init txt mcolClasses.Add ldclsCtlTextBox, txt.Name End Function Class destroy cleans out the control class collection, calling term of each control class then destroying the pointer to that class. Function ClsDestroy() Dim obj As Object On Error Resume Next For Each obj In mcolClasses obj.Term Set obj = Nothing Next obj Set mcolClasses = Nothing End Function FindControls is the major enhancement that the dclsFrm brings to the table. This function iterates the forms controls collection. It examines each control for its control type and instantiates a class instance for each control. The type of control class instantiated depends on the controls controltype property. So far we only have a control class for text boxes but we will soon be adding more control classes for combos, check boxes, lists and so forth. This function will then be used to load those other control classes as well. ' 'THIS FUNCTION SEARCHES THE FORM FOR CONTROLS OF VARIOUS TYPES. ' 'The framework will build functionality using controls with consistant naming. We can 'find these controls simply by searching the form's control collection looking for 'controls named something specific. In other cases we might want to load a class 'to handle a specific type of control - perhaps a text box class or a dependent combo 'class. ' 'This function will be used to do the search through the form's control collection 'looking for controls that we know how to handle and setting up the hooks to handle 'those controls ' 'Parameters: 'Created by: Colby Consulting 'Created : 4/26/98 10:23:44 AM Private Sub FindControls() On Error GoTo Err_FindControls Dim ctl As Control Dim intIndex As Integer Dim col As Collection Set col = New Collection For Each ctl In mfrm.Controls 'Find each control in the form's control collection With ctl Select Case .ControlType 'Determine it's type Case acTextBox 'Find all text boxes and load class to change backcolor mcolClasses.Add New dclsCtlTextBox, .Name mcolClasses(.Name).Init ctl Case acSubform Case acTabCtl 'tab pages are handled in the tab control Case acOptionGroup Case acCheckBox Case acOptionButton Case acCommandButton Case acToggleButton Case acListBox Case acComboBox Case Else End Select End With NextCtl: Next ctl Exit_FindControls: On Error Resume Next Set ctl = Nothing Exit Sub Err_FindControls: Select Case Err Case 0 'insert Errors you wish to ignore here Resume Next Case Else 'All other errors will trap Beep MsgBox Err.Description, , "Error in function Forms.FindControls" Resume Exit_FindControls End Select Resume 0 'FOR TROUBLESHOOTING End Sub The forms built-in class That takes care of the dclsFrm form class. To use this class we modify our forms built-in class as follows See frmPeople6: Option Compare Database Option Explicit Dimension our new form class Public fdclsFrm As dclsFrm Set it and initialize it, passing a pointer to the form Private Sub Form_Open(Cancel As Integer) Set fdclsFrm = New dclsFrm fdclsFrm.Init Me End Sub If you remember the first form with the control handling built into the form s class you can see we have dropped the total code resident in our form by a huge amount. Additionally, should we have 1 or 100 controls on the form, the generic processing code on the form doesnt expand by even a single line of code. Now your form can concentrate on functionality that is specific to the application rather on functionality that is generic to all applications. The dclsFrm is one of a handful of supervisor classes we will look at as we work on the framework. We now have what could be called a minimal framework. We have a form class that you can build upon to hang form functionality on, and we have one control class dctlTextBox that demonstrates the concept of the control scanner in dclsFrm loading control classes for us automatically. This class can have additional functionality added to it to allow it to automatically perform other generic functionality for our applications. In fact we will be adding more functionality to both of these classes in addition to adding other controls classes. More importantly though, we need to get a framework foundation established, which will take the shape of another supervisor class which I call clsFW. Note: frmPeople5 uses the forms built-in class Close event to cleanup the pointer to dclsFrm which causes a page fault. I just wanted to point out that you cant do this in Access 2K, and that this is a bug in Access that was fixed in AccessXP. John W. Colby www.ColbyConsulting.com