It s Always Been That Way

A Gala Event

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.

What's an Event?

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.

How to Handle Events

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.

It's Not My Default!

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::NameOfTheMethod
but 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.

"Ready, Aim, Fire"

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.

Non-container Controls

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.

Containers

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

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.

Other Objects

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.

The Whole Event

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.

Event, Event—Who's Got the Event?

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.

Event

Object(s)

Meaning

Init

All base classes in VFP 6 and later. In earlier versions, some classes omitted this event.

Fires when the object is created and can accept parameters. If Init returns .F., the object is not created. Contained objects fire before containers, in the order added; see ZOrder in the reference section.

Error

All base classes in VFP 6 and later. In earlier versions, some classes omitted this event.

Fires when an error occurs in the method of an object. Receives the error number, method name and line number. If there's no code here or in its class hierarchy, the error handler established with ON ERROR, or as a last resort, the built-in VFP "Cancel Ignore Suspend" dialog, fires.

Destroy

All base classes in VFP 6 and later. In earlier versions, some classes omitted this event.

Code runs just before an object is released. Containers fire before contents.

DragOver, DragDrop

All base classes except ActiveDoc, Column, Cursor, Custom, DataEnvironment, FormSet, Header, Hyperlink, ProjectHook, Relation, Separator, Session, and Timer

Fire during and on completion, respectively, of a native VFP drag and drop operation. Each receives parameters to accept a reference to the data being dragged and the mouse coordinates.

MouseMove

All base classes except ActiveDoc, Cursor, Custom, DataEnvironment, FormSet, Hyperlink, OLEControl, OLEBoundControl, ProjectHook, Relation, Separator, Session, and Timer

Tracks mouse movement over an object. Receives status of Ctrl, Alt, and Shift keys, as well as left, middle, and right mouse button statuses.

MouseWheel

All base classes except ActiveDoc, Cursor, Custom, DataEnvironment, FormSet, Hyperlink, OLEControl, OLEBoundControl, ProjectHook, Relation, Separator, Session, and Timer

Fires on use of the rotating wheel available on some pointing devices. Receives parameters indicating direction of movement, current position, and the status of the Ctrl, Alt, and Shift keys.

MouseEnter, MouseLeave

(VFP 7)

All base classes except ActiveDoc, Cursor, Custom, DataEnvironment, FormSet, Hyperlink, OLEControl, OLEBoundControl, ProjectHook, Relation, Separator, Session, and Timer

Fires when the mouse enters or leaves, respectively, the boundaries of the control. Receives status of Ctrl, Alt, and Shift keys, as well as left, middle, and right mouse button statuses.

MouseDown, MouseUp, Click

All base classes except ActiveDoc, Column, Cursor, Custom, DataEnvironment, FormSet, Hyperlink, OLEControl, OLEBoundControl, ProjectHook, Relation, Separator, Session, and Timer

Fire when the user uses the left (primary) mouse button. Typically detected in the order shown.

RightClick, MiddleClick

All base classes except ActiveDoc, Column, Cursor, Custom, DataEnvironment, FormSet, Hyperlink, OLEBoundControl, OLEControl, ProjectHook, Relation, Separator, Session, and Timer

Fires when the right or middle mouse button, respectively, is clicked. These are not preceded by MouseDown and MouseUp events.

OLEDragOver, OLEDragDrop

All base classes except ActiveDoc, Column, Cursor, Custom, DataEnvironment, FormSet, Header, Hyperlink, OLEBoundControl, OLEControl, Relation, Separator, Session, and Timer

Fire during and on completion, respectively, of an OLE drag and drop operation. Receive parameters describing the drag action in progress, including a reference to the dragged data object.

OLEGiveFeedback

All base classes except ActiveDoc, Column, Cursor, Custom, DataEnvironment, FormSet, Header, Hyperlink, OLEBoundControl, OLEControl, Relation, Separator, Session, and Timer

Fires for the drag source of an OLE drag and drop each time the OLEDragOver event fires for a drop target. Allows the source to control the potential results of a drop and the icon in use.

OLEStartDrag, OLECompleteDrag

All base classes except ActiveDoc, Column, Cursor, Custom, DataEnvironment, FormSet, Header, Hyperlink, OLEBoundControl, OLEControl, ProjectHook, Relation, Separator, Session, and Timer

Fire for the drag source of an OLE drag and drop when the operation starts and when it ends. OLEStartDrag lets the data source indicate valid actions. OLECompleteDrag lets it respond to whatever occurred.

OLESetData

All base classes except ActiveDoc, Column, Cursor, Custom, DataEnvironment, FormSet, Header, Hyperlink, OLEBoundControl, OLEControl, ProjectHook, Relation, Separator, Session, and Timer

Fires for the drag source of an OLE drag and drop when the drop target requests data. Receives a reference to the data object and the format requested by the drop target.

DblClick

All base classes except ActiveDoc, Column, CommandButton, Cursor, Custom, DataEnvironment, FormSet, Hyperlink, OLEControl, OLEBoundControl, ProjectHook, Relation, Separator, Session, and Timer

Fires when the user double-clicks.

UIEnable

CheckBox, ComboBox, CommandButton, CommandGroup, Container, Control, EditBox, Grid, Image, Label, Line, ListBox, OLEBoundControl, OLEControl, OptionButton, OptionGroup, PageFrame, Shape, Spinner, TextBox

Fires when control becomes visible or invisible because of activation or deactivation of the page it sits on in a page frame.

This method receives a parameter indicating whether the page is being activated (.T.) or deactivated (.F.). We think that separate UIEnable and UIDisable events would be more consistent with the rest of the event model.

GotFocus, LostFocus

CheckBox, ComboBox, CommandButton, Container, Control, EditBox, Form, ListBox, OLEBoundControl, OLEControl, OptionButton, Spinner, TextBox

GotFocus occurs when the control is tabbed to or clicked on. The When event fires before, and determines whether, GotFocus fires. LostFocus fires when another control is clicked on or tabbed to, and that control succeeds in gaining the focus using its When event.

When

CheckBox, ComboBox, CommandButton, CommandGroup, EditBox, Grid, ListBox, OptionButton, OptionGroup, Spinner, TextBox

Good old When, a useful carryover from FoxPro 2.x, fires before GotFocus. A control can't have the focus unless When says it's okay. When also fires on each up-arrow and down-arrow keystroke while scrolling through a list box, but does not fire while scrolling in a combo box (use InteractiveChange for that).

Valid

CheckBox, ComboBox, CommandButton, CommandGroup, EditBox, Grid, ListBox, OptionButton, OptionGroup, Spinner, TextBox

Valid usually fires when a change is made. Even if no changes are made, tabbing though a combo box, edit box or text box fires its Valid event. Returning a numeric value from Valid determines the next control to get focus. Zero forces focus to stay on the current control without firing the ErrorMessage event; negative values move back through the tab order; positive values move forward.

ErrorMessage, Message

CheckBox, ComboBox, CommandButton, CommandGroup, EditBox, ListBox, OptionButton, OptionGroup, Spinner, TextBox

When Valid returns .F., ErrorMessage fires to display an error message. We hardly ever use this—we handle the problem in the Valid, prompting the user if necessary, and use the return of zero in Valid to handle this. Message is an old-fashioned way of putting text on the status bar—consider StatusBar and StatusBarText instead.

KeyPress

CheckBox, ComboBox, CommandButton, EditBox, Form, ListBox, OptionButton, Spinner, TextBox

Allows processing of input keystroke-by-keystroke, rather than waiting for input to be completed.

Moved, Resize

Column, Container, Control, Form, Grid, OLEBoundControl, OLEControl, PageFrame, Toolbar

Fire when the object has been moved or resized, respectively.

InteractiveChange, ProgrammaticChange

CheckBox, ComboBox, CommandGroup, EditBox, ListBox, OptionGroup, Spinner, TextBox

What UPDATED() always should have been, but at a finer level. Fires each time a change is made to a control's Value, even before focus has shifted from the control. InteractiveChange detects user changes; ProgrammaticChange fires on changes performed in code.

Activate, Deactivate

Form, FormSet, Page, ProjectHook (VFP 7), Toolbar

Occur when container gets the focus or the Show method is called, and when it loses the focus or the Hide method is called, respectively. With regard to the ProjectHook, these events occur when the developer activates or deactivates the Project Manager by clicking on or outside the window, or using the ACTIVATE WINDOW <project> command.

RangeHigh, RangeLow

ComboBox, ListBox, Spinner, TextBox

These events have two distinct uses, both of them outdated. For text boxes and spinners, these can be used to prevent out-of-range entries. Don't do it this way—use Valid instead. For combo boxes and list boxes, these are used only in forms converted from FoxPro 2.x to indicate the first element and number of elements settings.

DownClick, UpClick

ComboBox, Spinner

Not to be confused with MouseDown, fires when the down or up arrow of a spinner is pressed or, for combos, when the arrows on the scrollbar are used.

Load, Unload

Form, FormSet

Load is the first form event to fire, before Init, Activate and GotFocus. Load fires for the form set before the form. Unload is the last form event to fire, reversing the order: form first and then form set.

Paint

Form, Toolbar

Fires when the item is repainted. CAUTION: Don't Resize or Refresh an object in its Paint event—a "cascading" series can occur!

Scrolled

Form, Grid

Fires when the user uses the scrollbars. Parameter indicates how the scrollbars were used.

BeforeOpenTables, AfterCloseTables

DataEnvironment

Wrappers around the automatic behavior of the DataEnvironment. Occur before and after tables are automatically opened and closed, respectively.

BeforeRowColChange, AfterRowColChange

Grid

Fire before the Valid of the row or column of the cell being left, and after the When of the cell being moved to, respectively.

Deleted

Grid

Fires when the user marks or unmarks a row for deletion.

DropDown

ComboBox

Fires when user opens the list part of the combo box.

Timer

Timer

Fires when a timer is enabled and its Interval has passed.

BeforeDock, AfterDock, Undock

ToolBar

Microsoft missed a great chance here for a property to tell you which toolbars are attached just below the menu—a WhatsUpDock property. These events probably won't shock you: BeforeDock fires before a toolbar is docked, AfterDock after the fact, and Undock when the toolbar is moved from a docked position.

QueryUnload

Form

Fires when a form is released other than through a call to its Release method or explicit release of the referencing variable. Allows testing of the ReleaseType property to determine how the form is being released and takes appropriate action.

BeforeBuild, AfterBuild

ProjectHook

Fire before and after a project is built, whether through the interface or the Build method.

QueryAddFile, QueryModifyFile, QueryNewFile (VFP 7), QueryRemoveFile, QueryRunFile

ProjectHook

Fire when the specified action is taken on a file in the project. NODEFAULT in the method prevents the indicated action.

ShowDoc, HideDoc

ActiveDoc

Fire when the user navigates to or from an active document application, respectively.

Run

ActiveDoc

Fires when an active document application is all set up and ready to go. Use it to start the application doing something useful.

CommandTargetQuery, CommandTargetExec

ActiveDoc

Fire when the user of an active document application in a browser begins or completes an action in the browser that might be handled by the application.

ContainerRelease

ActiveDoc

Fires when the browser holding an active document application lets go of the application. Allows the app to figure out what to do next.

ReadActivate, ReadDeactivate, ReadShow, ReadValid, ReadWhen

Form

Based on the FoxPro 2.x READ model, these work only in the "compatibility" modes. Ignore them unless you're converting older apps.


Add Your Own Events

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.

How to Mangle the Event Model

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.

On Key Label commands

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 In General

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.

BROWSEs

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.

View Updates

Copyright © 2002 by Tamar E. Granor, Ted Roche, Doug Hennig, and Della Martin. All Rights Reserved.



Hacker's Guide to Visual FoxPro 7. 0
Hackers Guide to Visual FoxPro 7.0
ISBN: 1930919220
EAN: 2147483647
Year: 2001
Pages: 899

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net