Friday, April 10, 2020

Getting a reference to a parent form


It sometimes happens that we have two forms that depend upon one another in some way. Typically one form is called from another requires access to multiple values, or selection criteria, that are defined in its “parent”. I have seen various solutions proposed for addressing this scenario, but there is one very simple solution that does not seem to be very widely known, although it has been true in VFP since Version 3.0.
Whenever VFP is started  up, whether as a full development copy, or using the run-time libraries, an object (based on a form class incidentally – try “? _Screen.Class” from the command window) named “_Screen” is created. Note that it doesn’t matter whether the VFP screen is actually visible, or not. The object is still created as it is a fundamental component used by VFP itself.
One of several important custom properties of _Screen is one named “ActiveForm”. As the name implies, this property holds a reference to the currently active form and can be used when writing generic code that needs to access whatever form is active.
For example, suppose you have a procedure that can be called from several different forms. Sometimes you can simply pass a parameter, or two and that is sufficient. However if the procedure needs to access properties or methods on the calling form you have to give it a reference to the form, something like this:
DO myproc WITH ThisForm
and in the procedure you need a corresponding parameter
FUNCTION myProc( toForm )
This is not a problem so long as the procedure is called from the form itself (or an object that is a member of the form). However there are times when a procedure needs to access whichever form is active at the time even though that form is not the one that called the procedure to begin with. For example, when there are multiple instances of a form, or because the procedure itself changes the active form. This is a scenario where _Screen.ActiveForm is useful because we don’t have to worry about what the instance of the form is called and can just get a reference to whichever form is active:
IF TYPE( “_Screen.ActiveForm” ) = “O”
  *** OK, we really do have an active form
  loForm = _Screen.ActiveForm
ELSE
  *** Ooops! Shouldn’t there be an active form here?
ENDIF
Now, you may be wondering why I have wrapped this in a test. The answer is simple, if there is NO active form and you try to access the property, VFP throws error 1924 (“ACTIVEFORM is not an object”). Clearly this is not desirable so it is important to check that there really is a form there before trying to grab a reference to it. Incidentally, note that this is also one of those occasions when we cannot use VARTYPE() because if there is no active form, trying to access the property also causes Error 1924. TYPE() in that case just returns “U”.
That’s all very interesting but, I hear you say, how does that help us get a reference to the parent form? The key is recognizing the moment at which _Screen.ActiveForm gets updated when a new form is instantiated.
The answer is that when one form calls another, the value of Screen.ActiveForm does not change until AFTER the new form has successfully completed it’s LOAD(), and INIT() methods. This is easily demonstrated by writing the name of the active form (Screen.ActiveForm.Name) out to a text file when calling one form from another – here is the result:
In Parent Form, Active Form is currently [FrmParent]
*** Call child form here ***
STARTING Child Form LOAD, Active Form is currently [FrmParent]
STARTING Child Form INIT, Active Form is currently [FrmParent]
STARTING Child Form SHOW, Active Form is currently [FrmParent]
STARTING Child Form ACTIVATE, Active Form is currently [FrmChild]
*** Release Child Form here ***
STARTING Child Form DESTROY, Active Form is currently [FrmChild]
STARTING Child Form UNLOAD, Active Form is currently [FrmChild]
STARTING Parent Form ACTIVATE, Active Form is currently [FrmParent]
This makes perfect sense when you think about it. After all, if either Load() or Init() returns FALSE the child form is not going be instantiated so if _Screen.ActiveForm were to change earlier in the process than the Show() there would be no guarantee that the form would not be prematurely terminated. Once the Show() is reached VFP can be sure that the new form is not going to terminate abnormally and so the value of _Screen.Activeform can be safely updated.
Similarly, once the child form’s Unload() is reached, the form will definitely be destroyed so the prior form (if any) is activated and the property updated accordingly.
So, what does this mean for us? Well, one thing it means is that if a form is called from another form, we don’t need to pass anything at all in order to get a reference to the parent. More importantly, perhaps, because of this behavior, we can actually access the Properties, Events and Methods of the parent form from the Load() of the child form – which happens before any controls are even instantiated.
This is important because if there are criteria for controls on the child form that rely on a value from the parent form (typically when the values depend on some selection that has been made in the parent form) it is much better to handle this before the controls have been instantiated. Passing the reference as a parameter does not permit this because parameters are received in the form’s Init() which only fires after all controls have already been instantiated.
The bottom line is that in order to provide a reference to a parent form we can use generic code, in the Load() event of a form class. All that is necessary is to add a custom property to the form (call it ‘oParentForm’) and then add the following code to the Load() event:
IF TYPE( “_Screen.ActiveForm” ) = “O”
  This.oParentForm = _Screen.ActiveForm
ELSE
  This.oParentForm = NULL
ENDIF
Any method, or any object, on the form can get now a reference to the parent form by simply checking that the “oParentForm” property is not null. The only other thing to do (and it is not really required, though I consider it ‘good practice’) is to clean up the reference in the form’s Destroy() event – and that is a one-liner!
ThisForm.oParentForm = NULL
Published Sunday, September 04, 2005 4:29 PM by andykr

No comments:

Post a Comment

Writing better code (Part 1)

Writing better code (Part 1) As we all know, Visual FoxPro provides an extremely rich and varied development environment but sometimes to...