Friday, April 10, 2020

Form Event Sequences

Published January 26, 2008 | By Andy Kramek


One of the things that occasionally seems to confuse people is the question of the order in which the events happen when a form is instantiated. the basic answer, in terms of the form's own events, is given by the acronym "lisa g", which stands for:


  • L => load
  • I => init ()each control first, then form last)
  • S => show
  • A => activate
  • G => gotfocus (first control in the tab order)


Actually you could argue that this should be "LISAR G" – because whenever the Activate() event of a form is triggered, a Refresh() is also initiated. Anyway, while these are the native form events, there are other things happening when a form is instantiated, most notably when a dataenvironment is involved.

For a form which uses a de, and contains an exit button and pageframe with two pages the full initialization sequence is:

form.dataenvironment.opentables
form.dataenvironment.beforeopentables
form1.load
form1.dataenvironment.cursor1.init
form1.dataenvironment.init
form1.cmdexit.init
form1.cmdrefresh.init
form1.pgfmain.page1.grdcustomer.column2.txtcol2.init
form1.pgfmain.page1.grdcustomer.init
form1.pgfmain.page1.init
form1.pgfmain.page2.tb01.init
form1.pgfmain.page2.cb03.init
form1.pgfmain.page2.tb02.init
form1.pgfmain.page2.init
form1.pgfmain.init
form1.init

The whole thing starts with the dataenvironment opentables() which is an event that calls the beforeopentables(), and then proceeds with the form’s load() event. after that the sequence is pretty much as expected. the objects contained by the form (command button and pageframe) are initialized first, in the order in which they were added to the form, followed by the containers themselves, beginning with the dataenvironment. this part of the sequence finishes with the form’s init().

This is, of course, why parameters passed to a form have to be received in the init(). it is the first form method that fires after all the controls are available, but before any of them have actually been updated.

The next part of the process is concerned with updating the controls from their data sources (which is what the refresh() method actually does) and then making the form visible, the events fire as follows:


form1.show
form1.pgfmain.page1.activate
form1.activate
form1.refresh
form1.cmdexit.refresh
form1.cmdrefresh.refresh
form1.pgfmain.refresh
form1.pgfmain.page1.refresh
form1.pgfmain.page1.grdcustomer.refresh
form1.cmdrefresh.when
form1.gotfocus
form1.cmdexit.when

Notice, however, that only the active page (page1) of the pageframe gets refreshed. this means that the controls on page 2 have not yet been updated and this explains why you always need to do an explicit refresh() when navigating to a new page.

There is one thing to watch here, though. if you simply add a "thisform.refresh()" to the activate event of the first page of your pageframe you would actually end up calling it three times when initializing a form. once (implicitly) from the form.activate(), a second time explicitly when the first page of the pageframe is refreshed and finally when the page itself is activated.

Note: this underlines just how important it is to avoid the (way-too-common) practice of calling thisform.refresh() from all over the place. a form level refresh should really only be called when you are certain that it is needed. in other words, thisform.refresh() should always be preceded by some sort of test!



One way to handle this is to create a wrapper method for the form refresh() and include a test for a form level property that is set by events that will require a form level refresh (like moving the record pointer, or changing the form's mode from "add" to "view" for example). we use a method named "refreshform" to handle this and it looks like this:


lparameters tlsetobjmode
with thisform
    if .formrefreshneeded
        .lockscreen = .t.
        if .beforerefresh()
            if tlsetobjmode
               .setobjmode( this )
            endif  
            .refresh()
            .afterrefresh()
        endif
     endif  
     .lockscreen = .f.
endwith 

The parameter is used to determine whether to re-evaluate the form's mode and change any object whose state depends on that mode (handled by the setobjmode() method). notice that we also have hooks to "before" and "after" methods that allow us to modify the behavior, and even cancel a refresh (by returning .f. from beforerefresh()) if necessary. by always calling this refreshform() method instead of directly calling refresh() we buy ourselves a lot of flexibility.

Incidentally, if you instantiate a form from a vcx instead of an scx the event sequence is identical except, of course, that there are no dataenvironment events.

So what happens when we release a form? the answer is that it depends on on how the form is released.

There are basically three ways of killing a form. first you can explicitly call its release() method (i.e. thisform.release() ). second, you can click on the ‘close’ button, which actually calls the form’s queryunload() method and third you can release the object reference to the form: ( i.e. release thisform)

[1] form release() called:
r => release
d => destroy (form first, then all contained controls in reverse of the init order)
u => unload

[2] close button used:
q => queryunload
d => destroy (form first, then all contained controls in reverse of the init order)
u => unload

[3] object reference released (note that neither release(), nor queryunload() are fired in this case):
d => destroy (form first, then all contained controls in reverse of the init order)
u => unload
in each case, the form's de does not get released until after the form unload - so tables are still available in the unload as well as properties - but no controls.

There is a potential problem here though. notice that the first event common to all three methods is the destroy(). however, once the form's destroy fires there is no getting out of it, even a nodefault will not stop the release process. that means that there is no single native method or event in which you can place code that checks to determine whether a form should be released or not. (it also means that if you release the object reference, it absolutely will be killed - there is no way to undo such this action because it fires the destroy() directly).

If you do need to check that it is ok to release a form you will either have to disable the 'close' button on all your forms, or place the checking code in a custom method that is called from both the form's release() and queryunload() events because a nodefault in either of these methods will halt the destruction of the form. this code, in both events, will do the trick:

If not thisform.ok2destroy()
nodefault
endif

The most usual reason for this sort of code is to prevent users from closing a form with uncommitted changes pending, so the ok2destroy() will typically call the form's checkforchanges() method. as usual we prefer to call 'control methods' that then call the actual 'action' methods so that if we do need to modify behavior there is a place to do it from.

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...