Kbase P71755: Event Driven GUI Programming vs. Procedure Driven V6 Style
Autor |
  Progress Software Corporation - Progress |
Acesso |
  Público |
Publicação |
  4/14/2004 |
|
Status: Unverified
GOAL:
Event Driven GUI Programming vs. Procedure Driven V6 Style
FIX:
Procedure-driven vs. Event-driven Programming
---------------------------------------------
There are two fundamental differences between a procedure-driven
program and an event-driven one.
(1) In a procedure-driven program, execution starts at the top of
the program and proceeds sequentially. The flow control in all
called procedures is sequential as well. Users are guided
through the program by code that interacts with them in a
predetermined fashion, with each statement leading to the next,
until processing is complete.
In event-driven programs, THE MAIN BLOCK DOES NOT DO ANY
PROCESSING. Instead, the functional parts of the program are
placed in various trigger blocks, which fire based upon events
applied by the user. Any processing to be carried out by the
program does not take place until the user tells it to.
(2) In procedure-driven programs, the _interface_ and the _function_
of the program statements are bound together. In event-driven
programs, the interface and function of the program are
conceived separately.
To illustrate how interface and function can be bound together,
consider the UPDATE statement as it is used below:
UPDATE x WITH FRAME f.
The _interface_ is defined by the fact that UPDATE includes
a "display" and "enable" when executed. Here, the display and
enable are done with frame f. The _function_ is defined by
the fact that UPDATE also includes an "assign" when executed.
The interface and function of this UPDATE cannot be separated
unless different 4GL statements are used instead of UPDATE.
There are three conceptual parts to an event-driven program:
(1) Creation of the user interface by defining and enabling it.
(2) Definition of trigger blocks for program functionality.
(3) Establishment of a wait state.
Here we see a major conceptual difference between event-driven
and procedure-driven programs: the main block of an event-driven
program does nothing more than set up a user interface and
establish a wait state. The "wait state" is simply a mechanism
by which the user interface is retained in front of the user
until some specific, predetermined event (or events) occurs.
These events are typically the selection of a "Quit" button or
the choosing of an "Exit" pull-down menu.
While the wait state is active, the user is allowed to act upon
the widgets in the interface. By acting on the interface, events
are applied which in turn trigger the various trigger blocks.
These trigger blocks constitute the functional part of the program.
Events are captured and fed to the program by use of widgets.
A widget is simply a graphical structure which provides a visual
cue to a user regarding its purpose -- in other words, it
provides the "graphical" part of the "graphical user interface",
or GUI. For example, a button with a label of "Quit" would
cue a user that clicking upon it will dismiss the user interface.
Or, a fill-in with a label of "Customer Name" would tell a
user that its contents are the name of the customer whose
record is currently being acted upon.
The developer of an event-driven GUI program decides which
widgets will be placed on the interface and what their functionality
will be. This stage of design can be both functional and
aesthetic and can vary highly from program to program. (This
is why most GUI environments have "style rules" concerning how
screens should be laid out, how widgets should be labeled, etc.
Without such style rules, users would have to re-adjust for
every application they encounter.)
Properly defining widget functionality requires writing
blocks of trigger code that are based on the types of events that
can be applied to the widget. Since the main block of the program
does nothing more than set up the interface and establish the
wait state, .the actual processing that happens in a program
takes place as a result of events which fire these triggers.
When an event is applied and the corresponding trigger
fires, its block of code executes and actual data interaction takes
place: a record is created, updated, or deleted; a set of records
is browsed; or the result of a calculation is displayed.
In procedure-driven programming, these functions are presented
sequentially as the program executes. Users must interact with
the program in a manner pre-determined by the application
developer, and the state of the program at any given moment
is carefully constructed to prevent a user from diverting
from the established path.
By contrast, in event-driven programs a user can completely
change the state of the application to another state at any
moment. It is not possible for the application programmer
to rely upon any particular mode of interaction occurring.
The Structure of an Event-Driven Program
----------------------------------------
As mentioned above, an event-driven program begins by (1) defining
and enabling the widgets that make up its interface, (2) defining
the behavior (i.e., triggers) that should happen in response to
events applied to the widgets, and (3) establishing a wait state
which allows a user to interact with the interface.
Another term for the wait state is a WAIT-FOR condition.
The WAIT-FOR condition's main purpose is to define the means by
which the entire interface will be dismissed. While a program is
in the WAIT-FOR state, the user applies events to its widgets based
upon their associated functionality. Once the user is finished,
he or she then applies the specific event which will satisfy
the WAIT-FOR and thereby dismiss the interface. This dimissal
event should be readily apparent to the user, by means of an "Exit"
pull-down menu or a "Quit" button.
While a WAIT-FOR is active, a program is said to be "blocking for
user input." Users of GUI programs take for granted that this wait
state applies to the entire interface with which they have been
presented. They expect to be able to select widgets freely, move
between fill-ins freely, and interact freely with any other
enabled widget.
Data Validation in Event-Driven Programs
----------------------------------------
The concept of free interaction in GUI programs runs counter to what
users of procedure-driven programs expect. For example, a user of a
V6 PROGRESS procedure will accept that he or she cannot leave a
field until valid data is entered. Data validation is considered
to be a field-by-field process, done in "real time" while data is
actually being input.
In the event-driven world, however, such an approach proves
frustrating and counter-intuitive. Users of GUI applications
(including those not written in PROGRESS) expect to be able to point-
and-click at will, or to tab freely through an interface without
being forced to comply with any one field's data requirements at
the point of data entry.
For this reason, data validation in event-driven programs is
not enforced on a field-by-field basis, but rather on the basis
of the *entire set* of entered data -- the entire window or dialog
box.
Ultimately, this means conceiving of data validation as something
that is reserved for when the user has signaled, via an event,
that he or she believes that what has been entered on the screen
is satisfactory. Once this "save" event has been detected by the
program, it executes validation code for the complete set of data
and reports any invalid results to the user. (The program can also
automatically place the user into a specific widget containing
the invalid data, as well as change its color, etc., to further
cue the user.)
PROGRESS Version 7 provides schema trigger functionality for this
kind of data validation. In addition, application developers can
implement validation routines directly within the trigger code
that executes i.n response to the "save" event. Many GUI programmers
combine both approaches.
Use of the UPDATE Statement in Event-driven Programs
----------------------------------------------------
One example of a Version 6 programming technique which can lead to
problems in event-driven procedures is the UPDATE statement.
One default feature of the UPDATE statement is that it blocks
for input on each field or variable, in effect behaving as an
"implied" wait-for condition. The problem with this implied
wait-for is that it occurs at the field level rather than at the
interface level, in effect causing a "stacked wait-for" condition.
The UPDATE statement's input-blocking behavior is widely used by
Version 6 programmers to implement data validation at the instant
that data is entered. If a new value does not satisfy the database
or application requirements, a message can be presented to the user
explaining what kind of value should be entered. Program execution
does not continue until this condition is met. Users are required
to remain in a given field or variable until its validation
criteria are met.
While this technique is practical for procedure-driven programming,
the UPDATE statement's blocking properties most often cause
problems in the event-driven world. One reason is the expectation
by users of GUI programs that an application will not restrict
navigation through an interface at the widget level -- validation
should be carried out only when the user has signaled readiness
by applying a pre-defined "Save" event of some kind. Programmers of
event-driven programs who insist on implementing field-level
validation therefore risk violating the accepted style rules of the
native windowing environment. They also risk confusing experienced
users in that environment who are not accustomed to the counter-
intuitive behavior.
A more serious problem can arise when a WAIT-FOR statement is
used in combination with one or more UPDATE statements. The problem
arises due to another property of the UPDATE statement: that once
its last referenced field has been successfully updated by
the user, PROGRESS will automatically dismiss its associated frame
before continuing to the next statement. This results in runtime
errors when the WAIT-FOR statement is reached and PROGRESS detects
that the referenced frame or window no longer exists.
For example, the following code fragment results in a runtime
"WAIT-FOR terminated" error in PROGRESS:
DEF VAR x AS CHAR.
DEF VAR y AS CHAR.
DEF FRAME f x y WITH VIEW-AS DIALOG-BOX.
UPDATE x y WITH FRAME f.
WAIT-FOR "GO" OF FRAME f.
Completion of the UPDATE statement dismisses frame f automatically.
Once this frame is gone, the subsequent WAIT-FOR statement is
unable to reference it. PROGRESS automatically terminates the
WAIT-FOR and presents an error message to the user.
If an UPDATE statement must be used in event-driven programming,
any WAIT-FOR statement that references the same frame as that used
in the UPDATE should be eliminated, allowing the field-level
level blocking properties of the UPDATE to serve as the frame-level
wait-for:
DEF VAR x AS CHAR.
DEF VAR y AS CHAR.
DEF FRAME f x y WITH VIEW-AS DIALOG-BOX.
UPDATE x y WITH FRAME f.
Note that the only difference between this code fragment and the
previous one is that the WAIT-FOR statement has been deleted.
By including VIEW-AS DIALOG-BOX in the DEFINE FRAME statement, the
default "GO" that is applied by PROGRESS upon completion of the
variable's update is also implemented as a "GO" that dismisses the
entire Frame. Since this is accomplished within a single statmenet,
there is no runtime error due to a frame or window disappearing before
another statement references it.
Use of LEAVE Trigger Validation in Event-driven Programs
--------------------------------------------------------
Many programmers attempt to mimic Version 6 field-level va.lidation by
using LEAVE triggers in their GUI programs. This is not advised
for two reasons.
First, as stated above, users of GUI programs are not accustomed to
field-level validation and expect that data will be validated on a
screen-wide basis, when a "Save" event of some kind has been applied.
Incorporating LEAVE trigger validation can confuse users with its
insistence that a user remain in a particular widget until its
data needs are satisfied.
Second, the LEAVE trigger becomes a problem when a user decides that
he or she wishes to cancel data input altogether. For example, a
developer may provide a "Cancel" button on an interface
to serve as a mechanism for canceling. However, in order to click
on the "Cancel" button, the user must be able to leave whichever field
he or she is currently in. The LEAVE trigger prevents this, resulting
in a programming paradox: in order to cancel a transaction, a user
must first enter valid data as though the transaction were to be
completed.
A Sample Event-Driven Program
-----------------------------
Another way to see the difference between procedure-driven and
event-driven programs is to show what the same program would look
like when written using either of the two paradigms. Consider
first this simple procedure-driven 4GL program:
FIND FIRST customer.
UPDATE customer.name customer.phone WITH FRAME a.
Although only two statements long, this program shows all of the
properties of a procedure-driven program. Its main block and
its functional part are one and the same. Also, it is not possible
for a user to deviate from the established functional sequence.
To convert this to an event-driven program requires that more
visualization be given to it, and that the update function be
removed from the main block and placed into a trigger of some
sort. There also needs to be a wait state of some sort.
Visualization of an update is easily done by creating a screen
where there is an "Update" button. However, visualization cannot
end there -- it is also necessary to provide the user with a
visual cue for *not* doing the update. Namely, the interface
also requires a "Cancel" button.
The program's wait state can be provided by using a WAIT-FOR
statement. Following the WAIT-FOR, some means will be used
to delete the user interface.
Given all of this, the next example shows the above program as
it would appear when written with a basic event-driven approach.
The notes that follow afterward describe where each line of code
fits into the previous discussion of what makes up an event-driven
program.
/* Widget Definitions */
DEFINE BUTTON Btn_Cancel AUTO-END-KEY
LABEL "Cancel"
SIZE 9.72 BY 1.08.
DEFINE BUTTON Btn_Upd
LABEL "Update"
SIZE 9.72 BY 1.08.
DEFINE FRAME dialog-1
sports.Customer.Name
sports.Customer.Phone
Btn_Upd
Btn_Cancel
WITH VIEW-AS DIALOG-BOX KEEP-TAB-ORDER
SIDE-LABELS NO-UNDERLINE THREE-D.
/* Trigger Definitions */
ON CHOOSE OF Btn_Upd IN FRAME DIALOG-1 DO:
ASSIGN customer.name customer.phone.
APPLY "GO" TO FRAME dialog-1.
END.
/* Main Block */
ENABLE sports.Customer.Name sports.Customer.Phone Btn_Upd Btn_Cancel
WITH FRAME dialog-1.
FIND FIRST customer.
DISPLAY customer.name customer.phone WITH FRAME dialog-1.
WAIT-FOR GO OF FRAME dialog-1.
HIDE FRAME dialog-1.
The event-driven analysis of this example is as follows:
o The two DEFINE BUTTON statements define two static push-button
widgets, Btn_Ok and Btn_Cancel, and label them as "Update" and
"Cancel". The button labels provide visual cues to the
user as to the function of each button. Because Btn_Cancel is
defined as AUTO-END-KEY, one part of its function is to apply
an END-KEY event automatically whenever it is chosen.
o The DEFINE FRAME statement defines a dialog-box frame
named. dialog-1. Because dialog-1 is a dialog box, when it is
realized in the application it will require that the user finish
interacting with it before allowing program functions onnt is
other frames to become active. (Dialog boxes are recommended
for database interactions such as the update in this example,
since their modal behavior helps to enforce proper PROGRESS
transactions.)
The DEFINE FRAME statement also includes the database fields
Customer.Name and Customer.Phone. In addition, it specifies
that thee fields should also be displayed as widgets, in this
case fill-in's. Other PROGRESS keywords (KEEP-TAB-ORDER,
SIDE-LABELS, NO-UNDERLINE, THREE-D, SCROLLABLE, and TITLE)
define additional characteristics of the dialog box. These
characteristics are both visual and behavioral.
Note that the frame will not be realized until a statement is
executed which displays or enables its widgets.
o The ON statement for Btn_Upd defines trigger code which will
execute each time a CHOOSE event is applied to the button.
(At runtime, the CHOOSE event in the example will be applied by
means of a click on the left mousebutton while the cursor is
positioned over the button.)
Within the CHOOSE trigger block the following occurs: (1) an
ASSIGN, which moves data entered into the fields from the screen
buffer to the corresponding fields in the record buffer, and
(2) an APPLY statement, which applies a GO event to the frame.
Note that this is the precise event that is being blocked for
by the WAIT-FOR statment in the Main Block! This means that
in this example, once the new database values have been assigned,
the program will dismiss the user interface due to the WAIT-FOR
terminating and the subsequent HIDE FRAME executing.
Note that no ON statement has been included for a CHOOSE of
Btn_Cancel. This is because its AUTO-END-KEY properties already
define its CHOOSE behavior. When a user clicks on Btn_Cancel,
an END-KEY event is applied to the interface automatically,
resulting in a back-out of any active transaction and immediate
dismissal of the interface. (Note that in this manner, END-KEY
processing bypasses the WAIT-FOR condition.)
o The Main Block serves to set up the user interface and establish
the WAIT-FOR condition.
Here, setting up the interface consists of five steps: (1) an
ENABLE statement which places the Customer fields on the screen
along with the button widgets defined on the interface (note that
with the execution of this statement, frame dialog-1 is actually
realized on the screen); (2) a FIND statement which fetches the
first Customer record in the database; (3) a DISPLAY statement,
which displays the current contents the two fields from the found
record in their corresponding fill-in widgets; (4) a WAIT-FOR
statement which establishes a wait-for condition for the entire
interface; and (5) a HIDE statement which indicates the desired
behavior once the WAIT-FOR has been satisfied (here, the programmer
wishes merely to dismiss the interface once a GO has been applied.)
Note that processing ends under two conditions: the user clicks on
Btn_Cancel and invokes its AUTO-END-KEY properties, or the user
chooses Btn_Upd, which as part of its trigger code applies the "GO"
event that satisfies the WAIT-FOR. As a result of either of these
events, the interface is dismissed and control returns to the
interface that originally called it, if any.
Also, notice that there is no actual UPDATE statement in the event-
driven example! This is because the use of UPDATE would introduce
another (implied) wait-for ... plus, UPDATE's blocking properties
would prevent users from moving from one field to another as freely
as they need to. The wait-for in this example is based upon a
WAIT-FOR statement that applies to the *entire* interface, not on a
field-by-field basis.
SUMMARY.
-------
In short, the rules of good event-driven GUI programming include:
(1) Move program processing from the main execution part of the
program into trigger blocks.
(2) Do not use 4GL statements that feature implied wait states,
such as UPDATE and PROMPT-FOR, except under very controlled
conditions. Create wait states that apply to the *entire*
interface.
(3) Use the Main Block of the program to define and enable the
widgets in the interface.
(4) Do validation on a screen-wide basis, not field by field.
Place validation code into schema triggers and/or the
trigger blocks of specific widgets such as "Save" buttons.
(5) Create an aesthetically and functionally pleasing interface.
Too many widgets will confuse or overwhelm a user. Choose
widgets carefully when deciding how to present data, since
a bad choice here can also result in confusion.
Progress Software Technical Support Note # 15185.