John W. Colby
jwcolby at colbyconsulting.com
Tue Mar 23 23:34:06 CST 2004
The demo code for this lecture can be found on my site. Click the C2DbFW3G button, then the C2DbFW3G-DemoCtlClassV6.zip link. Framework Discussion Using System Variables Now that we have System Variables available to control program execution it is time to demonstrate how we use this and how useful it can be. First though, I need to display the new code to the Framework class that loads, cleans up and allows access to the SysVar class. The first thing we do is add a private variable in the header of clsFramework Private mclsSV As clsSysVars We set the variable instance in clsFrameworks Initialize method. Private Sub Class_Initialize() Set mclsSV = New clsSysVars End Sub We do our cleanup in Term(). Public Sub Term() mclsSV.Term Set mclsSV = Nothing End Sub Then we create a function to return a pointer to the frameworks SysVar class instance. Notice that while we may eventually have a handful of SysVar classes instantiated to handle other functionality, the one for the framework is a special case used throughout the Framework and thus has its own variable and methods in clsFramework. ' 'Get a pointer to the FRAMEWORK'S SV class instance ' Public Function cSV() As clsSysVars Set cSV = mclsSV End Function We also build a method for clsFramework to return framework SysVars. ' 'Return SysVars from the Framework's SysVar class ' Public Function SV(strSVName As String) As Variant SV = mclsSV.SV(strSVName) End Function Having done that we can go to the debug window, initialize the framework, then call the framework SV method and get a specific SysVar from the framework. FWInit ?fw.SV("gCtlDteFormat") dd/mm/yyyy We have demonstrated that the framework is initializing the SysVar class for the Framework itself and that we can now access this SysVar class instance through methods of the Framework. Demonstrating SysVar Usage Having SysVars available to control program flow, we can now start putting then to use. One of the uses discussed on the AccessD list was to determine the data type of a bound control and use that knowledge to set a text boxes format and input mask strings. This allows us to set these properties at the framework level rather than having to go through the application setting them one by one. In order to do this I added code in the control scanner of the framework to get the data type of the field a control is bound to. First I create a dao recordset variable in dclsFrms header. Private mrst As DAO.Recordset In the control scanner I create an integer variable to hold the data type, then set the class recordset to the forms Recordset clone. Dim intCtlData type As Integer Set mrst = mfrm.RecordsetClone Inside the loop that scans all of the controls I add code to get the data type of each control. For Each ctl In mfrm.Controls 'Find each control in the form's control collection With ctl On Error Resume Next intCtlData type = mrst.Fields(ctl.ControlSource).Type Now that I know that, I have to be able to use it. In each control class for data aware controls I added a new private variable in the class header. Private mintData type As Integer 'The data type of the field the control is bound to And added a new parameter to each of these class init(). This code is for the text box class. Inside the Init we save the passed in value so that it is available to the class. Public Sub Init(ByRef robjParent As Object, lfrm As Form, ltxt As TextBox, lintData type As Integer) mintData type = lintData type If mintData type = dbDate Then If FW.SV("gCtlDteFormatUse") Then mtxt.Format = FW.SV("gCtlDteFormat") End If If FW.SV("gCtlDteMaskUse") Then mtxt.InputMask = FW.SV("gCtlDteMask") End If End If Now we can set the format and inputmask properties of the control to match what is in the SysVar. Notice I have a SysVar that says use the format, and another that specifies a format. We can turn on/off using the format simply by setting the gCtlDteFormatUse variable True/False. We then change the format itself by changing the gCtlDteFormat SysVar. Likewise we can do the same thing for the mask. This is still a somewhat simplified example but demonstrates what is possible using the SysVars. The next thing I want to demonstrate is overriding a SysVar at the form level. Form level SysVar overrides It occasionally happens that certain behaviors need to be set for specific forms but not others. In order to do this we need to be able to have the form class load SysVars that apply to its behaviors or control behaviors, then look for the forms name in the SysVar name. For example, we might have a SysVar named gCtlDteMaskUse. The form class has a function that loads this SysVar into a variable in the class so that its controls can poll the form class to decide whether to use a date mask. However if also looks for a SysVar with the same name but the forms name at the end gCtlDteMaskUsefrmPeople. As forms load, each look for its own name in the each SysVar, but only frmPeople finds its name embedded in a SysVar. It strips its name out of the SysVar and uses the remainder of the SysVar name to set that SysVar value. Thus only that specific form gets an override of that SysVar. This will be a little easier to see as I go through the code. Please remember that this is not a science by any means. Since I have never talked to anyone else doing this in their framework, there are undoubtedly other or better ways to do this, and other things that could be used with this idea. Any positive suggestions on the implementation are appreciated. In fact I have never been particularly happy with my implementation since it required adding a new variable to the form class header for each new behavior / property I want to control, and a new Property Get to read that variable out, at least for those properties where a control will be using the SysVar. As always happens, I started implementing these and ended up with many additional variables in the class header and many additional property gets for the class just to handle the form / control SysVars. I am looking at throwing all of these into a collection keyed on the base SysVar name. That would allow a single collection to hold all form / control specific SysVars and a single property get to read any SysVar in the collection, allowing extremely easy expansion of the system as new SysVars are needed in the form / controls. To implement this functionality we add a collection variable in the header of dclsFrm. '+SysVar overrides Private mcolSysVars As Collection 'A collection to hold all the form / control specific SysVars '-SysVar overrides Initialize it in the class Initialize. Private Sub Class_Initialize() Set mcolSysVars = New Collection End Sub In Init() call a function to read the SysVars we want to use. Public Sub Init(ByRef robjParent As Object, lfrm As Form) 'Read all of the SysVars specific to forms / controls ReadSysVarBehaviorEnbls End Sub Cleanup in Term() Public Sub Term() Set mcolSysVars = Nothing End Sub ReadSysVarBehaviorEnbls reads (or tests for) exactly the SysVars that we need for form / control handling. 'The system variables can turn On and Off behaviors globally - for example combo dbl-click handling. 'However the developer may wish to override that behavior for certain forms. In order to accomplish 'this the form has to search the SysVar tables for specific switches defined at framework design time 'and handle them appropriately ' 'gCboDblClick - Gobal Dbl-Click turns on / off dbl-click handling for combos. May be over-ridden at the form level. 'gCboNotInList - Global NotInList turns on / off NotInList handling for combos. May be over-ridden at the form level. 'gCboEnter - Global Enter turns on / off Enter AND EXIT event handling for combos. ' 'fCboDblClickFrmClient - an example of an override - frmClient handles CboDblClick differently that the rest ' Private Function ReadSysVarBehaviorEnbls() On Error GoTo Err_ReadSysVarBehaviorEnbls 'The call to the function SysVarBehaviorEnbl requires the name of the SysVar minus the leading g, f, etc. SysVarBehaviorEnbl "CboDblClick" SysVarBehaviorEnbl "CboNotInList" SysVarBehaviorEnbl "EnblCtlBackGndColorChg" SysVarBehaviorEnbl "CtlBackGndColor" SysVarBehaviorEnbl "EnblLblBackGndColorDblClk" SysVarBehaviorEnbl "LblBackGndColorDblClk" SysVarBehaviorEnbl "CtlValidateBackGndColor" SysVarBehaviorEnbl "CtlDteFormat" SysVarBehaviorEnbl "CtlDteMask" SysVarBehaviorEnbl "CtlDteFormatUse" SysVarBehaviorEnbl "CtlDteMaskUse" SysVarBehaviorEnbl "PrpFrmCloseButton", mfrm.CloseButton, True SysVarBehaviorEnbl "PrpFrmMinMaxButtons", mfrm.MinMaxButtons, True SysVarBehaviorEnbl "PrpFrmControlBox", mfrm.ControlBox, True SysVarBehaviorEnbl "PrpFrmBorderStyle", mfrm.BorderStyle, True Exit_ReadSysVarBehaviorEnbls: On Error Resume Next Exit Function Err_ReadSysVarBehaviorEnbls: MsgBox Err.Description, , "Error in Function dclsFrm.ReadSysVarBehaviorEnbls" Resume Exit_ReadSysVarBehaviorEnbls Resume 0 '.FOR TROUBLESHOOTING End Function And finally SysVarBehaviorEnbl performs the magic of finding the SysVar, checking for an override etc. ' 'This function takes a passed in variable and the base name of a systemvariable, and builds up the 'global or form name, looks for the SysVars and sets the variable accordingly. ' 'The global switch starts with the character g, then the SysVar name 'the form switch starts with the letter f, then the SysVar name, then the name of the form ' 'For example, in the SysVar table we have 'gCboDblClick' which if set turns on combo dbl-clicks globally 'however a form can override the global if there is an 'fCboDblClickXXXXX' where XXXX is the name of the form ' 'This function basically just enforces a strict naming convention and wraps it all in an easy to use function ' Private Function SysVarBehaviorEnbl(strSysVarName As String, Optional varVariable As Variant, _ Optional blnIsFrmProperty As Boolean = False) On Error GoTo Err_SysVarBehaviorEnbl Dim var As Variant Dim varTemp As Variant 'get the global enable if any var = FW.SV("g" & strSysVarName) If Not IsNull(var) Then varVariable = var 'add the name of this form to 'fCboDblClick' to see if there's an override for this form specifically varTemp = FW.SV("f" & strSysVarName & mfrm.Name) If Not IsNull(varTemp) Then var = FW.SV("f" & strSysVarName & mfrm.Name) End If ' 'If this is a form property handle it as such. ' If blnIsFrmProperty Then varVariable = var Else ' ' 'Otherwise just add it to the SysVar collection keyed to the SysVarName If Not IsNull(var) Then mcolSysVars.Add var, strSysVarName End If End If Exit_SysVarBehaviorEnbl: On Error Resume Next Exit Function Err_SysVarBehaviorEnbl: MsgBox Err.Description, , "Error in Function dclsFrm.SysVarBehaviorEnbl" Resume Exit_SysVarBehaviorEnbl Resume 0 '.FOR TROUBLESHOOTING End Function We now have a single Property Get SV() to retrieve any SysVar that is loaded into the forms SysVars collection. ' 'This property reads a SysVar if it exists in mcolSysVars, 'or returns a null if not in the collection ' Property Get SV(strSVName As String) As Variant On Error Resume Next SV = mcolSysVars(strSVName) If Err <> 0 Then SV = Null End If End Property Now we can add new SysVars for form / control use simply by adding a single line of code in ReadSysVarBehaviorEnbls which allows much easier modifications to the form and control classes to use SysVar control. Form Properties Another use for SysVars and form level overrides is setting form properties through SysVars. Since properties are just some value true/false, an integer etc. we can if we desire set specific form properties using SysVars. If the property name exists then that value gets loaded in that property of any form that loads, or for a specific form or forms. I never really explored this idea fully but I implemented it in case I ran into a use for it. Just remember that some properties are only settable in design view, and these we cannot affect using SysVars. Things like the CloseButton property, MinMaxButton etc can be set through VB and thus can be controlled by SysVars if you find that useful. The Syntax is simply: SysVarBehaviorEnbl mfrm.CloseButton, "PrpFrmCloseButton" SysVarBehaviorEnbl mfrm.MinMaxButtons, "PrpFrmMinMaxButtons" SysVarBehaviorEnbl mfrm.ControlBox, "PrpFrmControlBox" SysVarBehaviorEnbl mfrm.BorderStyle, "PrpFrmBorderStyle" Control SysVars Controls obviously need to be able to read SysVars to modify their behaviors as well, as we saw demonstrated at the beginning of this lecture. In order to reduce complexity and place all form / control SysVar loads in a common location I do the load of all Control SysVars in the form class as well. By exposing a SV property that can read any SysVar value back out of the forms SysVar collection, the controls can poll the form class for SysVars that they need. To demonstrate this we will modify the text box class to ask the form class about the date mask. In the forms Init(): If mintData type = dbDate Then If mobjParent.SV("CtlDteFormatUse") Then mtxt.Format = mobjParent.SV("CtlDteFormat") End If If mobjParent.SV("CtlDteMaskUse") Then mtxt.InputMask = mobjParent.SV("CtlDteMask") End If End If To see the effects of using the SysVar, simply select the HOUSAC WATER QUALITY DISTRICT company, then notice that the DOB field is formatted per the gCtlDteFormat in usystblFWSysVars. You can turn on/off this functionality by setting gCtlDteFormatUse True or False as you desire, then in the debug window calling fw.csv.RefreshSysVars. This calls the framework, gets a pointer to the SysVars class using the frameworks csv property, then calls the RefreshSysVars method of that class. Summary SysVars provide a powerful tool for modifying the Framework or Application behaviors. We can use generic behaviors, setting default behaviors that we can then modify behaviors for an entire application, or on a form by form basis. The controls can modify their behaviors by calling the parent form class SV method to get a SysVar value. In the future we will be adding dozens of behaviors to the form class and the various control classes. Virtually all of these behaviors will be programmable by setting up SysVars in usystblFWSysVars and then reading out the SysVars in the form class. Our code will then use these values to set control and label background colors when behaviors are enabled, enable these various behaviors, enable things like JIT subforms and so forth. A framework is far more than a simple hammer. Any time we program a behavior that we have needed forever or are using over and over in our applications, consider placing that behavior in the control or form class as appropriate, then turn on / off that behavior with SysVars. Suddenly you will be able to offer options to the client literally at the push of a button or the setting of a SysVar. To a man with a hammer, everything may very well look like a nail. To a developer with a framework and SysVars, the search and replace tool becomes the last resort instead of the first. John W. Colby www.ColbyConsulting.com