The great events of life often leave one unmoved...
—Oscar Wilde
What is an event? What do we need to do about it? The FoxPro family was a leader in the Xbase world in moving from a procedural application to an event-driven interface, even in DOS. With Visual FoxPro, FoxPro became fully attuned to the rich event model of the underlying Windows interface. Visual FoxPro can create far more responsive applications, and applications that are more consistent with the behaviors of other Windows programs. We'll examine the different events that are possible, under what circumstances they occur, and what code is appropriate for each of them.
Simply put, events happen. In OOP terms, an event is an
occurrence outside your control, about which one of your objects is notified
with a message. An event can be system-generated, user-initiated, or caused by
another object in the application. When Windows handles the resizing of a form,
it sends a message perceived by the form as a Resize event. When the user
clicks on a control, the control receives a Click event. When a Timer control
in your application counts down its elapsed time, a Timer event fires.
As we discussed in "Controls and KAOS," there is no difference between the code contained in an event and that in a method. When we talk of modifying "the Mouse event," it's shorthand for "the code contained in the method associated with the Mouse event." We won't apologize for using this shorthand, nor do we expect to stop.
The set of events is fixed. Unlike methods, it isn't
possible to design your own custom events. Although you can customize the code
that occurs (or specify that nothing occurs) when an event happens, you cannot
create additional events. Starting in Visual FoxPro 7.0, the EVENTHANDLER()
function allows you to bind to events from ActiveX controls and COM objects, so
that you can designate Visual FoxPro code to run when these events occur.
Internally, Visual FoxPro has provided us with such a rich event model that few
events are missing. In fact, with the addition of Access and Assign methods in
VFP 6 (see the Reference section), and Database Events in VFP 7 (again, see the
Reference section), the object model only gets richer with each version.
In days gone by, it was a major undertaking to get FoxPro to just stop and let the user direct what was to happen next. Xbase was originally designed as a procedural language, where the program demanded input and then performed its process. The emphasis has shifted over the years, with improving user interfaces, toward an event-driven system, where the tables are turned in such a way that it is the user who seems to be controlling events and the computer that responds. The shift in FoxPro to this new way of doing business has been a gradual and not altogether smooth transition.
Several alternative event-handling methods have been
proposed over the years, and each has its proponents. Until the release of
Visual FoxPro, there were good reasons why each method might have been
desirable under some circumstances. A simple looping structure, checking for a
keystroke, could be used as a basis for the application. Several means of
detecting a keystroke, using WAIT, INKEY(), CHRSAW() or READ, could respond to
the event. In FoxPro 2.0, an alternative, named the Foundation Read (and
quickly nicknamed "The Mother Of All Reads," or MOAR, for short)
became popular. This READ worked without any corresponding GETs, causing the
READ VALID code snippet to fire when an event occurred. Several elegant
application frameworks were developed based on this parlor trick. But, like the
techniques before them, this method could be tricky to implement under some
circumstances, and had kinks and limitations. Visual FoxPro solved the need for
these artificial constructs by allowing our applications to become part of the
native event loop of the FoxPro engine. In essence, we can now tell FoxPro
"Just wait until something happens, or until we tell you it's time to
quit."
The READ EVENTS command sets the event handler in action
after establishing your environment. There was some discussion that a more
suitable command would have been "WAIT HERE," but WAIT is already
overloaded. Just what should Visual FoxPro do if someone issued WAIT HERE
NOWAIT "What now?" TO lcFred AT 10,10? Or perhaps
"ENERGIZE!" (but some dumb bunny's already cornered the market on
that one) or "MAKE IT SO" (but Paramount might sue)? So, READ EVENTS
it is. It doesn't READ anything at all, and EVENTS go right past it without a
raised brow, but that's the command to start the ball rolling.
When you're done in Visual FoxPro and ready to close up
shop, CLEAR EVENTS is the command to tell READ EVENTS to stop whatever it has
been doing. CLEAR is one of the most heavily overloaded commands in the language,
releasing everything from class libraries cached in memory to DLL declarations
to menu items defined with @ ... PROMPT or even CLEARing the screen. We would
have preferred newer, cleaner terminology, like "STOP" or "ALL
DONE", but no one asked us.
When an event occurs, you probably want to provide some code
for it to run. When the user clicks on a button, or a timer times out, you need
to provide code to describe what happens next. That's easy. You can do that.
You're a programmer, right? We spend most of the rest of the book telling you
how to do that. But what happens when you don't want any code to run, or
perhaps want absolutely nothing at all to happen? If the code fragment for the
event you're concerned with is left blank, the same event for the control's
parent class is searched to find code to run. If there's nothing there, the
same event in the parent's parent class is searched, and so on and so forth.
Even if there's no code for that event anywhere in the inheritance hierarchy
(that's what determines who gets Uncle Scrooge's millions, right?), there's
often some default behavior associated with the event. For example, by default,
when the user presses a key, the KeyPress event fires—the default behavior is
to put the key in the keyboard buffer. To get a control to do nothing, not even
the default behavior, you issue the NoDefault command, valid only in methods.
User interface events occur even when NoDefault is issued.
For example, when a command button is clicked, the button is visually depressed
(guess it needs a good therapist). Check boxes and option buttons display when
they have been toggled. If you want no action from one of these controls at
all, NoDefault is not for you—use the When event to return .F. for a complete
lack of response.
We run into another situation a lot: We need to let the
normal action of the class occur, but we just need to do one teensy-weensy
thing besides that. Normally, when you put code in an event, the search
described above doesn't happen. That is, once you add some code, the parent
class isn't checked for code in that event. The parent class' code is said to
be overridden. (The fortunate
exception here is that the built-in behavior of the event always occurs, even
if there's code, unless you specifically suppress it with NODEFAULT.) In the
bad old days, we'd just cut and paste the code from one class to another, and
then modify it, but these are the good new days. The newer (and better) way to
do this is to perform what code we need, and then call on the code from the
parent class (or its parent or its parent or ...). Or, if it's more appropriate
in your situation, call the code from the parent class (or its parent or ... you
get the idea) and then perform your custom code.
There are two ways to call the code from the parent class.
In VFP versions 5 and later, use DODEFAULT(). Put DODEFAULT() in any method and
it calls the same method in the parent class (and yes, you can pass
parameters).
In all versions of VFP (though we'd only use this version in
VFP 3), you can use the operator :: to call up one level in the class
hierarchy. The simple version is just:
NameOfTheParentClass::NameOfTheMethodbut this locks you into the parent class and method name you are using when you write the code, and isn't portable. If you change the parent class or move the method code to a different method, you may not be invoking the code you meant. If you want code that works anywhere, anytime, use:
LOCAL cMethodName
cMethodName = SUBSTR(PROGRAM(),1+RAT(".",PROGRAM()))
= EVALUATE(This.ParentClass+"::" + cMethodName)This calls the method of the parent class from which this class is derived, reinstating the "normal" code hierarchy as if no code were present in the class.
Obviously, either form can be a real time-saving device, since many subclasses differ in just one aspect from the parent, and the parent code can be called before, after, or even in the middle of the custom code written for this subclass. More importantly, though, calling up the hierarchy makes your classes more maintainable. Imagine changing code near the top of the class hierarchy and finding it doesn't affect some of the objects derived from that class! Wouldn't that be frustrating? More importantly, wouldn't that defeat the primary object of object orientation—reduced maintenance? By always calling up the hierarchy, except when you explicitly want to override the normal behavior, you know what to expect when you use a particular subclass.
In some cases, you might want to simply change the time at
which the built-in behavior of the VFP base classes occurs (such as putting a
character in the keyboard buffer or opening tables). The built-in behaviors
normally occur after all the custom code in an event, but there are times when
you want VFP to do its thing before your code, or in the middle of your code.
For those situations, you can combine DODEFAULT() and NoDefault. Issue
DODEFAULT() at the point at which you want the built-in behavior. Issue
NoDefault at any point in the code (or at least, any point that actually gets
executed). We like to put the two together to make it clear what's going on,
though.
Finally, we should point out that there's nothing magical about either keyword. Like anything else in FoxPro, they take effect only if they get executed. So, you can write code that figures out what's going on and suppresses base behavior or calls up the class hierarchy only when it's appropriate.
In order to have your code perform as you expect it to, it's essential that you understand the order and the circumstances in which a particular event's code will be called. For the purposes of this discussion, we break up the VFP classes into four groups: general non-container controls, containers, forms and form sets, and the rest (which includes some classes that don't have anything to do with forms). A fifth group, Database events, added in VFP 7, is covered in the Reference section. Most of the event model discussions can be explained by looking at individual controls, but some events only make sense (or only occur) in terms of higher-level objects.
Init fires when the object is first created or
"instantiated." This method is similar to the "constructor"
methods of other object-oriented languages. (It differs from constructors in
that it doesn't actually create the object.) If the object should be populated
with data at runtime, or if its properties should be altered based on the
present circumstance in the user's environment (say, her selection of currency
values calls for a change to InputMask, or his color set calls for a change to
the contrasting colors of a control), Init is the place to do it.
Despite the fact that Init code is the first to run after an
object has been created, we have been able to change the properties of an
object in the Init of another object that fires before the Init of the targeted
object. However, trying the same code in the form's Load event generates the
expected "Unknown member" error. We suspect that all of the objects
are instantiated first, and then their Inits are run in the same order. We
don't recommend depending on this undocumented behavior, though it has remained
the same for four versions.
Destroy fires when the object is released in one of three situations: the container is being released (like a form closing), a release method is fired, or all references to the object go out of scope.
An Error event fires when an error occurs in a method of the
control (or in a program or ActiveX control method called from a method of the
control) and the Error method contains any code (at any level of its class
hierarchy). The method can call a global event handler object, or assume the
responsibility for dealing with an anticipated error, handling it locally for
greater encapsulation of the control's behavior.
Other events fire when the corresponding user actions occur.
For example, when the mouse enters the boundaries of the control, the
MouseEnter event fires, followed by a series of MouseMove events. The
MouseLeave event fires when the mouse moves off the control. MouseEnter and
MouseLeave are new in VFP 7.
Similarly, a control's GotFocus event fires when the control
receives the focus. LostFocus fires when the control loses focus, and so forth.
A container behaves very much like other controls. It has
similar, if not identical, events associated with it. Init occurs when the
object is created, Destroy when it is released. Error fires when an error
occurs, and the user interface events (MouseOver, Drag, Click) fire as they do
for the non-containers. The difference between a container and other controls
is the sequence of event firings for the container and its contained controls.
Init events start with the innermost controls. This makes
sense once you realize that a container cannot perform its actions until its
contents exist. (Yeah, we guess you could argue that you can't put the controls
anywhere until the container exists, but that's not how it works.) Therefore,
objects are created from the inside out. If a text box is placed in the column
of a grid and that grid is on the page of a page frame in a form, the sequence
of Init firings is: text box, column, grid, page, page frame, and finally form.
This is probably counter-intuitive to our mental models of a form; first you
get a box, then you fill it with stuff, right? But there is some logic to the
idea that first you create the individual controls and then you can run the
routines from the container that affects them all.
Destroy events fire in the opposite order, from the outside
in, as if the container is imploding. This, too, makes some sense from a
programming point of view, since the destruction of the container forces the
things inside to go "ka-blooie," too.
Containers and their contained controls also share some user
interface events. The amount of sharing and interaction between the two objects
depends on how tightly bound the two objects are to each other. For example,
when a text box is placed on a page or in a column of a grid, that text box
pretty much has free reign over what occurs on its turf. Once the object gains
the focus, events are within the domain of that control. Once focus is lost,
the container can then fire related events, such as the AfterRowColChange event
in a grid.
On the other hand, "dedicated" container controls
that hold only one type of control, such as option groups and command groups,
tend to be much more involved in interactions with their contents. When the
mouse is moved over a command button in a command group, the MouseMove event of
the button fires first, followed by the MouseMove event of the button group.
See "Controls and KAOS" for the sequence of events when a button in a
button group is clicked—the key point is that some events fire at both the
button and the group levels.
Forms, formsets and toolbars are just big containers. Like
the other controls before them, they have Init, Destroy and Error, as well as
Click, DblClick and the other Mouse events. But they also have some additional
events and features.
The data environment's OpenTables and CloseTables methods
fire automatically (despite the fact that they're methods) if automatic opening
of tables has been selected using the cleverly named AutoOpenTables and (you'll
never guess) AutoCloseTables properties. In this case, these two methods behave
more like events than methods. If manually initiated opening or closing is
selected, explicit calls to the OpenTables and CloseTables methods are required
to open and CLOSE TABLES. The BeforeOpenTables and AfterCloseTables events fire
immediately before and after (respectively) the tables are opened or closed.
We found BeforeOpenTables a hard event to understand at
first. BeforeOpenTables fires immediately before the tables are actually
opened, but after the OpenTables method has been called and the custom code in
it has run. Placing a DEBUGOUT in each of the methods gives the unintuitive
sequence OpenTables, then BeforeOpenTables, but in fact, the BeforeOpenTables
event is fired because the OpenTables code is preparing to actually open the
tables. (You can't use Event Tracking to test this sequence because OpenTables
isn't an event.) The key point is that BeforeOpenTables fires not before the OpenTables method, but
before that method's default behavior of opening tables occurs.
In any event (pun intended), the data environment events are wrapped around the form, so that the form has data available from the time it starts until it finishes.
The Load event fires before all other form events, including
the initial data environment events, offering a handy place to take care of
form-wide settings. Unload is the last event on the form to fire, although the
data environment's Destroy events occur after the form is long gone. Activate
and Deactivate fire when the form or toolbar gets the focus (is
"activated") or loses the focus. The Paint event fires in toolbars or
forms whenever the object needs to be redrawn because of the removal of an
overlying object or a change in the size of the object or its contents. The
QueryUnLoad event, unique to forms, allows the form to sense the reason it's
being released and either prevent incorrect actions, or ensure that all
processing is complete before the form terminates.
VFP 6 introduced some new objects that don't fit into any of
the categories above: ActiveDoc, Hyperlink and ProjectHook. VFP 6 SP3
introduced the Session object. All have Init, Destroy and Error events.
Hyperlink and Session have no additional events (and we're sort of inclined to
think of them as non-container controls), but the other two classes each have
some events that are different from any others in VFP.
Active docs have several events that let them interact with
their "host" (that is, the browser in which they're running). The Run
event fires when the active doc has been created and is ready to go. It's
essentially the "main program" of the active doc. ShowDoc and HideDoc
fire when their names say—when the active doc application becomes visible or
invisible. CommandTargetQuery and CommandTargetExec fire when the user performs
actions in the host, to offer the active doc a chance to respond. Finally,
ContainerRelease fires when the host lets go of the active doc.
Project hooks have events that fire when the user
(developer, in this case, presumably) acts on the associated project.
QueryAddFile, QueryModifyFile, QueryNewFile (added in VFP 7), QueryRemoveFile
and QueryRunFile fire when the specified action occurs on a file in the
project—issuing NoDefault in one of those methods prevents the action from
taking place. BeforeBuild and AfterBuild are also well named because they fire
when a build is initiated and when it's completed. You shouldn't need to deal
with any of these events at runtime, since project hooks are essentially a
design-time creation.
Let's run through the whole event loop now, just to tie it
all together. Your user has started your application. You've run the startup
routine, perhaps instantiating a number of application-wide objects. These
might include an Application object, to contain application-wide preferences,
keep track of the current status and get the ball rolling. Other objects your
application might use are a Security object, to accept a login and password and
to dole out permissions as requested; a Data Manager object, to control the
flow of data to and from your various forms; a Form Manager object, to keep
track of active forms and handle any interactions among them; and an Error
Handler object, to take care of errors not dealt with locally. (In VFP 6 and
later, take a look at the classes in the _framewk library found in the Wizards
subdirectory to get a sense of how you can distribute responsibilities in an
application. Be forewarned: This is complex code, though quite well written.
Don't expect to fully understand it on the first pass.)
Once everything is set up the way it should be, your program
issues the READ EVENTS command to get the event loop started, and your
application sits back and waits for the user to pick something to do. The user
chooses a form to work on, and the form begins.
The data environment starts the ball rolling by optionally
setting up a private data session for this form, opening the tables, setting up
relations, and instantiating cursors. OpenTables (if it has code) and
BeforeOpenTables fire first and the tables get opened. The Load event of the
form or form set fires next. This is the first form event, and a place to put
code that should run before the rest of the form gets to work, such as settings
to be used throughout the form. There follows a flurry of Init events—first
created are the data environment and its contents (from the inside out), and
then controls from the innermost outward, by ZOrder within a container level.
This ends finally with the Init of the form, and is followed by the Activate
event of the form. Next, the Refresh methods are called (by whom? Refresh is a
method, not an event), one for each control and for the form itself, from the
outside in. Finally, the control designated to be the first on the form (by
TabIndex) fires its When clause, to make any last-minute changes before it
accepts the focus. If the When returns .T. (or nothing at all—.T. is assumed),
the GotFocus events fire—first the form's, then any container's, and finally
the control's. And there we sit, waiting for the next action.
While a control is sitting on a form, snoozing, waiting for
something to happen, no events fire, except perhaps the Timer event of a timer
control. When the user tabs to a control, or brings his mouse over a control,
that's when the fun begins. If the mouse was used to select a control, the
MouseMove event can be the first to sense the approach of the user's pointer.
(If both container and contained controls have code in their MouseMove events,
the container can even prepare the controls for the arrival of the mouse. But
watch out—MouseMove fires a lot. Too much code there could slow things down. On
a fairly powerful machine, 50,000 repetitions of a totally empty loop were
enough to result in some visual oddities.) Along with the MouseMove event, as
the mouse moves over the boundaries of the controls, the MouseEnter event
fires. Likewise, if you roll off the control, the MouseLeave event fires.
MouseEnter and MouseLeave were added in VFP 7.
The When event determines whether the object is allowed to
gain focus; if When returns .F., the events stop here for now. Once it's been
confirmed that the new object can have the focus, the last object's LostFocus
event runs. Next up are the new object's GotFocus and Message events.
What goes on when the user is within the domain of an
individual control depends to some extent on what the control is and what it is
capable of doing. A simple command button can sense and react to MouseDown,
MouseUp, Click and Valid events, all from a single mouse click, but we find we
usually put code only in the Click event. Although it is nice to have the other
options there, we suspect that many of the events don't see much use except
when designing very specific interfaces per clients' requests. A more complex
control, like a combo box or a grid, can have a richer set of interactions with
the user. We leave the specifics of each control's behavior to the Reference
section, but cover below exactly which controls have which events.
Finally, the user wants to leave our form. We usually
provide our users with a Close button for that purpose. But they can also
select the Close option from the form's control menu or the close
("X") button on the title bar. In the last two cases, the QueryUnload
event occurs, letting us detect the user's desire to quit, so we can ensure the
same handling that occurs when he uses the Close button. If QueryUnload lets
the form close, the form's Destroy event fires, followed by the Destroy events
of the objects on the form. The form's UnLoad event follows the Destroy events
of all contained objects. The data environment then shuts down. If the data
environment's AutoCloseTables property is set to true, the tables close and the
AfterCloseTables event fires. (If, on the other hand, AutoCloseTables is false,
the tables close only if the CloseTables method is called programmatically.)
The data environment's Destroy events follow. Like its associated form, the
data environment implodes, firing first the data environment's Destroy, and
then the Destroy events of any contained relations and cursors.
So, with 36 different base classes and 72 events to choose from, how's a body to know which classes support which events? Well, we suppose you could just open them up in the Form or Class Designer and check it out, but we've saved you the trouble by putting together this table. The events are listed in descending order based on how many base classes have them.
In addition to the events provided by the designers,
versions starting with VFP 6 let us add our own custom events. Sort of. The new
Access and Assign methods let us attach code to any property of any object. The
Access method for a property fires when the property's value is read; the
Assign method fires when a value is stored to the property (whether or not it's
actually changed). In addition, the This_Access method fires whenever any
property of the object is read or written.
We consider these events even though their names are "Access method" and "Assign method," because they fire on their own under specified circumstances. That makes them events, by us. If we did add these to the table above, they'd have to go at the top, since not only does every object support them, but for the property-specific versions, a given object can have as many Access methods and as many Assign methods as it has properties.
There are a few items, especially those which have been retained in Visual FoxPro "for backward compatibility" that can cause some real difficulties with the new event model. We cover a few of them here for your consideration. We think these are some of the first items you should be looking at revising if you're moving a FoxPro 2.x application to Visual FoxPro.
An ON KEY LABEL command defines an action to be performed as
soon as the keystroke is received. Unlike keyboard macros and keystrokes
processed in input controls, "OKLs," as they are often called, are processed
immediately, interrupting the current processing between two lines of code. If
these routines do not restore the environment to exactly the condition it was
in before the OKL initiated, the results, as Microsoft likes to say, can be
unpredictable. Disastrous is more like it. We recommend trying newer
alternatives, like the KeyPress event of the affected controls, rather than
depending on being able to control all the side effects of this
shot-in-the-dark.
ON commands are a different kind of event handler. ON KEY
reacts to each keystroke, ON ESCAPE to pressing the ESCape key, and ON KEY =
only to a specific keystroke (with a READ to be in effect). Give up on these.
Use the newer Visual FoxPro events. While there are exceptions—Christof Lange's
very clever Y2K solution is one of them—generally speaking, anything done with
the old ON model can be done in a more extensible, supportable way with the new
event model. ON ERROR and ON SHUTDOWN are the necessary exceptions that prove the
rule.
Integrating BROWSE with READ was the sought-after Holy Grail
of FoxPro 2.x. BROWSE, arguably one of the most powerful commands in the
language, was a bit testy about sharing the stage with READ. Because BROWSEs
did not make it easy to detect when they were activated and deactivated, it was
difficult to properly manage them within a READ situation. Although several
plausible solutions were advanced, most were very sensitive to changes in the
environment and difficult to work with. With the advent of grids in Visual
FoxPro, these complexities have been eliminated (and totally new ones
introduced), as should BROWSEs from your application code. If you haven't heard
enough of this topic, tune in to "Commands Never to Use" for more.
