Hacker’s Guide to Visual FoxPro
An irreverent look at how Visual FoxPro really works. Tells you the inside scoop on every command, function, property, event and method of Visual FoxPro.
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 CLEAR
ing 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::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.
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.
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. |
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.
ON KEY
Label commandsAn 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 BROWSE
s
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 BROWSE
s from your application code. If you haven’t heard
enough of this topic, tune in to “Commands Never to Use” for more.