The event model is an important aspect of ObjectPAL. The event model is the map of when events associated with UIObjects execute during Run mode. This chapter explores the event model in great detail.
The Event Model Makes Sense
As we have discussed, the form is the highest container in ObjectPAL. It always contains at least one page. You place objects, such as fields, table frames, and buttons inside a page. All UIObjects, including forms, pages, and UIObjects you place on pages, contain a set of events. All, for example, have an open event. The event model is not linear, but can be used in a linear fashion. Much like a procedural language, you can attach code to the open event of the form, page, box, field, and button. The code executes in order. First the code on the form executes, then the code on the page, box, and field executes. Finally, the code on the button executes.
If you place the following line of code in all the open events of the objects mentioned above, you"™ll notice the message information boxes you just saw are executed in a linear fashion:
MsgInfo("Open Event", self.name)
When the box and the button are on the same level, however, they both can"™t execute at the same time. What dictates the order of execution? The containership hierarchy determines which code is executed first. Generally, the order in which objects are placed on the page dictates which object is opened first. For an experiment, place a larger box around both of these objects and alternate moving them onto the page level. What do you expect to happen? Try it and find out.
Note: You can alter the path of objects by moving objects around in the containership hierarchy. Move objects on the same level by selecting Format | Order | Bring to Front and Format | Order | Send to Back.
Although events can execute in a linear fashion"”that is, one event executes after another"”you don"™t program ObjectPAL in a linear fashion. When you program in Paradox, think, "What object do I want to alter?" For example, if you want to check a value in a field after the user enters it, go to that field and put code on the canDepart event. You can use canDepart to check the field"™s contents before it permits the cursor to leave the field.Default Behavior Occurs Last
The default behavior of the open event is to open the objects within it. When exactly does an object open? All default behavior occurs just before endMethod.
Take a closer look at the events open and arrive and when the default behavior occurs. An object is always opened before it is arrived on. Therefore, code in the open event always executes before code in the arriveevent. Any code that you want to execute as a form opens or before a form opens goes in the open event. Any code that you want to execute after a form opens goes in the arrive event. The arrive event is a good place for code that requires user interface objects, because UIObjects aren"™t opened until the end of the openevent (just before endMethod).
Events Are the Event Model
Objects in ObjectPAL come with events you can place code in. In addition to the code you can add to an event, each event has default built-in behavior. This behavior is executed just before endMethod. Sometimes an event is used by an object, and sometimes it's passed to another event. This using and passing of events is known as the event model, which is a map of the order in which events are executed. You cannot see this hidden code, but it is there and you can affect it, for example, with doDefault, disableDefault, and enableDefault.
Events can be generated by a user action, such as moving the mouse or pressing a key, or by ObjectPAL. An event generated by ObjectPAL usually is generated or called from a user"™s actions. So, in a sense, all events are started in one way or another by the user. Thinking of the event model in this simplified way is a good way to program. An open event occurs because the user chooses File | Open | Form. It"™s easier to think of a user opening a form, which causes the open event, than it is to think of the open event as receiving an internal event generated by ObjectPAL.
Likewise, when a user clicks a button, code in the button"™s pushButton event is executed, right? Wrong. pushButton is an internal event that is called by the external event mouseUp only when the cursor is inside the boundaries of the button. It"™s much easier, however, to think that when the user clicks a button, several events are executed in order: mouseDown, mouseUp, mouseClick, and pushButton. There are times when it"™s important to understand that mouseUp calls pushButton only when the cursor is within the boundaries of the button. Even so, most of the time you can think in terms of simple user actions.
Understanding when events are executed is key to understanding ObjectPAL. The better you know when code executes, the better and cleaner your applications will be. Although you usually use pushButton when you program a button, occasionally you will want to use mouseDown, mouseUp, or mouseClick. Understanding what the differences are is important.
On The Net: The form http://prestwood.com/forums/paradox/books/official/files/BUTTON.FSL demonstrates the order of execution of a button's mouseDown, mouseUp, mouseClick, and pushButton events. In addition, it demonstrates mouseEnter and mouseExit.
Some Event Model Rules
There are many rules that govern the event model. The following are some introductory rules to keep in mind:
All events generate a packet of information. This event packet contains information on what generated the event, the target of the next event, and more. Events are generated either by ObjectPAL or by a user interacting with a form. A few examples of events for which ObjectPAL generates events are open, arrive, and depart. A few examples of events for which a user generates events are mouseClick, keyChar, and menuAction.
All events go to the form first. If the target is an object other than the form, the event goes through the form's prefilter and the event is sent to the target. This is important because the prefilter of the form is a great location to code certain types of generic code.
Internal events are passed from the form's prefilter to the target object. Internal events, discussed in detail in the next section, are events called from within Paradox.
External events, like internal events, are passed from the form's prefilter to the target object. Unlike internal events, however, external events can bubble back up to the form.
The Form's Init Event
The only object that supports this event is the form object itself; all other UIObjects within the form do not have this event. The chief benefit of this event, unlike all other form-level events, is that it does not have a prefilter clause. Now, you have control over what happens before the open event. In previous versions of ObjectPAL, the open event was called from a prewired C routine. When you access the ObjectPAL editor, you see the following:
method init (var eventInfo Event)
endMethod
How to Use the Init event
The init event is called once whenever a form is opened. The default behavior of init is to call open. It is important to note that when you use disableDefault in init, the form and all the objects within the form are still opened. However, any code in the open events of all objects does not execute. You have, in fact, disabled the code in the object"™s open events. To stop a form from opening, set an error code as in the following:
method init(var eventInfo Event) if cmSomeCondition() = True then ;In place of CanNotArrive, you ;can use any non zero value. eventInfo.setErrorCode(CanNotArrive) endIf endMethod
The primary use for the init event is for initialization code. For example, if you want to set up some tables before the open event tries to open the tables, do it in init. If, after the tables are created, you want to use them in init, then call the open event with doDefault, as shown below:
Categorizing Events: Internal, External, and Special
Events are categorized as internal, external, and special. These three categories of events follow very specific rules. Table 6-1 lists the events.
Internal
External
Special
init
mouseClick
pushButton
open
mouseDown
changeValue
close
mouseUp
newValue
canArrive
mouseDouble
arrive
mouseRightDown
setFocus
mouseRightUp
canDepart
mouseRightDouble
removeFocus
mouseMove
depart
keyPhysical
mouseEnter
keyChar
mouseExit
error
timer
status
action
menuAction
Table 1: The Three Types of EventsInternal EventsInternal events
are events generated by Paradox. A good case study example of an internal event is open. The open event occurs from outer container inward. The form is opened, then the page, and then the objects in the page. Code in the canArrive, arrive, and setFocus events are also executed from outer container inward.
Like external events, internal events go first to the form and then to the target object. Unlike external and special events, internal events do not bubble. In simple terms, the event dies at the target. In other words, the complete path for an internal event is sent to the form and back to the object.
Take a closer look at the default behavior of each internal event. Internal events are always called either from other internal events or from an external built-in event. Paradox has many built-in default behaviors. The following sections describe the internal events and their default behaviors.
Rules Guiding Internal Events
Code placed in their counterparts close, canDepart, and depart execute from inner container out. To study this, suppose that you have a field inside a box on the first page of a form and you try toclose the form. In what order does the code placed in various object's canDepart events execute? First, code in the canDepart for the field is executed. Then, code in its container the boxs canDepart is executed; and then, finally, code in the box container the page canDepart is executed. Code placed in the close, canDepart, and depart events are executed from inner container outward.
Like external events, internal events go first to the form and then to the target object. Unlike external and special events, internal events stop at the target. In other words, the complete path for an internal event is sent to the form and back to the object. Table 6-2 describes the internal events. The concept of bubbling back up to the form does not exist for internal events.
Event
Short Description
Init
Executed once when the form is opened
Open
Executed once for every object when the form is opened
Close
Executed once for every object when the form is closed
CanArrive
Executed before moving to an object
CanDepart
Executed before moving off an object
arrive
Executed after moving to an object
depart
Executed after moving off an object
setFocus
Executed whenever an object gets the focus
removeFocus
Executed whenever an object loses the focus
mouseEnter
Executed whenever the mouse pointer enters an object
mouseExit
Executed whenever the mouse pointer exits an object
timer
Executed at a time interval specified by the programmer
Table 2: Internal Events
Default Behavior of Internal Events
Take a closer look at the default behavior of each internal event. Internal events are always called either from other internal events or from an external event. There is much built-in default behavior in Paradox. The following paragraphs describe the internal events and their default behavior.
Every object has to be opened. The open event is called only once for every object, starting with the form, then the page, then the objects contained by the page, and finally the objects contained within that container. After the first page is completely open, the process starts over with the next page in the form.
Remember that the prefilter of the form sees the open event before the target object sees it. The default code for open calls the openevent for each of the objects it contains. Then, the open event for each one of those objects calls the open event for the objects it contains, and so on. The default behavior for the close event acts in the same way but in reverse. If a table is bound to the object, the object also opens the table. Any errors will abort the open process.
The canArrive event is interesting. It occurs before movement to an object is permitted. Think of canArrive as asking permission to move to the object. Contrary to what is implied in the manuals, canArrive is not used just for restricting entrance to a field. You can use this event to execute almost any kind of code just before arriving on an object. The canArrive event blocks arrival for records beyond the end of a table"”except, of course, when you are in Edit mode and the Auto-Append option is checked in the data model. Any object whose tab stop property is unchecked also is blocked.
The arrive event is executed after movement has arrived on an object. An arrive event can be called only after a canArrive event. Pages, table frames, and multirecord objects move to the first tab stop object they contain. When you arrive on a field or a record, the object is made current; if you"™re in Edit mode, an edit window is created for the edit region of a field. If the object is a drop-down edit list, the focus moves to the list. If the object is a radio button, the focus moves to the first button.
The setFocus event occurs every time an object gets the focus. If the object getting the focus is contained in another object, setFocus is called for each container"”from the outermost container inward. For example, if a page contains a box, which contains a field, code in setFocus is executed first for the page, next for the box, and then for the field. In an edit field, the default code highlights the currently selected edit region and causes the insertion point to blink. The focus property is set to True, and the status message reports the number of the current record and the total number of records. For buttons, if the tab stop property is set, a dotted rectangle is displayed around the label.
The canDepart event is executed before a move off an object. Field objects try to post their contents and trip changeValue. If the record is a changed record, the object tries to commit the current record. If the record is locked, the form tries to unlock it.
The removeFocus event occurs when an object loses the focus. On field objects, the flashing insertion point and highlight are removed. On a button, the dotted rectangle is removed. The object"™s focus property is set to FALSE. This is called for the active object and its containers.
After canDepart and removeFocus have executed successfully, the depart event is called. Field objects close their edit windows, then repaint and clean up the screen.
The mouseEnter event is generated whenever the mouse pointer enters an object. Form, page, and button objects set the pointer to an arrow. Field objects set the pointer to an I-beam. If a button is still down, its value toggles between TRUE and FALSE.
The mouseExit event is generated whenever the mouse pointer exits an object. Field objects set the pointer back to the arrow.
The init Event
The init event is called once when a form is opened. The primary use for the init event is for initialization code. The only object that supports this event is the form object itself; all other UIObjects within the form do not have this event. The chief benefit of this event, unlike all other form-level events, is that it does not have a prefilter clause. Now, you have control over what happens before the open event. In previous versions of ObjectPAL, the open event was called from a prewired C routine that the developers of Paradox wrote.
Default Behavior
The default behavior of init is to call open. You should note that when you use disableDefault in init, the form and all the objects within the form are still opened. However, any code in the open events of all objects does not execute. You have, in fact, disabled the code in the object"™s open events. To stop a form from opening, set an error code in the init event using eventInfo.setErrorCode(CanNotArrive).
The open and close Events
Every object has to be opened and is eventually closed. The open event is called only once for every object, starting with the form, then the page, then the objects contained by the page, and finally the objects contained within that container. After the first page is completely open, the process starts over with the next page in the form. Remember that the prefilter of the form sees the open event before the target object sees it.
Default Behavior
The default code for open calls the open event for each of the objects it contains. Then, the open event for each one of these objects calls the open event for the objects it contains, and so on. If you use DisableDefault in the open of an object, the object and the objects it contains are still opened. The appearance is that DisableDefault has no effect; however, it does. DisableDefault in the open event prevents the code in the openevent of the objects it contains from executing. If a table is bound to the object, the object also opens the table.
The default behavior for the close event acts in the same way.
Effect of Errors
Any errors abort the open process and put the object in Design mode. For example, eventInfo.setErrorCode prevents an object from opening and puts the object in Design mode.
The canArrive Event
The canArrive event occurs before movement to an object is permitted. Think of canArrive as asking permission to move to the object. Contrary to what is implied in the manuals, canArrive is not used just for restricting entrance to a field. You can use this event to execute almost any kind of code just before arriving on an object.
Default Behavior
The canArrive event blocks arrival for records beyond the end of a table"”except, of course, when you are in Edit mode and the Auto-Append data model property is checked. Any object whose tab stop property is unchecked also is blocked. You can"™t disable the default behavior using DisableDefault. Instead, use the following:
eventInfo.setErrorCode(CanNotArrive).
EventInfo
The eventInfo packet for the canArrive event is type MoveEvent. The reasons for a MoveEvent are PalMove, RefreshMove, ShutDownMove, StartupMove, and UserMove. Suppose that you want to know whether a move was made by ObjectPAL or by a user. You could use the following in canArrive:
1: Switch 2: case eventInfo.reason() = PalMove 3: : message("ObjectPAL move") 4: case eventInfo.reason() = UserMove 5: : message("move by user") 6: endSwitch
The MoveEvent eventInfo packet has a unique method called getDestination(), which enables you to know which object the user is trying to move to. Suppose that you want to know whether a user is going to move to either of two fields, such as Last_Name or First_Name, and you want to do this at the form level. You can use the following:
1: ;Form :: canArrive prefilter 2: method canArrive(var eventInfo MoveEvent) 2: var 3: ui UIObject 4: endVar 5: if eventInfo.isPreFilter() then 6: ;// This code executes for each object on the form 7: else 8: ;// This code executes only for the form 9: eventInfo.getDestination(ui) 10: if ui.name = "Last_Name" or ui.name = "First_Name" then 11: ;Execute code here. 12: endIf 13: endMethod
Effect of Errors
Any error denies permission to arrive on the object. Suppose that you want to stop movement to a field. You could use the following code in the canArrive built-in event:
eventInfo.setErrorCode
ObjectPAL does provide the constant CanNotArrive to humanize the language a bit. As an alternative to using any nonzero value, you could use the following:
eventInfo.setErrorCode(CanNotArrive)
The arrive Event
The arrive event is executed after movement has arrived on an object. An arrive event can be called only after a canArrive event. You can use the inserting and blankRecord properties to tell when a record is being inserted and when a record is blank.
Default Behavior
The arrive event calls the arrive event of the objects it contains. This process occurs inward; that is, from the outer container in"”the form, the page, the objects in the page, and so on. Pages, table frames, and multirecord objects move to the first tab stop object they contain. When you arrive on a field or a record, the object is made current. If you"™re in Edit mode, an editing window appears when the field is touched, in FieldView or MemoView. If the object is a drop-down edit list, the focus moves to the list. If the object is a radio button, the focus moves to the first button.
EventInfo
The eventInfo packet for the arrive event is type MoveEvent.
Effect of Error
Any error prevents arriving on the object. Visually, an error means that no object gets focus. DisableDefault seems to have the same effect as setting an error. As usual, the preferred way to stop the behavior is to set an error.
The setFocus Event
The setFocus event occurs every time an object gets the focus.
Default Behavior
If the object getting the focus is contained in another object, setFocus is called for each container"”from the outermost container inward. For example, if a page contains a box, which contains a field, code in the setFocus event is executed first for the page, next for the box, and then for the field. In an edit field, the default code highlights the currently selected edit region and causes the insertion point to blink. The focus property is set to True, and the status message reports the number of the current record and the total number of records. For buttons, if the tab stop property is set, a dotted rectangle appears around the label.
The canDepart Event
The canDepart event is executed before a move off an object.
Default Behavior
Field objects try to post their contents and trip changeValue. If the record is a changed record, the object tries to commit the current record. If the record is locked, the form tries to unlock it.
EventInfo
The eventInfo packet for the canDepart event is type MoveEvent.
The removeFocus Event
The removeFocus event occurs when an object loses the focus.
Default Behavior
On field objects, the flashing insertion point and highlight are removed. On a button, the dotted rectangle is removed. The object"™s focus property is set to FALSE. This is called for the active object and its containers.
EventInfo
The eventInfo packet for removeFocus is type Event.
The depart Event
After canDepart and removeFocus have executed successfully, the depart event is called.
Default Behavior
Field objects close their edit windows and then Paradox repaints and cleans up the screen.
EventInfo
The eventInfo packet for depart is type MoveEvent. The reasons for a MoveEvent are PalMove, RefreshMove, ShutDownMove, StartupMove, and UserMove. All these event reasons are self-explanatory except perhaps for RefreshMove. An example of when RefreshMove is generated is when data is updated by scrolling through a table.
Effect of Error
Any nonzero value stops the departure from a field or a page. For example, eventInfo.setErrorCode(CanNotDepart) in the depart event of a field or page keeps focus on the current field or page. In the case of a page, however, focus is lost. Therefore, a better location to execute this code for all objects is in canDepart. Setting the error code in the depart event of the form does not stop the form from closing.
The mouseEnter Event
The mouseEnter event is generated whenever the mouse pointer enters an object.
Default Behavior
Form, page, and button objects set the pointer to an arrow. Field objects set the pointer to an I-beam. If a button has received a mouseDown but not a mouseUp, and is still down, its value toggles from False to True. You can disable this default behavior by using DisableDefault in mouseEnter.
The mouseExit Event
The mouseExit event is generated whenever the mouse pointer exits an object.
Default Behavior
Field objects set the pointer back to the arrow. If a button has received a mouseDown but not a mouseUp, and is still down, its value toggles from True to False. You can disable this default behavior by using DisableDefault in mouseEnter.
External EventsExternal events
are generated by the user interacting with a form. Keep in mind, however, that ObjectPAL can call some external events.
Now take a closer look at the default behavior of each external event. Both internal and external events go first to the form and then to the target object. External events, however, unlike internal events, bubble back up to the form. Paradox has many built-in default behaviors. The default behavior for an external event is to pass the event to its container, which is how it bubbles up to the form. The following sections explain the default behavior of the external events that do something in addition to bubbling their events.
Rules Guiding External Events
External events are events generated by the user interacting with a form and by ObjectPAL. Both internal and external events go first to the form and then to the target object. External events, however, unlike internal events, bubble back up to the form. The default behavior for an external event is to pass the event to its container, which is how it bubbles up to the form. External events are generated when a user interacts with the user interface. Table 6-3 describes the external events.
Event
Short Description
MouseMove
Occurs whenever the mouse moves
MouseDown
Occurs when the left mouse button is pressed
MouseUp
Occurs when the left mouse button is released
MouseClick
Occurs when the pointer is inside an object and the left mouse button is pressed and released
MouseDouble
Occurs when the left mouse button is double-clicked
MouseRightDown
Occurs when the right mouse button is pressed
MouseRightUp
Occurs when the right mouse button is released
MouseRightDouble
Occurs when the right mouse button is double-clicked
KeyPhysical
Occurs whenever any key is pressed
KeyChar
Occurs whenever a character key is pressed
Action
Executes when a keystroke or menu option maps to an action
MenuAction
Occurs when a menu option or a toolbar icon is selected
Error
Occurs whenever an error is encountered
Status
Occurs whenever a message is displayed in the status bar
Table 3: External Events
Default Behavior of External Events
Now take a closer look at the default behavior of each external event. The following paragraphs explain the default behavior of the external events that do something in addition to bubbling their events.
The mouseDown event occurs when the left mouse button is pressed. The event packet for mouseDown contains the mouse coordinates in twips (1/1,440 of an inch) relative to the last object that executed a mouseEnter event. If the object is a field that is active, the field is put into Field View. If the object is a button, its value is toggled between True and False.
The mouseRightDown event occurs when the right mouse button is pressed. It is the same as the mouseDown event, except that it uses the right mouse button instead. If the object is a formatted memo, a graphic, OLE, or an undefined field, a pop-up menu is displayed.
The mouseUp event occurs when the left mouse button is released. mouseUp is called for the last object that received a mouseDown event. Therefore, an object always sees the mouseDown and mouseUp events in a pair. If you select text, mouseUp ends the selection. If the object is a button and the pointer is still inside the button, mouseUp calls the pushButton event. The mouseRightUp event is the same as the mouseUp event, except that it uses the right mouse button instead.
The mouseDouble event occurs when the left mouse button is double-clicked. A field object enters Field view. The mouseRightDouble event is the same as the mouseDouble event, except that it uses the right mouse button.
The movement of the mouse is tracked with the mouseMove event. Whenever the pointer is moved within an object, the mouseMove event is executed.
The keyPhysical event occurs whenever any key is pressed and each time a key is autorepeated. keyPhysical includes all the physical keys on the keyboard, including the character keys, the function keys, and the alt, ctrl, and esc keys. A keystroke goes first to Windows and then to Paradox, which gives it to the form"™s prefilter. The form sends it to the active object for processing. The object"™s keyPhysical determines whether the keystroke represents an action or a display character. Actions are passed to the actionevent, and display characters are passed to keyChar.
The keyChar event occurs whenever a character key is pressed. As discussed above, the keyPhysical event for the active object sends action events such as DataNextRecord to the action event, and it sends characters such as "a" to keyChar. If the active object is a field in Edit mode, a lock is put on the table before the first character is inserted. If the active object is a button and the character is a spacebar, the button"™s pushButton event is called. Remember a button can be active only if its TabStop option is set to TRUE.
The action event is called frequently. It executes when it is sent an action keystroke from keyPhysical, when menuAction maps to a menu option, or when a event such as DataPostRecord calls for an action. The default behavior for action is extensive because all actions go through it. For example, Page Down moves to the next record, F9 toggles Edit mode, and Insert inserts a record only if the form is in Edit mode.
The menuAction event occurs when a menu option, a Toolbar icon, or an option from the control box is selected. The option is sent first to the form"™s action event for processing and then to the active object.
The error event occurs right after an error is encountered. You shouldn"™t test for errors with the error event. Use action instead. An error is passed to its container until it gets to the form. The form might or might not display a message, depending on the severity of the error. You can trap for errors and alter the default behavior in the action event before the error gets to the error event. You can use the error event to add to the built-in default error behavior.
The status event occurs whenever a message is displayed on the status bar. The default behavior of status is too extensive to be described here. In short, any time you see a message in one of the four status areas, an event has gone through the status event.
The mouseDown Event
The mouseDown event occurs when the left mouse button is pressed.
Default Behavior
If the object is a field that is active, the field is put into field view. If the object is a button with its tab stop property set to True, the button becomes active. A button"™s value is toggled from False to True. You can verify this value by typing the following code into the pushButton event of a button:
The eventInfo packet for mouseDown contains the mouse coordinates in twips relative to the last object that executed a mouseEnter event.
The mouseRightDown Event
The mouseRightDown event occurs when the right mouse button is pressed. It is the same as the mouseDown event, except that it uses the right mouse button instead.
Default Behavior
If the object is a formatted memo, a graphic, OLE, or an undefined field, a pop-up menu appears.
The mouseUp Event
The mouseUp event occurs when the left mouse button is released. mouseUp is called for the last object that received a mouseDown event. Therefore, an object always sees the mouseDown and mouseUp events in a pair.
Default Behavior
If you select text, mouseUp ends the selection. If the object is a button and the pointer is still inside the button, mouseUp calls the pushButtonevent.
The mouseRightUp event is the same as the mouseUp event, except that it uses the right mouse button instead.
The mouseDouble Event
The mouseDouble event occurs when the left mouse button is double-clicked.
Default Behavior
A field object enters Field view.
The mouseRightDouble event is the same as the mouseDouble event, except that it uses the right mouse button.
The mouseMove Event
The movement of the mouse is tracked with the mouseMove event. Whenever the pointer is moved within an object, the mouseMove event is executed.
Default Behavior
An active edit field checks the state of the shift key. If the shift key is down (or pressed), the selection is extended. If necessary, an active graphic field scrolls the graphic. When you press and hold the mouse button inside an object, the mouseMove event of the object is called until you release the button (even when the pointer moves outside the object).
The keyPhysical Event
The keyPhysical event occurs whenever any key is pressed and each time a key is autorepeated. keyPhysical includes all the physical keys on the keyboard, including the character keys, the function keys, and the alt, ctrl, and esc keys.
Default Behavior
A keystroke goes first to Windows and then to Paradox, which gives it to the form"™s prefilter. The form sends it to the active object for processing. The object determines whether the keystroke represents an action or a display character. Actions are passed to the action event, and display characters are passed to the keyChar event.
The keyChar Event
The keyChar event occurs whenever a character key is pressed. Actually, the keyPhysical event for the active object sends action events such as nextRecord() to the action event, and it sends characters such as "a" to keyChar; if a keyPhysical does not map to an action, then it calls keyChar.
Default Behavior
If the active object is a field in Edit mode, a lock is put on the record before the first character is inserted.
If the active object is a button and the character is a spacebar, the button"™s pushButton event is called without calling mouseDown or mouseUp. In other words, your code in mouseDown and mouseUp does not execute. (Remember, a button can be active only if its tab stop is set to True.)
The action Event
The action event is called frequently. It executes when it is sent an action keystroke KeyEvent from keyPhysical, when a MenuEvent from menuAction maps to a menu option, or when a event calls for an action. An example of a event calling for an action is UIObject.postRecord(). In this case, the postRecord() calls for a DataPostRecord. The constant DataPostRecord is sent to the action event. You can send action commands to action using the action() method.
Default Behavior
The default behavior for action is to perform the pending action. Its default behavior is extensive because all actions go through it. For example, Page Down moves to the next record, F9 toggles Edit mode, and alt-tab task-switches to another application.
The menuAction Event
The menuAction event occurs when a menu option or Toolbar icon is selected. You can send MenuCommands to menuAction using the menuAction() event.
Default Behavior
The option is sent first to the form"™s menuAction event for processing and then to the active object.
The error Event
The error event occurs after an error is encountered. Because error is always called after an error, to prevent an error, trap for specific types of errors in the action event.
Default Behavior
An error is passed to its container until it gets to the form. The form might or might not display a message, depending on the severity of the error; that is, depending on whether the error is a warning or critical level error. All critical errors produce the Error dialog box. Warning errors, on the other hand, may or may not display a message in the status bar. You can trap for errors and alter this default behavior in the form"™s action event.
The status Event
The status event occurs whenever a message is displayed in the status bar.
Default Behavior
The default behavior of status is too extensive to be described here. In short, any time you see a message in one of the four status areas, it has gone through the built-in status event. For example, whenever a key violation occurs, a message is sent to the StatusWindow. Special Events
Special events are specific to a few objects, such as a field"™s newValue event. The following sections explain the default behavior of the special events.
Introducing Special Events
Special events are specific to a few objects, such as newValue of a field. Table 6-4 describes the special events.
Event
Description
pushButton
Executes whenever you click a button
newValue
Executes whenever the value in a field changes
changeValue
Executes whenever the value in a table changes
Table 4: Special Events
Default Behavior of the Special Events
The following paragraphs explain the default behavior of the special events.
The only UIObjects that have a pushButton event are buttons and fields displayed as a list box. The form has a pushButtonevent because it dispatches it with its prefilter clause. pushButton occurs when the pointer is inside an object for both the mouseDown and mouseUp events. In fact, mouseUp calls pushButton. Button objects visually depress and pop out. Check boxes check or uncheck. Radio buttons push in or pop out. If the Tab Stop property is set to True, the focus moves to it.
The newValue event is executed after the value in a field changes. Code in newValue is executed even if the value is changed only onscreen.
Code in the changeValue event on a defined field, on the other hand, is executed by a change in a table. Code in the changeValue event on an undefined field is executed when the value in the field changes.
The changeValue event is executed before a value in a table is changed. If you have code on both changeValue and newValue, the code on changeValue occurs first; that is, before the value changes. Code in the newValue event is executed after the value changes. Therefore, if you want to do validity checks on a field, changeValue is a good place to put them.
The pushButton Event
Only button objects and the form have a pushButton event. Some field display types are actually composite objects that include buttons; fields themselves never have a pushButton. For example, a field displayed as a check box is composed of a field, a button, and a text object.
The form, which has all the events, acts like a dispatcher. The pushButton event occurs when the pointer is inside an object for both the mouseDown and mouseUp events. In fact, mouseUp calls mouseClick, which then calls pushButton.
Default Behavior
Button objects visually depress and pop out. Check boxes check or uncheck. Radio buttons push in or pop out. If the tab stop property is set to True, the focus moves to it.
The newValue Event
Only fields and the form have a newValue event. The newValue event is executed after the value in a field changes. Code in the newValue event is executed even if the value is changed only onscreen. Code in the form"™s open event also executes the code in the newValue event for each field object in the form. Code in the changeValue event, on the other hand, is executed by a change in a table.
The changeValue Event
The changeValue event is executed before a value in a table is changed. If you have code on both changeValue and newValue, the code on changeValue occurs first, before the value changes. Code in the newValue event is executed after the value changes. Therefore, if you want to do validity checks on a field, do them in changeValue. To fully understand the relationship between DoDefault, self.value, and eventInfo.newValue(), enter the following code into the changeValue event of a field bound to a table and then change the value:
In this example, you study the events by using a feature of the debugger called the Tracer. Your goal is to use the Tracer to demonstrate the event model and default behavior.
Step By Step
Create a new form by selecting File | New | Form.
Open the Editor window for the action event of the form.
Open the Tracer by selecting View | Tracer.
From the Tracer window, choose Properties | Built-In Events.
The Select Built-in Events for Tracing dialog box, shown here, lists all the events you can trace. In this dialog box, choose Select All and click on OK.
Illustration 1
Make sure that Properties | Trace On is checked:
Illustration 2
Run the form (there is no need to save the form). Note in Figure 6-1 how many events occur even on an empty form.
Figure 1: The Tracer tracing codeTracing one or two events at a time is a great way to get acquainted with the event model. Take some time right now (about 30 minutes) to try tracing various combinations of events.
Tracing Your Own Code Example
This section demonstrates how to use the Tracer to trace just your code. Suppose that while you are developing an application, an error occurs and you have no idea what code is causing the error.
Step By Step
Change your working directory to Paradox"™s Samples directory and open the OV-LIKE.FSL form that you created in Chapter 3 (or any form that has ObjectPAL code added to it).
Open the Editor window for any event.
Select Program | Compile with Debug. This step is crucial. Selecting this option allows the compiler to generate the data needed by the run-time interpreter to display your code in the Tracer.
Select View | Tracer to open the Tracer.
Select Properties | Built-In Events and make sure that no events are selected for tracing.
Make sure that Properites | Trace On and Properties | Show Code are checked.
Run the form, select the various buttons, and see what happens (see Figure 6-2).
Figure 2: Using the Tracer to trace your own code
You can use the Tracer for two purposes: to analyze the events and/or to analyze your code. When analyzing or debugging your code, the Tracer is great for finding the location of bugs.
Tracing execution on a large form is very time-consuming. The Tracer updates the screen every time it executes a line of code. As usual in a GUI, screen updates dramatically slow you down. Shrink the Tracer execution window as small as possible. When you want to see the trace execution, then open the window. Following are the steps:
Run it.
Press ctrl-break.
Minimize it.
This solution works so well, you can now just leave the Tracer execution windows minimized on your screen.Introducing the Event Packet
When you open a form interactively, many events are generated by ObjectPAL. The form opens and then the prefilter tells the page to open by sending an event to it. Understanding the sending and receiving of events is understanding the event model.
Whenever a event is called, an information packet is generated. This information packet often is passed from one event to the next. This information is called the event packet. You can read any value in the event packet with the eventInfo variable. Table 6-5 describes the different types of events.
Type of Event
Information About
Events
ActionEvent
Editing and navigating a table
action
ErrorEvent
Errors
error
Event
Base event type from which all others are derived"”inherited
init, open, close, setFocus, removeFocus, newValue, and pushButton
The type of eventInfo a event generates is declared in its prototype syntax. Every time you open the ObjectPAL editor for a event, Paradox automatically prototypes the event for you: the first line of every event. For events, simply go into the event, and it will tell you on the first line the type of event the eventInfo variable is. Take a look at a button"™s pushButton event:
Notice var eventInfo Event in parentheses. In this prototype, eventInfo is a variable that is declared as an Event. This is important because it tells you what types of events can be used to extract and set information in the eventInfo variable. With pushButton, all the events can be used.
The eventInfo packet is defined as a KeyEvent type event. This indicates that you can use any of the KeyEvents to extract and deal with the event information. To see a list of all the events that work with KeyEvent, from within the ObjectPAL editor, select View | ObjectPAL Quick Lookup to bring up the ObjectPAL Quick Lookup dialog box, and then KeyEvent from the Types and Methods tab, shown here:Illustration 3
You can use any of the KeyEvents in the keyPhysical or keyChar events to deal with and alter an event created by the keyboard. For example, to prevent all ctrl keystroke combinations on a certain field, alter the field"™s keyPhysical as follows:
1: ;Field :: keyPhysical 2: method keyPhysical(var eventInfo KeyEvent) 3: if eventInfo.isControlKeyDown() then 4: disableDefault 5: msgInfo("", "Control key combinations are invalid here") 6: endIf 7: endMethod
Â
In this routine, the ctrl key is trapped for, and disableDefault prevents the default behavior.The Path of eventInfo from Field to Field
One important part of the event model is the sequence of execution from field to field. This illustration shows the sequence of execution of the events when moving from field to field:Illustration 4
Controlling Default Behavior
The default behavior for events was described earlier in this chapter. The preceding section introduced you to the eventInfo packet. To use and manipulate the default behavior, you must understand the default behavior and the eventInfo packet. You can use the following keywords and variables to control the default behavior: disableDefault, doDefault, enableDefault, and passEvent.
 disableDefault
prevents the built-in behavior from executing. Normally, the default behavior is executed just before endMethod. The exception is if doDefault explicitly executes the default behavior ahead of time. Also, you can prevent the default behavior with disableDefault. If you have disabled the default behavior"”at the beginning of the event, for example"”you can reenable it with enableDefault. passEvent passes an event to the object"™s container.
Redirecting Status Messages
You can use reason() in the status event to trap for a particular category of messages and redirect either with setReason() or statusValue(), as shown in this table:
Constant
Description
StatusWindow
The left largest area on the status bar
ModeWindow1
First small window right of the status area
ModeWindow2
Second small window right of the status area
ModeWindow3
Third small window right of the status area (rightmost window)
Â
To redirect status messages to a field, use the following code in the prefilter of the status event:
1: if eventInfo.reason() = StatusWindow then 2: fldStatus.value = eventInfo.statusValue() 3: endIf
Â
Case Study Trapping for the down arrow in keyPhysical
Trapping for the down arrow in keyPhysical presents some interesting problems when combined with doDefault and disableDefault. For example, suppose that you put the following code on the keyPhysical event of a field named Name. Figure 6-3 shows what happens when the code is executed.
 Figure 3: Demonstration of trapping for keyPhysical
What do you think will happen when the user presses the down arrow while on the Name field? The goal is to jump from the Name field to the Total_Invoice field when the user presses the down arrow key. The code, however, appears to have no effect. What actually happens is the focus does move to the Total_Invoice field, but immediately moves back to the Name field (this time on the next record down). This movement occurs because the default behavior executes after the move.
Now, what do you think will happen when the user presses the down arrow while on the Name field? In this case, the code appears to do the job. The focus is indeed on Total_Invoice. What actually occurs, however, is that the focus moves first to the Name field of the next record and then to the destination; this move is invoked by doDefault.
What if you have code on the arrive of Name? It, of course, would execute.
 Now the code works just the way you want. In this case, you disable the default behavior and in essence pretend that the keystroke never occurred.
Creating and Sending Your Own Events
You have already learned about action() and menuAction() to generate built-in Action and MenuAction constants. Now, you"™re ready to learn about broadcastAction() and postAction(), which work similar to action(). They send Action constants to the action event. broadcastAction() sends an Action constant to the actionevent of an object and all the objects it contains. postAction() sends the Action constant to a queue for delayed execution. postAction() is most useful when working with the newValue event.
In general, avoid placing code in newValue except when you"™re using postAction(). The newValue event is called when the data underneath a field changes, or when you click a list member (such as a radio button or list box). While you are in a newValue event, you are coming out of the heart of a recalc/repaint loop inside the newValue event. Therefore, you cannot modify the value of a bound field. The solution is to use newValue sparingly, and when you do use it, use postAction()"”for example, self.postAction(UserAction + 1)
The line of code above uses the constant UserAction, which offsets the standard action constants with a Corel set integer so that the actions defined by the programmer will never map to an existing Paradox action. This constant is similar to the UserMenu constant.
Using postAction() has the effect of queuing up a user-defined action call to the object receiving the newValue. As soon as the newValue is done executing and the system has completed the refresh loop, the built-in action() fires, and you can trap for this action with the following:
1: If eventInfo.id() = UserAction + 1 then 2: ;Do here what you would have done in newValue. 3: endIf
Â
The end effect is the same, but the code is much safer and easier to debug.
If you have a common set of code (such as a calculation), which you were putting in newValue, then you can execute this calculation from anywhere in your application simply by calling the action() or postAction() method for this object, thus making your code more reusable.
In a newValue event, you can do many tasks, such as writing to unbound fields or changing display properties. Many programmers try to do "database stuff" inside a newValue, and this kind of programming isn"™t good. When in doubt, use postAction() to send a custom action ID to self and trap for the custom action ID in the action event. Your safety will be guaranteed because the action code cannot execute until the recalc/repaint cycle is complete.
Events Can Cause EventsIn an event-driven environment such as Paradox, an event often creates another event before the first event finishes. In effect, you can have several events occurring at the same time. (Some developers have mistakenly called this a secondary event stream. Furthermore, they have mistakenly referred to the first event as the primary event stream. I do not use that terminology here because it just confuses the issue.) Suffice it to say, just as Windows sends messages from application to application, Paradox passes messages from object to object. This message in ObjectPAL is always named eventInfo and has a particular type.
Using the Built-In Error Codes
Rather than always just setting the error code to a nonzero value, try using built-in error constants whenever possible. The following example enforces uniqueness in a table and checks whether the field is required. This example is interesting because it uses the peReqdErr and peKeyViol constants instead of just nonzero values.
1: ;Record :: action 2: var 3: tc TCursor 4: endVar 5: 6: ;check for required field 7: if isBlank(Ship_Via) and self.locked then 8: eventinfo.setErrorCode(peReqdErr) 9: return 10: endIf 11: 12: ;Key violation check 13: tc.attach(ORDERS) 14: if tc.locate("Order_No", Order_No.value) then 15: if tc.recNo()<> self.recNo then 16: eventinfo.setErrorCode(peKeyViol) 17: endIf 18: endIf
Note that the preceding code works for both Paradox and dBASE tables.
This chapter formally introduced you to the event model. The event model is the core piece to understanding how and where to program in ObjectPAL. Events are categorized in three categories: internal, external, and special. Only external events bubble. You also learned that you can control the default behavior with doDefault, disableDefault, enableDefault, and passEvent. You will revisit the event model many times.
You learned that code in internal events is executed by Paradox. Like external events, internal events go first to the form and then to the target object. Unlike external and special events, internal events do not bubble. In general, external events are events generated by the user interacting with a form. In this chapter, you also completed your knowledge of generating your own events by learning about postAction() and broadcastAction() (you previously learned about action() and menuAction()).
Its not bad. I relearned the basics and a few new things. But I think you need to write and in-depth book just on the event model, and possibly the object model Mike? All the properties and events available, including error codes, make the Paradox event model a good introduction to heavy programming, much better than Access.
Thanks Steve for the kind words. Yes, the event model in Paradox is very robust. I do plan on writing another Paradox book this year but it'll be geared to more of an overall usage of Paradox covering working with data, forms, repots, and coding in ObjectPAL.