Progress
Progress
Progress
Programming
Handbook
software products are copyrighted and all rights are reserved by Progress Software Corporation.
This manual is also copyrighted and all rights are reserved. This manual may not, in whole or in part, be
copied, photocopied, translated, or reduced to any electronic medium or machine-readable form without
prior consent, in writing, from Progress Software Corporation.
The information in this manual is subject to change without notice, and Progress Software Corporation
assumes no responsibility for any errors that may appear in this document.
The references in this manual to specific platforms supported are subject to change.
Progress, Progress Results, Provision and WebSpeed are registered trademarks of Progress Software
Corporation in the United States and other countries. Apptivity, AppServer, ProVision Plus, SmartObjects,
IntelliStream, and other Progress product names are trademarks of Progress Software Corporation.
SonicMQ is a trademark of Sonic Software Corporation in the United States and other countries.
Progress Software Corporation acknowledges the use of Raster Imaging Technology copyrighted by
Snowbound Software 1993-1997 and the IBM XML Parser for Java Edition.
IBM Corporation 1998-1999. All rights reserved. U.S. Government Users Restricted Rights Use,
duplication or disclosure restricted by GSA ADP Schedule Contract with IBM Corp.
Progress is a registered trademark of Progress Software Corporation and is used by IBM Corporation in the
mark Progress/400 under license. Progress/400 AND 400
CLEAR
F8
CTRLZ
F8
CTRLZ
Progress Programming Handbook
620
CLOSE F8
ESCZ
F8
CTRLALTZ
F8
COMPILE
SHIFTF2 ESCP CTRLALTP
COPY
F11
ESCC
F11
CTRLALTC
CURSORDOWN
CURSORDOWN CURSORDOWN
CTRLJ
CURSORDOWN
CTRLJ
CURSORLEFT
CURSORLEFT CURSORLEFT
CTRLO
CURSORLEFT
CTRLO
CURSORRIGHT
CURSORRIGHT CURSORRIGHT
CTRLL
CURSORRIGHT
CTRLL
CURSORUP
CURSORUP CURSORUP
CTRLK
CURSORUP
CTRLK
CUT
CTRLX
F10
ESCX
F10
CTRLALTX
DEFAULTPOPUP
SHIFTF10 ESCU SHIFTF4
CTRLALTU
DELETECHARACTER DEL DEL
DELETE
DELETE
DELETECOLUMN
ESCCTRLZ
DELETEENDLINE
ESCK CTRLALTK
DELETEFIELD
ESCCTRLD
DELETELINE
CTRLD CTRLD
DELETEWORD
ESCD CTRLALTD
EDITORBACKTAB
CTRLB CTRLB
Table 64: Progress Key Functions (2 of 6)
Key Function
Key Label
Windows Graphical
Interface
UNIX
Character Interface
Windows Character
Interface
Handling User Input
621
EDITORTAB
CTRLG
TAB
CTRLG
TAB
END END END
ESC.
END
ENDERROR ESC F4
CTRLE
F4
ESC
CTRLE
ENTERMENUBAR ALT F3
PF3
ESCM
F3
ALT
EXIT
ESCQ CTRLALTQ
FIND
CTRLF CTRLF CTRLF
FINDNEXT F9
ESCF CTRLALTF
FINDPREVIOUS
SHIFTF9 ESCI CTRLALTI
GET F3 F5
ESCO
F5
CTRLALTO
GO F2 F1
CTRLX
F1
CTRLX
GOTO
CTRLG ESCG CTRLALTG
HELP F1
ESC?
HOME HOME
ESC,
ESCH
HOME
INSERTCOLUMN
ESCCTRLN
INSERTFIELD
ESCCTRLG
INSERTFIELDDATA
ESCCTRLF
Table 64: Progress Key Functions (3 of 6)
Key Function
Key Label
Windows Graphical
Interface
UNIX
Character Interface
Windows Character
Interface
Progress Programming Handbook
622
INSERTFIELDLABEL
ESCCTRLE
INSERTMODE INSERT F9
CTRLT
INSERT
F9
CTRLT
LEFTEND HOME
ESCCURSORLEFT ALTCURSORLEFT
MAINMENU
ESCRETURN
ESCCTRLM
MOVE
ESCCTRLV
NEW
SHIFTF3 ESCN CTRLALTN
NEWLINE
CTRLN CTRLN
NEXTERROR
ESCE
NEXTFRAME F6
ESCTAB
ESCCTRLI
NEXTWORD
CTRLW
OPENLINEABOVE
ESCL CTRLALTL
OPTIONS
ESCCTRLO
PAGEDOWN
PAGEDOWN
PGDN
NEXTPAGE
NEXTSCRN
ESCCURSOR DOWN PAGEDOWN
PAGELEFT
ESCW
PAGERIGHT
ESCY
Table 64: Progress Key Functions (4 of 6)
Key Function
Key Label
Windows Graphical
Interface
UNIX
Character Interface
Windows Character
Interface
Handling User Input
623
PAGEUP
PAGEUP
PGUP
PREVPAGE
PREVSCRN
ESCCURSORUP PAGEUP
PASTE CTRLV F12
ESCV
F12
CTRLALTV
PICK
ESCCTRLP
PICKAREA
ESCCTRLW
PICKBOTH
ESCCTRLQ
PREVFRAME SHIFTF6 ESCCTRLU CTRLSHIFTTAB
PREVWORD
CTRLP CTRLP
PUT F6 F6
ESCS
F6
RECALL
F7
CTRLR
REPLACE
ESCR
REPORTS
ESCCTRLA
RESUMEDISPLAY
CTRLQ
RETURN ENTER
RETURN
CTRLM
RETURN
CTRLM
ENTER
CTRLM
RIGHTEND
ESCCURSORRIGHT ALTCURSORRIGHT
SAVEAS SHIFTF6 ESCA CTRLALTA
SCROLLLEFT
ESCCTRLL
SCROLLMODE
ESCT CTRLALTT
Table 64: Progress Key Functions (5 of 6)
Key Function
Key Label
Windows Graphical
Interface
UNIX
Character Interface
Windows Character
Interface
Progress Programming Handbook
624
SCROLLRIGHT
ESCCTRLR
SETTINGS
ESCCTRL@
STOP CTRLBREAK CTRLC CTRLC
STOPDISPLAY
CTRLS
TAB TAB TAB
CTRLI
TAB
CTRLI
TOPCOLUMN
ESCCTRLT
UNIXEND
CTRL\
Table 64: Progress Key Functions (6 of 6)
Key Function
Key Label
Windows Graphical
Interface
UNIX
Character Interface
Windows Character
Interface
Handling User Input
625
6.4 Changing the Function of a Key
You can globally change Progress key functions by modifying your environment. For more
information on modifying your environment, see the chapter on colors and fonts in this book
and the chapter on user interface environments in the Progress Client Deployment Guide.
Although Progress has defined functions for several of the keys on your keyboard, you can
redefine those keys to perform other functions within an application. In addition, you can assign
functions to any of the other keyboard keys.
Suppose the user is accustomed to pressing F2 to get help information and F1 to signal that they
are finished entering data. Although Progress usually treats F2 as GO and F1 as HELP, you can
switch the functions of those keys as demonstrated in the following procedure:
In this procedure, the ON statements redefine the function of F1, F2, and CTRLX. Run this
procedure, then press F1. You can see that F1 now performs the GO function, normally
performed by F2. If you press CTRLX, Progress rings the terminal bell. The new key definitions
are in effect for the duration of the session, unless you redefine them. Also, on UNIX, any key
label you use in an ON statement must have an entry in the PROTERMCAP file for your
terminal.
When you use the ON statement, you use the name of the key whose function you are
redefining, followed by the action you want to take when the user presses that key.
p-action.p
ON F1 GO.
ON F2 HELP.
ON CTRL-X BELL.
FOR EACH customer:
DISPLAY cust-num.
UPDATE Name credit-limit sales-rep.
END.
Syntax:
Meaning:
ON Statement
ON
When the user presses... ...this key... ...takes this action.
Keyboard Key Action
GO F1
Progress Programming Handbook
626
The p-action.p procedure uses three actions: GO, HELP, and BELL. There are many other
actions you can use when redefining the function of a key. Table 65 lists these actions.
Table 65: Actions You Assign to Keys
Action Default Key
BACKSPACE
BACKSPACE
BACKTAB
SHIFTTAB, CTRLU, CODETAB
BELL
CLEAR
F8, CTRLZ, CODEZ
CURSORUP
, CTRLK
CURSORDOWN
, CTRLJ
CURSORLEFT
?, CTRLH
CURSORRIGHT
?, CTRLL
DELETECHARACTER
DEL
ENDKEY
ENDERROR F4, CTRLE, CODEE, ESC (Windows)
ERROR
GO
F2, CTRLX, CODEX
HELP
F1, HELP, CTRLW, CODEW
HOME HOME, ESCH (UNIX)
INSERTMODE
F3, INSERT, CTRLT, OVERTYPE
RECALL
F7, CTRLR, CODER
RETURN
RETURN
STOP CTRLBREAK (Windows), CTRLC (UNIX)
TAB
TAB, CTRLI
Handling User Input
627
6.5 Using Mouse Buttons and Events
Progress uses a logical (portable) model to reference mouse button input. This ensures that your
application works in whatever operating environment it runs. Progress also provides access to
the physical mouse input of your operating environment if you need it. Both forms of input are
available as events in a manner similar to keyboard events.
6.5.1 Portable and Physical Buttons
Progress supports four portable mouse buttons:
SELECT You can select or choose an object by pointing to it and clicking this mouse
button. Any previously selected object becomes deselected.
EXTEND You can toggle the selection state of an object by pointing to it and clicking
this button. This has no effect on any other object; therefore, you can use the EXTEND
button to toggle selection individually on more than one object.
MENU If an object has an associated pop-up menu, you can activate that menu by
clicking this button.
MOVE By pressing and holding this button you can drag an object on the screen.
Although Progress supports four buttons, the standard mouse used with Windows has only two
buttons. Therefore, some physical mouse buttons have double functions, or, you must use
control keys with one or more buttons. Table 66 shows the mappings between the Progress
portable mouse buttons and the physical mouse buttons on Windows.
Progress supports two main classes of mouse eventsportable and three-button events. You
can use portable mouse events to associate triggers with logical actions of any mouse. You can
use the three-button mouse events to associate triggers with specific physical actions of a
three-button mouse. The names of the portable mouse events come from the mouse key labels
Table 66: Mouse Buttons on Windows
Portable Button Windows
SELECT LEFT mouse button
EXTEND CTRL with LEFT mouse button
MENU RIGHT mouse button
MOVE LEFT mouse button
Progress Programming Handbook
628
listed in Table 66 (for example, MOUSESELECTCLICK). They also correspond to the
names of the portable mouse buttons used to generate them. The names of the three-button
mouse events correspond to the physical buttons that generate them on a three-button mouse
(for example, LEFTMOUSECLICK). For a complete description of the names and functions
of the portable and three-button mouse events, see the Progress Language Reference.
Both portable and three-button mouse events divide into two subclasseslow-level and
high-level mouse events. Low-level mouse events are generated by the simplest mouse button
actions, while high-level events are generated by more complex actions.
6.5.2 Specifying Mouse Events
When choosing the events to associate with a trigger, use portable mouse events whenever
possible. If you use these events, Progress automatically maps the event to the mouse key the
native window system uses to perform the same event. For example, if Progress supports a
future window system that uses the right button as the selection button, the
MOUSESELECTCLICK event will automatically map to the right button for that system,
while still mapping to the left button for Windows.
Portable and Three-Button Event Priority
If you use three-button mouse events, they take priority over any corresponding portable event.
For example, if you define a MOUSESELECTCLICK and a LEFTMOUSECLICK trigger
for the same widget, Progress only fires the three-button trigger. (In this case, it only fires the
LEFTMOUSECLICK trigger.)
Low-level and High-level Events
The low-level category consists of those events triggered by mouse button motion in a single
direction, such as down or up. The high-level category consists of those events triggered by
more complex mouse button motion, such as click or double-click. For Progress, corresponding
low-level and high-level mouse events are analogous to equivalent key label and key function
events. Like key label and key function events, or three-button and portable mouse events,
low-level mouse events take priority over corresponding high-level mouse events. For example,
if you define a MOUSESELECTUP and MOUSESELECTCLICK trigger on the same
widget, only the MOUSESELECTUP trigger executes.
Like portable events, use high-level mouse events exclusively whenever possible. If you must
use low-level events, do not mix low-level and high-level event triggers on the same widget.
The processing of both classes of events on the same widget can lead to unintended results.
While Progress always recognizes corresponding low-level and high-level events, it executes
only one trigger for both. (The same is true for corresponding key label and key function events,
as well as portable and three-button mouse events). Progress looks first for a trigger on a
Handling User Input
629
low-level event. If there is no trigger on a low-level event, Progress looks for a trigger on a
high-level event. Once it finds and fires a trigger, it waits for the next event. However, each
graphic interface may recognize and pass high-level events to Progress after recognizing a
different combination of low-level events. For example, one system might recognize a
double-click on a down and another system might recognize the double-click on an up mouse
event. This causes mixed event sequences to differ significantly from interface to interface.
Thus, much like using pixels for frame and window layout, the mixing of low- and high-level
mouse events creates portability problems between graphic interfaces. Furthermore, there is no
guarantee that the expected event sequences in the same graphic interface might not change in
future versions of that interface.
6.6 Telling Progress How to Continue Processing
When you modify a field or variable, pressing GO tells Progress to accept the data in all the
modified fields and variables in the current statement and to go on to the next statement in the
procedure.
As the following figure shows, if you press GO while the cursor is in the name, creditlimit, or
salesrep fields, Progress continues on to the next statement in the procedure, which is END.
The procedure then returns to the beginning of the FOR EACH loop.
p-intro.p
Cust num Name Credit-Limit Sales-Rep
1 Lift Line Skiing 66,800 HXM
FOR EACH customer:
DISPLAY cust-num
UPDATE name credit-limit.sales-rep.
END.
GO
Continue on
to the next
Statement (END)
Progress Programming Handbook
630
You might want to define function keys that tell Progress to continue processing data a certain
way. You can use the GOON phrase with the SET or UPDATE statement to do this, as shown
in the following procedure:
In this example, if the user presses F8, F10, or F12 while updating the customer data, the
procedure immediately goes on to the next statement in the procedure. Lets take a closer look
at this procedure.
Any key you can press while running a Progress procedure has a code, a function, and a label
associated with it. The code of a key is an integer value that Progress uses to identify that key.
For example, the code of F1 is 301. The function of a key is the work that Progress does when
you press the key. For example, the function of the F1 key may be HELP. The label of a key is
the actual label that appears on the keyboard key. The label of the F1 key is F1.
As shown earlier, you can use the KEYLABEL, KEYCODE, KEYFUNCTION, and
KBLABEL functions to convert key labels, key codes, and key functions. In addition to these
functions, the LASTKEY function returns the key code of the last key pressed.
p-gon1.p
DISPLAY "You may update each customer." SKIP
"After making your changes, press one of:" SKIP(1)
KBLABEL("GO") + " - Make the change permanent" FORMAT "x(40)"
SKIP
KBLABEL("END-ERROR") + " - Undo changes and exit" FORMAT "x(40)"
SKIP
"F8 - Undo changes and try again" SKIP
"F10 - Find next customer" SKIP
"F12 - Find previous customer"
WITH CENTERED FRAME ins.
FIND FIRST Customer.upd-loop:
REPEAT:
UPDATE Cust-num Name Address Address2 City St
GO-ON(F8 F10 F12) WITH 1 DOWN CENTERED.
CASE LASTKEY:
WHEN KEYCODE("F8") THEN
UNDO upd-loop, RETRY upd-loop.
WHEN KEYCODE("F10") THEN
FIND NEXT Customer.
WHEN KEYCODE("F12") THEN
FIND PREV Customer.
END CASE.
END.
Handling User Input
631
You can use the functions described in this table to monitor the keys being pressed, as in this
example:
Run procedure p-keys.p to see how the different keys you press translate into key codes, key
labels, and key functions.
p-keys.p
REPEAT:
DISPLAY "Press any key".
READKEY.
DISPLAY LASTKEY LABEL "Key Code"
KEYLABEL(LASTKEY) LABEL "Key Label"
KEYFUNCTION(LASTKEY) LABEL "Key Function"
FORMAT "x(12)".
IF KEYFUNCTION(LASTKEY) = "end-error" THEN LEAVE.
END.
Progress Programming Handbook
632
Now, run the p-gon1.p procedure. You see a screen similar to the one shown in the following
figure:
Figure 64: The p-gon1.p Procedure
Do next
statement
Continue on CASE
GO F8 F10 F12
Cust-Name
City State
Address Address 2
1 Lift Line Skiing 276 North Street
Boston
MA
Last key pressed
KEYCODE(F8)
KEYCODE(F10)
KEYCODE(F12)
UNDO,
RETRY
FIND PREV
customer
FIND NEXT
customer
You may update each customer.
After making your changes, press one of:
F2 - Make the change permanent
ESC - Undo changes and exit
F8 - Undo changes and try again
F10 - Find next customer
F12 - Find previous customer
Handling User Input
633
While updating the customer information, press F9, F10, F11, or use either of the standard
techniques to signal the end of data entry, Progress goes on to the next statement in the
procedure. If you press any other key, Progress does not continue on to the next statement in the
procedure, but instead performs the data entry operation associated with that key. If you press
ENDERROR, Progress performs the default ENDKEY processing of UNDO, LEAVE. See
Condition Handling and Messages, for more information on ENDKEY processing.
If Progress does continue on to the next statement in the procedure, the CASE statement
determines the action to take by checking the value of the last key pressed.
Progress Programming Handbook
634
The procedure p-gon2.p shows how you can achieve the same functionality in an event-driven
application:
p-gon2.p (1 of 2)
FORM
Customer.Cust-num Customer.Name Customer.Address
Customer.Address2 Customer.City Customer.State
WITH CENTERED FRAME upd-frame.
DISPLAY "You may update each customer." SKIP
"After making your changes, press one of:" SKIP(1)
KBLABEL("GO") + " - Make the change permanent" FORMAT "x(40)"
SKIP
KBLABEL("END-ERROR") + " - Undo changes and exit" FORMAT "x(40)"
SKIP
"F8 - Undo changes and try again" SKIP
"F10 - Find next customer" SKIP
"F12 - Find previous customer"
WITH CENTERED FRAME ins.
ON F8 ANYWHERE
DO:
DISPLAY Customer.Cust-num Name Address City State
WITH CENTERED FRAME upd-frame.
END.
ON F10 ANYWHERE
DO:
APPLY "GO" TO FRAME upd-frame.
FIND NEXT Customer.
DISPLAY Cust-num Name Address Address2 City State
WITH CENTERED FRAME upd-frame.
END.
ON F12 ANYWHERE
DO:
APPLY "GO" TO FRAME upd-frame.
FIND PREV Customer.
DISPLAY Cust-num Name Address Address2 City State
WITH CENTERED FRAME upd-frame.
END.
Handling User Input
635
Use the ANYWHERE option of the ON statement to set up triggers that execute no matter
which widget has input focus. In p-gon2.p, the ANYWHERE option is used to assign triggers
to function keys.
NOTE: In a larger program, you must be careful of the scope of the ANYWHERE trigger.
6.7 Monitoring Keystrokes During Data Entry
Progress provides two methods to monitor keystrokesediting blocks and user interface
triggers.
6.7.1 Editing Blocks
Prior to Progress Version 7 and user interface triggers, editing blocks were the only method
available to monitor individual keystrokes. An editing block is part of an UPDATE, SET, or
PROMPTFOR statement that allows the programmer to read and process each keystroke
individually. Editing blocks are used for two purposes:
To enable a few special keys in one or more fields while allowing normal data entry.
To disallow normal data entry in one or more fields and instead allow the use of only a few
special keys to change the value.
Each type of editing block can now be replaced with user interface triggers.
6.7.2 User Interface Triggers
Progress allows you to access all keystrokes as events. You can intercept these events using the
ON statement or the trigger phrase of any statement that declares a user interface widget. For
each intercepted event, you can provide a user interface trigger to implement any Progress
action. This action can add to, and often replace, the default action associated with the event.
ON GO OF FRAME upd-frame
ASSIGN FRAME upd-frame Customer.Cust-num Name Address Address2
City State.
ENABLE ALL WITH FRAME upd-frame.
FIND FIRST Customer.
APPLY "F8" TO FRAME upd-frame.
WAIT-FOR END-ERROR OF FRAME upd-frame.
p-gon2.p (2 of 2)
Progress Programming Handbook
636
All Progress key labels and key functions are valid events. You can specify a keyboard event in
the ON statement or trigger phrase by:
Using the key label name, such as f2.
Using the key function name, such as go.
Key label events take priority over corresponding key function events. For example, if you
specify a trigger for the TAB key function event and another trigger on the same widget for the
CTRLI key label event, only the CTRLI trigger executes.
Where a key label event corresponds to a key function, use the key function event whenever
possible. Key functions are more portable. If you must reference key label events, be sure not
to also reference corresponding key function events for the same widget.
6.7.3 Using Editing Blocks and User Interface Triggers
This procedure uses an editing block to enable a special key while allowing normal data entry:
p-kystka.p
DEFINE FRAME cust-frame
Customer.Cust-num SKIP Customer.name SKIP Customer.address SKIP
Customer.address2 SKIP Customer.city Customer.state SKIP
Customer.sales-rep HELP "To see a list of values, press F6."
WITH DOWN SIDE-LABELS.
REPEAT WITH FRAME cust-frame:
PROMPT-FOR Customer.Cust-num.
FIND Customer USING Cust-num.
UPDATE Name Address Address2 City State Customer.Sales-rep
EDITING:
READKEY.
IF FRAME-FIELD = "sales-rep" AND LASTKEY = KEYCODE("F6")
THEN DO:
FOR EACH Salesrep:
DISPLAY Salesrep.Sales-rep
WITH NO-LABELS 9 DOWN COLUMN 60 ROW 5.
END.
END.
ELSE DO:
APPLY LASTKEY.
END.
END.
END.
Handling User Input
637
This procedure uses an EDITING block on the UPDATE statement. This means that the
EDITING block processes every keystroke the user enters while that UPDATE statement is
executing. Within the EDITING block, the READKEY statement reads a keystroke from the
user. The associated key code is automatically stored as LASTKEY. The procedure uses
LASTKEY and the FRAMEFIELD function to determine whether the key is F6, and whether
it was entered from within the Salesrep field. If so, it displays a list of all known sales reps. If
the key is not F6, or the current field is not the Salesrep field, then the APPLY statement is
executed. This causes Progress to handle the keystroke normally.
This procedure uses a user interface trigger in place of the EDITING block to achieve the same
functionality:
Use a trigger in place of the EDITING block to produce simpler, cleaner code. It would be easier
to rewrite this code to be more event driven. First, replace the UPDATE statement with
ENABLE, WAITFOR, and ASSIGN. You then might remove the REPEAT block and define
a button that the user can select to move to the next Customer record. You might also replace
the display of sales reps with a selection list or browse widget.
p-kystkb.p
DEFINE FRAME cust-frame
Customer.Cust-num SKIP Customer.name SKIP Customer.address SKIP
Customer.address2 SKIP Customer.city Customer.state SKIP
Customer.sales-rep HELP "To see a list of values, press F6."
WITH DOWN SIDE-LABELS.
ON F6 OF Customer.Sales-rep
DO:
FOR EACH salesrep:
DISPLAY Salesrep.Sales-rep
WITH NO-LABELS 9 DOWN COLUMN 60 ROW 5.
END.
END.
REPEAT WITH FRAME cust-frame:
PROMPT-FOR Customer.Cust-num.
FIND Customer USING Cust-num.
UPDATE Name Address Address2 City State Sales-rep.
END.
Progress Programming Handbook
638
The following procedure uses an EDITING block to disallow normal data entry on a field and
allow only a special key:
Like p-kystka.p, this procedure uses an EDITING block on the UPDATE statement. In the
EDITING block, it uses READKEY, LASTKEY, and FRAMEFIELD to obtain and analyze a
keystroke. If the keystroke is not in the Salesrep field, it is processed normally. Within the
Salesrep field, only the spacebar is treated specially and only the TAB, BACKTAB, GO, and
ENDERROR key functions are treated normally. If the user types any other key within the
field, the terminal bell sounds. When the user presses the spacebar in the Salesrep field, the
value of that field and the Salesregion field change.
p-kystk.p
DEFINE FRAME cust-frame
Customer.Cust-num SKIP Customer.Name SKIP Customer.Address SKIP
Customer.Address2 SKIP Customer.City Customer.State SKIP
Customer.Sales-rep HELP "Use the space bar to scroll the values."
WITH SIDE-LABELS.
REPEAT WITH FRAME cust-frame:
PROMPT-FOR Customer.Cust-num.
FIND Customer USING Cust-num.
UPDATE Name Address City State Sales-rep
EDITING:
READKEY.
IF FRAME-FIELD <> "Sales-rep"
THEN DO:
APPLY LASTKEY.
END.
ELSE DO:
IF LASTKEY = KEYCODE(" ")
THEN DO:
FIND NEXT Salesrep NO-ERROR.
IF NOT AVAILABLE Salesrep
THEN FIND FIRST Salesrep.
DISPLAY Salesrep.Sales-rep @ Customer.Sales-rep.
END.
ELSE IF LOOKUP(KEYFUNCTION(LASTKEY),
"TAB,BACK-TAB,GO,END-ERROR") > 0
THEN APPLY LASTKEY.
ELSE BELL.
END.
END.
END.
Handling User Input
639
The following procedure uses triggers to accomplish the same thing:
Note the use of RETURN NOAPPLY in both the ANYPRINTABLE and spacebar trigger.
This is equivalent to omitting the APPLY LASTKEY statement in an EDITING block. Thus,
the spacebar trigger brings the next Salesrep into view, without also inserting a space character
in the field.
Note also that in p-kystk2.p the ANYPRINTABLE trigger rejects all keys that enter data
characters other than a space. This works together with the spacebar trigger to allow only the
spacebar and navigation keys (TAB, etc.) in the field.
p-kystk2.p
DEFINE FRAME cust-frame
Customer.Cust-num SKIP Customer.Name SKIP Customer.Address SKIP
Customer.Address2 SKIP Customer.City SKIP Customer.State SKIP
Customer.Sales-rep HELP "Use the space bar to scroll the values"
WITH SIDE-LABELS.
ON " " OF Customer.Sales-rep
DO:
FIND NEXT Salesrep NO-LOCK NO-ERROR.
IF NOT AVAILABLE Salesrep
THEN FIND FIRST Salesrep NO-LOCK.
DISPLAY Salesrep.Sales-rep @ Customer.Sales-rep
WITH FRAME cust-frame.
RETURN NO-APPLY.
END.
ON ANY-PRINTABLE OF Customer.Sales-rep
DO:
BELL.
RETURN NO-APPLY.
END.
REPEAT WITH FRAME cust-frame:
PROMPT-FOR Customer.Cust-num.
FIND Customer USING Cust-num.
FIND Salesrep OF Customer NO-LOCK.
UPDATE Name Address Address2 City State Customer.Sales-rep.
END.
Progress Programming Handbook
640
Sometimes an EDITING block defines a special key that applies for all active fields:
In p-kystk3.p, the EDITING block defines a special key, F6, that is available in all fields of the
UPDATE statement. When the user presses this key, the previous Customer record displays.
p-kystk3.p
DEFINE FRAME cust-frame
Customer.Cust-num SKIP Customer.Name SKIP Customer.Address SKIP
Customer.Address2 SKIP Customer.City Customer.State SKIP
Customer.Sales-rep WITH SIDE-LABELS.
REPEAT WITH FRAME cust-frame:
PROMPT-FOR Customer.Cust-num.
FIND Customer USING Cust-num.
MESSAGE "Press F6 to see the previous Customer.".
UPDATE Name Address Address2 City State Sales-rep
EDITING:
READKEY.
IF KEYLABEL(LASTKEY) = "F6"
THEN DO:
FIND PREV Customer NO-ERROR.
IF NOT AVAILABLE Customer
THEN FIND FIRST Customer.
DISPLAY Cust-num Name Address City State Sales-rep
WITH FRAME cust-frame.
END.
ELSE APPLY LASTKEY.
END.
HIDE MESSAGE.
END.
Handling User Input
641
The following procedure uses a trigger to achieve the same result:
If you define a trigger to be active ANYWHERE, then it applies to all widgets. In p-kystk4.p,
the F6 trigger executes whenever the user presses F6 while input is enabled. Within the trigger,
the FRAMEFIELD function determines whether the t rigger executes from the UPDATE or
PROMPTFOR statement. If it is the PROMPTFOR statement, then F6 is ignored; if it is the
UPDATE statement, the previous Customer record displays.
NOTE: In a larger program, be careful of the scope of the ANYWHERE trigger. You are
usually better off listing the specific widgets to which a trigger applies. For example,
you could rewrite the ON statement in p-kystrk4.p as follows:
If you take this approach, you can remove the code that checks whether FRAMEFIELD
is Custnum within the trigger.
p-kystk4.p
DEFINE FRAME cust-frame
Customer.Cust-num SKIP Customer.Name SKIP Customer.Address SKIP
Customer.Address2 SKIP Customer.City Customer.State SKIP
Customer.Sales-rep WITH SIDE-LABELS.
ON F6 ANYWHERE
DO:
IF FRAME-FIELD = "Cust-num"
THEN RETURN NO-APPLY.
FIND PREV Customer NO-ERROR.
IF NOT AVAILABLE Customer
THEN FIND FIRST Customer.
DISPLAY Cust-num Name Address City State Sales-rep
WITH FRAME cust-frame.
END.
REPEAT WITH FRAME cust-frame:
PROMPT-FOR Customer.Cust-num.
FIND Customer USING Cust-num.
MESSAGE "Press F6 to see the previous Customer.".
UPDATE Name Address Address2 City State Sales-rep.
HIDE MESSAGE.
END.
ON F6 OF Name, Address, Address2, City, State, Sales-rep
Progress Programming Handbook
642
7
Alternate I/O Sources
Most of the procedures you have seen so far have used the terminal as the input source (the user
types in data) and as the output destination (data is displayed to the user).
However, youve probably already thought of situations in which you might want to get data
from and send data to locations other than the terminal. For example, you might want to send
reports to your printer or an operating system file.
This chapter describes input/output and then covers the following topics:
Changing the output destination and input source
Defining additional input/output streams and sharing streams
Importing data from a process and piping data to a processes
Performing code page (character set) conversions
Reading the contents of a directory
Converting non-standard input files
Terminal
Get Input
Send Output
Procedure
Progress Programming Handbook
72
7.1 Understanding Input and Output
When a Progress procedure gets input from the terminal, it uses an input stream. Similarly,
when the procedure sends output to the terminal, it uses an output stream.
Every procedure automatically gets one input stream and one output stream. These are called
the unnamed streams. By default, Progress assigns both of these unnamed streams to the
terminal, as shown in Figure 71.
p-io.p
Figure 71: The Unnamed Streams
This procedure contains no special syntax about where to get and send data so Progress
automatically assigns the input stream and the output stream to the terminal.
7.2 Changing the Output Destination
You use the OUTPUT TO statement to name a new destination for output. All statements that
output data (such as DISPLAY or SET) use the new output destination. The possible
destinations include:
The printer
An operating system file or device
The terminal (by default)
The system clipboard (Windows only)
Terminal
Output
Stream
Input
Stream
PROMPT-FOR customer.cust-num.
FIND customer USING cust-num.
DISPLAY name state credit-limit.
END.
REPEAT:
Alternate I/O Sources
73
7.2.1 Output to Printers
Figure 72 shows the output stream being redirected to a printer using the PRINTER option of
the OUTPUT TO statement.
p-iop2.p
Figure 72: Redirecting the Unnamed Output Stream
If you run the procedure, you do not see anything displayed on your terminal because all the
output from the DISPLAY statement goes to the printer. The STREAMIO Frame phrase
option formats the output especially for character-based destinations. See the section Stream
I/O vs. Screen I/O for more information.
Output
Stream
Terminal
OUTPUT TO PRINTER.
FOR EACH customer:
DISPLAY name address city state postal-code
credit-limit WITH STREAM-IO.
END.
Customer
Report
Progress Programming Handbook
74
7.2.2 Additional Options for Printing on Windows
On Windows, you can also get a list of printers, change the default printer for the session, and
provide access to the Print dialog box.
Getting a List of Printers
You can use the GET-PRINTERS option of the SESSION statement to obtain a list of the
printers configured for the current system. The following example shows you how to obtain the
list of currently configured printers, select a printer, and print to it:
The following list explains the important elements in the example above:
1. Create two variables: one for the output of SESSION:GETPRINTERS; the other, for the
output of LOOKUP.
2. The SESSION: GETPRINTERS method returns a comma-separated list of printers that
are currently configured on the system.
3. The LOOKUP function obtains an integer that gives the position of SpecialPrinter in the
list. If the printer is not in the list, it returns a 0.
4. The IF statement determines whether the printer SpecialPrinter is available and if so,
prints to it.
p-wgetls.p
/*1*/ DEFINE VARIABLE printer-list AS CHARACTER.
DEFINE VARIABLE printer-idx AS INTEGER.
/*2*/ printer-list = SESSION:GET-PRINTERS().
/*3*/ printer-idx = LOOKUP("SpecialPrinter", printer-list).
/*4*/ IF printer-idx > 0 THEN DO:
MESSAGE "output" VIEW-AS ALERT-BOX.
OUTPUT TO PRINTER "SpecialPrinter".
END.
/*5*/ ELSE DO:
OUTPUT TO PRINTER.
END.
FOR EACH customer WHERE country = "Finland" :
DISPLAY NAME country.
END.
/*6*/ OUTPUT CLOSE.
Alternate I/O Sources
75
5. If the printer SpecialPrinter is not available, the ELSE DO statement prints to the default
printer.
6. The OUTPUT CLOSE statement stops sending output to the current destination and
redirects output to the destination used prior to OUTPUT TO. See the section Stream I/O
vs. Screen I/O for more information.
Changing the Default Printer
To change the default printer for the session, you can use the PRINTERNAME option of the
SESSION statement as shown in the following example.
1. Create a variable for the output of SESSION:PRINTERNAME.
2. The SESSION:PRINTERNAME attribute returns the name of the default printer.
3. The SESSION:PRINTERNAME attribute sets another printer, \\AB1\hplaser, as the
default printer.
4. The OUTPUT TO PRINTER prints the report on the printer \\AB1\hplaser.
5. The SESSION:PRINTERNAME attribute restores the original default printer.
Providing Access to the Print Dialog Box
You can use the SYSTEMDIALOG PRINTERSETUP statement to provide access to the
Windows print dialog box. This allows the application user to set up the printer or even change
the printer that receives the output.
p-wchpr.p
/*1*/ DEFINE VARIABLE printername AS CHARACTER.
/*2*/ printername = SESSION:PRINTER-NAME
/*3*/ SESSION:PRINTER-NAME = "\\AB1\hplaser".
/*4*/ OUTPUT TO PRINTER.
FOR EACH customer:
DISPLAY name SPACE balance.
END.
/*5*/ SESSION:PRINTER-NAME = printername.
Progress Programming Handbook
76
7.2.3 Output to Files or Devices
You can direct the report to a standard text file. Replace the PRINTER option of the OUTPUT
TO statement in Figure 72 as follows:
In this OUTPUT TO statement, rpt-out is the name of the file where you direct the output. ON
UNIX, the filename is case sensitive. That is, UNIX treats rpt-out and RPT-OUT as two different
files. However, to most other operating systems, these are the same file.
The PAGED option indicates that you want a page break in the output every 56 lines. PAGED
is automatic for output to a printer. If you do not use the PAGED option, Progress sends the data
to the file continuously without any page break control characters.
You can also use the VALUE option to specify filenames stored in variables or fields. For
information, see the Sending Output to Multiple Destinations section.
Some operating systems (like UNIX) allow you to send output to devices other than printers,
terminals, and disk files using a name (like a filename) to reference them. You redirect output
to these devices exactly like files.
7.2.4 Output to the Clipboard
In general, you use the CLIPBOARD system handle to write output to the system clipboard (in
graphical environments) or Progress clipboard (in character environments). However, on
Windows, you can use the OUTPUT TO statement to redirect output to the system clipboard
using the CLIPBOARD option (quotes required). This allows you to use Progress output
statements (such as DISPLAY) to buffer up to 64K of data to the clipboard. You send the
buffered data to the clipboard by changing or closing the output destination (using the OUTPUT
CLOSE statement). For more information on changing and closing the output destination, see
the Sending Output to Multiple Destinations section. For more information on using the
CLIPBOARD system handle for output (and also input), see the Progress External Program
Interfaces manual.
7.2.5 Resetting Output to the Terminal
You can reset any output destination to the terminal using the TERMINAL option of the
OUTPUT TO statement. For more information, see the Sending Output to Multiple
Destinations section.
OUTPUT TO rpt-out PAGED.
Alternate I/O Sources
77
7.2.6 Sending Output to Multiple Destinations
At some points in a procedure, you might want to send output to the terminal, but at other points
you might want to send output to a file. Progress does not restrict you to one output destination
per procedure.
p-chgot2.p
Figure 73: Multiple Output Destinations
In the procedure in Figure 73, you supply the name of the file where you want to send a
customer report and then, if that file does not already exist, send the report to that file.
Output
Destinations
Terminal
Operating
System File
DEFINE VARIABLE outfile AS CHARACTER FORMAT "x(8)"
LABEL "Output file name".
getfile:
DO ON ERROR UNDO, RETRY:
SET outfile WITH SIDE-LABELS.
IF SEARCH(outfile) = outfile THEN DO:
MESSAGE "A file named" outfile "already exists".
MESSAGE "Please use another name".
BELL.
UNDO getfile, RETRY getfile.
END.
END.
OUTPUT TO VALUE(outfile).
FOR EACH customer:
DISPLAY name credit-limit
WITH NO-BOX NO-LABELS STREAM-IO.
END.
Progress Programming Handbook
78
Here are the specific steps the procedure takes:
1. The SET statement prompts you for the filename.
2. The SEARCH function searches for the file, returning the filename if the file is found.
3. If the file is found, the procedure:
Displays a message telling you the file already exists and to use another filename.
Rings the terminal bell.
Undoes the work done in the DO block and retries the block, giving you the
opportunity to supply a different filename.
4. If the file is not found, the procedure uses this statement:
This statement redirects the output to the file you specified. You must use the VALUE
keyword with the OUTPUT TO statement. The VALUE option tells Progress to use the
value of the outfile variable rather than the name outfile itself. If instead you say
OUTPUT TO outfile, Progress assumes that outfile is the name of the text file to which
you want to send output.
You use the OUTPUT CLOSE statement to stop sending output to a destination. Output sent
after the OUTPUT CLOSE statement goes to the destination used prior to the OUTPUT TO
statement.
OUTPUT TO VALUE(outfile).
Alternate I/O Sources
79
For example, the procedure in Figure 74 uses the OUTPUT CLOSE statement to reset the
output destination from a file to the terminal.
p-out.p
Figure 74: The OUTPUT CLOSE Statement
This procedure sends customer information to a file called cust.dat. Then the procedure
displays the word Finished on your terminal screen. The specific steps in the procedure are as
follows:
1. The OUTPUT TO statement redirects output so all statements that normally send output
to the terminal send output to the cust.dat file.
2. The FOR EACH customer and DISPLAY statements produce a report listing each
customers name, address, city, and state. The procedure sends the report to the cust.dat
file.
3. The OUTPUT CLOSE statement resets the output destination for the procedure from the
cust.dat file to the terminal.
4. The last DISPLAY statement displays the message Finished on the terminal screen.
If the output destination prior to execution of p-out.p was the printer (or any destination other
than the terminal), you must explicitly set the output destination to the terminal between Steps
Terminal
Operating
System File
(cust.dat)
Output
Destinations
p-out.p
OUTPUT TO cust.dat.
FOR EACH customer:
DISPLAY cust-num name address address2 city state
SKIP(2) WITH 1 COLUMN SIDE-LABELS STREAM-IO.
END.
OUTPUT CLOSE.
DISPLAY "Finished".
Progress Programming Handbook
710
3 and 4 using the OUTPUT TO statement with the TERMINAL option. Otherwise the OUTPUT
CLOSE statement resets the output destination to the printer (or other prior destination).
7.2.7 Stream I/O vs. Screen I/O
When you compile a procedure, Progress automatically lays out the frames as appropriate for
the current screen display. The layout differs across user interfaces because some fields have
special decoration. The font also affects the size, and therefore the layout, of fields.
However, when you are displaying a frame to a file or printer rather than the screen, you do not
want Progress to design the frame for screen display. You want Progress to lay out the field for
a character display using a fixed font and fields without decorations. Otherwise, output written
to a printer or operating system file may be unattractive or even partially unreadable. You also
want Progress to represent all data fields as text, not as graphical widgets such as editors or
sliders.
Progress provides two mechanisms to deal with these issues:
The STREAMIO option of the COMPILE statement
The STREAMIO and SCREENIO options of the Frame phrase
If all output from a procedure is to printers or operating system files, you can use the
STREAMIO option of the COMPILE statement. This forces Progress to lay out all frames
using a standard fixed font and to display all data fields as simple text fields. The following
widget types are converted to text:
Editors
Selection lists
Sliders
Radio sets
Toggle boxes
If you only write a few frames to a printer or file in your procedure, you can use the
STREAMIO frame option to mark those frames. This forces Progress to lay out only those
specific frames for output to a printer or file. If you do not specify STREAMIO in the
COMPILE statement, then all other frames are designed for screen display.
If only a few screens in your procedure are displayed to a screen, you use the SCREENIO
frame option to mark those frames. Then compile with STREAMIO so that all other frames
are laid out for display to a printer or file.
Alternate I/O Sources
711
7.2.8 A Printing Solution
The Progress ADE toolset provides a portable solution for printing text files. The solution is a
procedure called _osprint.p and it is located in the adecomm directory in the Progress product
directory (DLC). You can also access the source code for this procedure in the src/adecomm
directory located in the Progress product directory.
The _osprint.p procedure sends a specified text file to the default printer as paged output.
Input parameters for the procedure allow you to specify values that configure a print job. On
Windows, you can also direct the _osprint.p procedure to display the Print dialog box and
print the text in a specified font. Use the following syntax to call the _osprint.p procedure from
a Progress procedure:
The parameters of the _osprint.p procedure are as follows:
INPUT parentWindow
A window handle identifying the parent window for Print dialog box and any print status
messages on Windows. The procedure ignores a value specified for this parameter in
character interfaces. If you specify the unknown value (?) or an invalid handle on
Windows, the procedure uses the CURRENTWINDOW handle.
INPUT printFile
A string value representing the name of a text file to print. You can specify an absolute or
relative path for the file. The _osprint.p procedure uses the PROPATH to locate the file.
SYNTAX
RUN adecomm/_osprint.p
( INPUT parentWindow , INPUT printFile ,
INPUT fontNumber , INPUT PrintFlags . INPUT pageSize ,
INPUT pageCount , OUTPUT result
).
Progress Programming Handbook
712
INPUT fontNumber
An integer value representing an entry in the font table maintained by the FONTTABLE
handle. The _osprint.p procedure uses the specified font to print the text file on
Windows. The procedure ignores a value specified for this parameter in character
interfaces. If you specify the unknown value (?) or an integer value that does not exist in
the font table for Windows, the procedure uses the default system font to print the text file.
INPUT PrintFlags
An integer value that determines which printing options are used for a print job on
Windows (only). You can use the values in Table 71. If you need to use more than one
option, add the values of the options together. In all cases, the _osprint.p procedure sets
the value of the PRINTERCONTROLHANDLE attribute of the SESSION handle to
zero (0).
Table 71: Printing Options for Windows
Printing Option Value Selection
Context 0 Default print context
1 Print dialog box. This allows the user to establish a
print context.
Landscape orientation 2 Landscape orientation
Paper Size 4 Letter
8 Legal
12 A4
Paper Tray 32 Upper tray
64 Middle tray
96 Lower tray
128 Manual
160 Auto
Alternate I/O Sources
713
INPUT pageSize
An integer value representing the number of lines per page. If you specify zero (0) for this
parameter, the printer determines the page size. Windows ignores this parameter and
calculates the page size based on the Paper Size setting in the Print Setup dialog box and
the font specified with the fontNumber parameter.
NOTE: The maximum number of character per line is 255.
INPUT pageCount
An integer value that determines if _osprint.p prints the entire text file or a range of
pages from the text file on Windows. The procedure ignores a value specified for this
parameter in character interfaces. If the value of this parameter is not zero (0) on Windows,
Progress uses the page range specified for the current print context.
OUTPUT result
A logical value that reports the success or failure of the print job.
To call the _osprint.p procedure from a Progress procedure, you must define a variable for the
result output parameter. Here is an example:
_osprint.p
/*1*/ DEFINE VARIABLE result AS LOGICAL NO-UNDO.
/*2*/ OUTPUT TO tmp.dat.
FOR EACH customer:
DISPLAY Cust-num Name Phone WITH STREAM-IO.
END.
OUTPUT CLOSE.
/*3*/ RUN adecomm/_osprint.p (INPUT ?, INPUT "tmp.dat", INPUT ?, INPUT 1,
INPUT 0, INPUT 0, OUTPUT result).
/*4*/ OS-DELETE "tmp.p".
Progress Programming Handbook
714
The following list describes the important elements of the previous example:
1. Create a variable for the OUTPUT parameter of the _osprint.p procedure.
2. Generate the temporary text file to be printed. Remember to close the output stream after
generating the text file.
3. Run the _osprint.p procedure to print the generated text file.
4. Delete the temporary text file.
For more information on the language elements referenced in this section, see the Progress
Language Reference.
7.3 Changing a Procedures Input Source
You use the INPUT FROM statement to name a new source for input. The possible sources
include:
An operating system file or device
The contents of an operating system directory
The terminal (by default)
All statements (such as UPDATE or SET) that require data use the new input source.
7.3.1 Input From Files
A data file (not a database table) that contains information on new customers might look like
the following example:
This file is in standard Progress format. That is, blanks separate the field values. Field values
that contain embedded blanks are surrounded by quotation marks ( ). Later sections in this
chapter discuss using alternate formats.
You can write a Progress procedure and tell that procedure to get its input from the p-datf1.d
file. See Figure 75.
p-datf1.d
90 "Wind Chill Hockey" BBB
91 "Low Key Checkers" DKP
92 "Bings Ping Pong" SLS
Alternate I/O Sources
715
p-chgin.p
Figure 75: Redirecting the Unnamed Input Stream
The SET statement, which normally gets its input from the terminal, gets its input from the
p-datf1.d file. The custnum field uses the first data item, 90. The name field uses the next
quoted data item, Wind Chill Hockey, etc. Each time Progress processes a data entry
statement, it reads one line from the file.
7.3.2 Input from Devices, Directories, and the Terminal
Some operating systems (like UNIX) let you receive input from devices other than terminals
and disk files and let you use a name (like a filename) to reference them. You redirect input from
these devices exactly like files. For more information, see the Input From Files section.
You can read the contents of a directory using the OSDIR option of the INPUT FROM
statement. For more information on reading the contents of a directory, see the Reading the
Contents of a Directory section.
You can reset any input source to the terminal using the TERMINAL option of the INPUT
FROM statement. For more information, see the Receiving Input from Multiple Sources
section.
Terminal
Operating
System File
p-chgin.p
INPUT FROM p-datf1.d.
REPEAT:
CREATE customer.
SET cust-num name sales-rep.
END.
Input
Stream
Progress Programming Handbook
716
7.3.3 Receiving Input from Multiple Sources
At some points in a procedure you might want to get input from the terminal, but at other points
you might want to get input from a file. A single procedure can use multiple input sources.
For example, suppose you want to create records for the customers in the p-datf1.d data file.
Before creating the records, you probably want to display the customer numbers in the file and
ask if the user wants to create customer records for those numbers. To do this, you need input
from the terminal. If the user wants to create customer records for the customers in the
p-datf1.d file, you also need input from the file.
The p-chgin2.p procedure uses multiple input sources to perform the work described above.
Because p-chgin2.p uses the same data file (p-datf1.d) you used in the previous section to
create customer records, you must delete customers 90, 91, and 92 from your database before
you run p-chgin2.p. Use the following procedure to delete the customers:
p-io3.p
FOR EACH customer WHERE cust-num > 89:
DELETE customer.
END.
Alternate I/O Sources
717
Figure 76 shows the p-chgin2.p procedure.
p-chgin2.p
Figure 76: Multiple Input Sources
p-chgin2.p
DEFINE VARIABLE cust-num-var LIKE customer.cust-num.
DEFINE VARIABLE name-var LIKE customer.name.
DEFINE VARIABLE sales-rep-var LIKE customer.sales-rep.
DEFINE VARIABLE answer AS LOGICAL.
DISPLAY "The customers in the data file are: " WITH NO-BOX.
INPUT FROM p-datf1.d.
REPEAT WITH 10 DOWN COLUMN 38:
SET cust-num-var name-var sales-rep-var WITH NO-BOX.
END.
INPUT FROM TERMINAL.
SET answer LABEL
"Do you want to create database records for these
customers?"
WITH SIDE-LABELS NO-BOX FRAME ans-frame.
IF answer
THEN DO:
DISPLAY "Creating records for..." WITH FRAME ans-frame.
INPUT FROM p-datf1.d.
REPEAT:
CREATE customer.
SET cust-num name sales-rep WITH NO-BOX COLUMN 28.
END.
END.
Terminal
Operating
System File
Input
Sources
Progress Programming Handbook
718
These are the specific steps the procedure follows:
1. The DISPLAY statement displays some text.
2. The first INPUT FROM statement redirects the input source to the p-datf1.d file.
3. The SET statement assigns the values in the p-datf1.d file to the custnumvar,
namevar, and salesrepvar variables. As it assigns the values to these variables, you see
the values on the terminal screen.
4. The INPUT FROM TERMINAL statement redirects the input source to the terminal. The
INPUT CLOSE statement could have been used instead of the INPUT FROM
TERMINAL statement. However, since this procedure might have been called from
another procedure, it is better to be explicit about the input source you want to use.
5. The SET answer statement prompts you to create database records for the customer data
just displayed. If you answer yes, the procedure:
Redirects the input source to come from the beginning of the p-datf1.d file.
Creates a customer record and assigns values to the custnum., name, and salesrep
fields in that record for each iteration of a REPEAT block.
Ends the REPEAT block when the SET statement reaches the end of the input file.
7.3.4 Reading Input with Control Characters
You can use the BINARY option of the INPUT FROM statement to read control characters.
Input sources often contain control characters that affect the format or quantity of data that you
receive. For example, a text file might contain NUL (\0) characters to terminate character
strings. Without the BINARY option, Progress ignores all input on a line after the first NUL
character. The BINARY option allows you to read all the data in the file, including any NUL
and other non-printable control characters without interpretation.
7.4 Defining Additional Input/Output Streams
When you start a procedure, Progress automatically provides that procedure with input and
output streams. As described in the previous sections, the default source for the input stream is
the terminal and the default destination for the output stream is also the terminal. You saw how
to use the INPUT FROM and OUTPUT TO statements to redirect these input and output
streams.
Alternate I/O Sources
719
You might find that having just one input stream and one output stream is not enough for
particular procedures. That is, you might want to get input from more than one source at the
same time or send output to more than one destination at the same time.
Suppose you want to produce a report of the items you have in inventory and you want to send
the report to a file. You already know how to use the OUTPUT TO statement to redirect the
output stream to a file. Suppose that you also want to produce an exceptions report at the same
time. Any item where the allocated amount is greater than the on-hand amount is an exception.
Figure 77 illustrates this scenario.
Figure 77: Multiple Output Streams Scenario
For items that are exceptions, the procedure needs to send output to a second location. That
means you need two different output streams.
You use the DEFINE STREAM statement to define additional streams for a procedure to get
input from more than one source simultaneously and send output to more than one destination
simultaneously. Streams you name can be operating system files, printers, the terminal, or other
non-terminal devices.
Read each record in
the item file.
Write some data from
the item record to the
file that contains the
Item inventory report.
If the allocated amount
of the item is greater
than the amount on
hand, write some data
to the file that contains
the item exception
report.
Item Inventory
Report
Item Exception
Report
Operating
System File
Operating
System File
1.
2.
3.
Progress Programming Handbook
720
The procedure p-dfstr.p uses the two report scenarios shown in Figure 77.
p-dfstr.p
DEFINE STREAM rpt.
DEFINE STREAM exceptions.
DEFINE VARIABLE fnr AS CHARACTER FORMAT "x(12)".
DEFINE VARIABLE fne AS CHARACTER FORMAT "x(12)".
DEFINE VARIABLE excount AS INTEGER LABEL "Total Number of
exceptions".
DEFINE VARIABLE exception AS LOGICAL.
/*1*/ SET fnr LABEL "Enter filename for report output" SKIP(1)
fne LABEL "Enter filename for exception output"
WITH SIDE-LABELS FRAME fnames.
/*2*/ OUTPUT STREAM rpt TO VALUE(fnr) PAGED.
OUTPUT STREAM exceptions TO VALUE(fne) PAGED.
/*3*/ DISPLAY STREAM rpt "Item Inventory Report" SKIP(2)
WITH CENTERED NO-BOX FRAME rpt-frame STREAM-IO.
/*4*/ DISPLAY STREAM exceptions "Item Exception Report" SKIP(2)
WITH CENTERED NO-BOX FRAME except-frame STREAM-IO.
/*5*/ FOR EACH item:
/*6*/ IF on-hand < alloc THEN DO:
DISPLAY STREAM exceptions item-num item-name on-hand alloc
WITH FRAME exitem DOWN STREAM-IO.
excount = excount + 1.
exception = TRUE.
END.
/*7*/ DISPLAY STREAM rpt item-num item-name
WITH NO-LABELS NO-BOX STREAM-IO.
/*8*/ IF exception
THEN DISPLAY STREAM rpt "See Exception Report".
exception = FALSE.
END.
/*9*/ DISPLAY STREAM exceptions SKIP(1) excount
WITH FRAME exc SIDE-LABELS STREAM-IO.
/*10*/DISPLAY STREAM rpt WITH FRAME exc STREAM-IO.
/*11*/OUTPUT STREAM rpt CLOSE.
OUTPUT STREAM exceptions CLOSE.
Alternate I/O Sources
721
The numbers to the left of the procedure correspond to the following step-by-step descriptions:
1. The SET statement prompts you for the filenames you want to use for the Item Inventory
Report and for the Item Exception Report. It stores your answers in the fnr and fne
variables, respectively.
2. The OUTPUT STREAM statements open two output streams, named rpt and exceptions.
These streams were defined at the start of the procedure with the DEFINE STREAM
statement.
The rpt and exceptions streams are directed to the files whose names you supplied:
VALUE(fnr) and VALUE(fne). This means that output can now be sent to either or both
of those files.
3. The DISPLAY statement displays the text Item Inventory Report. But instead of
displaying that text on the terminal, it displays it to the rpt stream. The file you named for
the Item Inventory Report contains the text Item Inventory Report.
4. This DISPLAY statement also displays text but it uses the exceptions stream. The file you
named for the Item Exception Report contains the text Item Exception Report.
5. The FOR EACH block reads a single item record on each iteration of the block.
6. If the allocated amount of an item is larger than the on-hand amount of that item:
The DISPLAY statement displays item data to the exceptions stream. After this
DISPLAY statement finishes, the file you named for the Item Exception Report
contains item data for a single item.
The excount counter variable, defined at the start of the procedure, is incremented
by 1. The value of this variable is displayed at the end of the procedure so that you
know the total number of exception items in inventory.
The exception logical variable, defined at the start of the procedure, is set to TRUE.
7. The DISPLAY statement displays some item data to the rpt stream. After this statement
finishes, the file you named for the Item Inventory Report contains item data for a single
item.
8. If the item is an exception, determined by the value in the exception logical variable, the
DISPLAY statement displays the string See Exception Report to the rpt stream. That
way you know, when looking at the Item Inventory Report, which items are exceptions.
9. The DISPLAY statement displays the value of the excount variable to the exceptions
stream. The value of this variable is the total number of exception items.
Progress Programming Handbook
722
10. This DISPLAY statement displays the value of the excount variable to the rpt stream.
Although the DISPLAY statement does not explicitly say what is being displayed, it does
name the same frame, exc, as is used to display excount in the previous DISPLAY
statement. That means that the exc frame already contains the excount value. Thus, all this
second DISPLAY statement has to do is name the same frame.
11. The OUTPUT STREAM CLOSE statements close the rpt and exception streams,
redirecting all further output to the default output destination.
7.5 Sharing Streams Among Procedures
In some cases, you might want two or more procedures to share the same input or output
streams. The following procedures, p-sstrm.p and p-dispho.p, share the same output stream,
phonelist. Notice that phonelist is defined as a shared stream in both procedures:
The p-sstrm.p procedure defines a NEW SHARED STREAM called phonelist. The procedure
sends the output from the phonelist stream to a file called phonefile. The procedure also calls
the p-dispho.p procedure:
p-sstrm.p
DEFINE NEW SHARED BUFFER xrep FOR salesrep.
DEFINE NEW SHARED STREAM phonelist.
OUTPUT STREAM phonelist TO phonefile.
PAUSE 2 BEFORE-HIDE.
FOR EACH xrep:
DISPLAY xrep WITH FRAME repname
TITLE "Creating report for " 2 COLUMNS CENTERED ROW 10.
DISPLAY STREAM phonelist xrep WITH 2 COLUMNS STREAM-IO.
RUN p-dispho.p.
END.
p-dispho.p
DEFINE SHARED BUFFER xrep FOR salesrep.
DEFINE SHARED STREAM phonelist.
FOR EACH customer OF xrep BY state:
DISPLAY STREAM phonelist cust-num name city state phone
WITH NO-LABELS STREAM-IO.
END.
Alternate I/O Sources
723
The p-dispho.p procedure defines the SHARED STREAM phonelist, and displays the
information from that stream on the screen. (It is more efficient to place the FOR EACH and
DISPLAY statements in the p-sstrm.p procedure. They are in a separate procedure here to
illustrate shared streams.)
NOTE: You cannot define or access shared streams in a persistent procedure. If you do,
Progress returns a run-time error when you create the persistent procedure. For more
information, see the sections on persistent procedures in Chapter 3, Block
Properties.
Sharing streams is much like sharing variables:
You use a regular DEFINE STREAM statement to define a stream that is available only
to the current procedure.
To define a shared stream, you define the stream as NEW SHARED in the procedure that
creates the stream, and as SHARED in all other procedures that use that stream. If you do
not explicitly close the stream, Progress closes it automatically at the end of the procedure
in which you defined it.
You define the stream as NEW GLOBAL when you want that stream to remain available
even after the procedure that contains the DEFINE NEW GLOBAL SHARED STREAM
statement ends.
Progress Programming Handbook
724
7.6 Summary of Opening and Closing Streams
Table 72 describes how you establish, open, use, and close default streams and streams you
name.
Table 72: Using Streams
Action Unnamed Streams Named Streams
Establish the
stream
By default, each procedure gets
one unnamed input stream and
one unnamed output stream.
You define the stream explicitly
by using one of these statements:
DEFINE STREAM, DEFINE
NEW SHARED STREAM,
DEFINE SHARED STREAM,
DEFINE NEW GLOBAL
SHARED STREAM.
Open the stream Automatically opened, using the
output destination to which the
calling procedures unnamed
stream is directed and the input
source from which the calling
procedures input is read. You
can also explicitly name a
destination or source by using
OUTPUT TO or INPUT FROM.
You open the stream explicitly
by using OUTPUT STREAM
name TO or INPUT STREAM
name FROM.
Use the stream All data-handling statements use
the stream by default.
Name the opened stream in the
data-handling statement that will
use the stream.
Close the stream Automatically closed at the end
of the procedure that opened it.
You can also explicitly close it
with the OUTPUT CLOSE or
INPUT CLOSE statement.
Local streams are automatically
closed at the end of the
procedure. Shared streams are
automatically closed when the
procedure that defined the stream
as NEW ends. Global streams are
closed at the end of the Progress
session.
You can also explicitly close
named streams by using the
INPUT CLOSE or OUTPUT
CLOSE statement or by opening
the stream to a new destination or
from a new source.
Alternate I/O Sources
725
Initially, a default input stream has as its source the most recent source specified in the calling
procedure or, if there is no calling procedure, the terminal. The default output stream has as its
destination the most recent destination specified in the calling procedure or, if there is no calling
procedure, the terminal. If you are running a procedure in batch or background, you must
explicitly indicate a source and/or destination.
When an unnamed stream is closed (either automatically or explicitly), it is automatically
redirected to its previous destination (the destination of the procedure it is in). If the stream is
not in a procedure, the stream is redirected to or from the terminal.
When you close a named stream, you can no longer use that stream until it is reopened. When
you close an input stream associated with a file and then reopen that stream to the same file,
input starts from the beginning of the file.
7.7 Processes as Input and Output Streams (NT and UNIX only)
You can import data into Progress from a process or pipe data from Progress to another process
using one of the following statements:
INPUT THROUGH statement to import data into Progress from another process
OUTPUT THROUGH statement to pipe data from Progress to another process
INPUTOUTPUT THROUGH statement to pipe the output of a process into a Progress
procedure and to pipe data from Progress back to that same process.
This allows two-way communication to a program written in C or any other language. You
might use this capability to do specialized calculations on data stored in a Progress
database or entered during a Progress session.
For more information on these statements, see the Progress Language Reference.
7.8 I/O Redirection for Batch Jobs
You can use the < and > symbols on the Progress command line to redirect I/O for batch jobs.
For details, see the entry for the Batch (b) startup parameter in the Progress Startup Command
and Parameter Reference.
Progress Programming Handbook
726
7.9 Reading the Contents of a Directory
Sometimes, rather than reading the contents of a file, you want to read a list of the files in a
directory. You can use the OSDIR option of the INPUT FROM statement for this purpose.
Each line read from OSDIR contains three values:
The simple (base) name of the file.
The full pathname of the file.
A string value containing one or more attribute characters. These characters indicate the
type of the file and its status. Every file has one of the following attribute characters:
F Regular file or FIFO pipe
D Directory
S Special device
M Member of a Progress r-code library
X Unknown file type
In addition, the attribute string for each file might contain one or more of the following
attribute characters:
H Hidden file
L Symbolic link
P Pipe file
The tokens are returned in the standard Progress format that can be read by the IMPORT or SET
statements.
Alternate I/O Sources
727
The following example uses the OSGETENV function to find the path of the DLC directory.
It then uses the OSDIR option of INPUT FROM to read the contents of the directory:
In p-osdir.p, only the base name of the file and attribute string are read from OSDIR. The
caret (^) is used in the SET statement to skip over the pathname of the file.
For more information on the OSDIR option, see the INPUT FROM Statement reference entry
in the Progress Language Reference.
You can find additional information on a single file by using the FILEINFO system handle. To
use the FILEINFO handle, first assign the pathname of an operating system file to the
FILEINFO:FILENAME attribute. You can then read other FILEINFO attributes. For
example, the p-osfile.p procedure prompts for the pathname of a file and then uses the
FILEINFO handle to get information on that file:
p-osdir.p
DEFINE VARIABLE search-dir AS CHARACTER.
DEFINE VARIABLE file-name AS CHARACTER FORMAT "x(16)" LABEL "File".
DEFINE VARIABLE attr-list AS CHARACTER FORMAT "x(4)" LABEL "Attributes".
search-dir = OS-GETENV("DLC").
INPUT FROM OS-DIR(search-dir).
REPEAT:
SET file-name ^ attr-list
WITH WIDTH 70 USE-TEXT TITLE "Contents of " + search-dir.
END.
INPUT CLOSE.
p-osfile.p
DEFINE VARIABLE os-file AS CHARACTER FORMAT "x(60)" LABEL "File".
REPEAT:
SET os-file WITH FRAME osfile-info.
FILE-INFO:FILE-NAME = os-file.
DISPLAY FILE-INFO:FULL-PATHNAME FORMAT "x(60)" LABEL "Full Path"
FILE-INFO:PATHNAME FORMAT "x(60)" LABEL "Path"
FILE-INFO:FILE-TYPE LABEL "Type"
WITH FRAME osfile-info SIDE-LABELS TITLE "OS File Info".
END.
Progress Programming Handbook
728
For more information, see the FILEINFO System Handle reference entry in the Progress
Language Reference.
7.10 Performing Code-page Conversions
Computer systems store text data using character codes, typically 7 or 8bit numeric codes that
map to specific visual character representations. The series of character codes that make up the
character set that a system uses is referred to as a code page.
Progress provides a character set management facility to automatically convert data between the
code pages of different data sources and destinations (targets). In general, the supported data
sources and targets for code page conversion include memory, streams, and databases. You can
specify default code page conversions for a session using conversion tables and startup
parameters to specify the code page for each data source and target. For more information on
this character set facility, see the Progress Internationalization Guide.
The Progress 4GL also allows you to perform I/O that explicitly converts data from one code
page to another in order to facilitate I/O between data sources and destinations intended for
different computer systems or components. You can specify the name of the code page for a data
source and target as parameters to several statements and functions.
To convert between source and target characters or strings in memory, you can specify code
page parameters in these functions:
ASC
CHR
CODEPAGECONVERT
To convert data input and output, you can specify code page parameters in these statements:
INPUT FROM (input source to memory target)
OUTPUT TO (memory source to output target)
To convert piped input and output, you can specify code page parameters in these statements:
INPUT THROUGH (program source to memory target)
OUTPUT THROUGH (memory source to program target)
INPUTOUTPUT THROUGH (program source to program target)
Alternate I/O Sources
729
These statements and functions take available code page name parameters as character
expressions (for example, ibm850). The code page names you specify must be defined in your
Progress conversion map file (convmap.cp, by default). Also, the source and target conversion
tables must be defined in the conversion map file for each code page to support the specified
conversions. For more information on building a conversion map file, see the Progress
Internationalization Guide.
For example, this procedure writes to two output streams using two different code pages:
For this example, assume that the internal code page is iso88591. If a customer name from
the sports database is greater than or equal to a prompted name (xname), then the procedure
writes the corresponding customer record to the file, german.txt, using the german7bit
code page. Otherwise, it writes the corresponding customer record to the file, swedish.txt,
using the swedish7bit code page. These conversions are handled by the conversion tables
provided with Progress.
For more information on specifying code page parameters, see the reference entry for each
statement or function that supports code page conversion in the Progress Language Reference.
p-codpag.p
DEFINE VARIABLE xname LIKE customer.name INITIAL ?.
DO WHILE xname <> "":
ASSIGN xname = "".
DISPLAY xname LABEL "Starting Name to German List"
WITH FRAME A SIDE-LABELS.
SET xname WITH FRAME A.
IF xname <> "" THEN DO:
OUTPUT TO german.txt
CONVERT SOURCE "iso8859-1" TARGET "german-7-bit".
FOR EACH customer WHERE customer.name >= xname BY name:
DISPLAY customer WITH STREAM-IO.
END.
OUTPUT CLOSE.
OUTPUT TO swedish.txt
CONVERT SOURCE "iso8859-1" TARGET "swedish-7-bit".
FOR EACH customer WHERE customer.name < xname BY name:
DISPLAY customer WITH STREAM-IO.
END.
OUTPUT CLOSE.
END.
END.
Progress Programming Handbook
730
7.11 Converting Non-standard Input Files
The input files used in the previous examples contained data that was in a very specific format:
When using a data file as an input source, Progress, by default, expects that file to conform to
the following standards:
One or more spaces must separate each field value.
Character fields that contain embedded blanks must be surrounded by quotes ( ).
Any quotes in the data must be represented by two quotes ( ).
What if you need to deal with an input file that does not conform to these standards? For
example, you might have a file in which each field is on a separate line; or where fields are
separated by commas instead of spaces; or where the fields have no special delimiters, but
appear at specific column locations.
Progress provides two strategies for dealing with such files:
Use the QUOTER utility to convert the file to the standard Progress format. You can then
use the normal Progress frame-based input statements, such as SET, to read data from the
file.
Use the IMPORT statement to read from the file.
Which method you choose depends on your particular circumstances. For example:
The frame-based statements, such as SET, validate the format of the incoming data;
IMPORT does not. In some cases you want this validation and in others you might not
want it.
If you expect to read the same file repeatedly, preprocessing that file once with QUOTER
might be worthwhile. However, if your code reads from different files, or the content of
the file is subject to change, you might want to avoid repeated preprocessing by using
IMPORT instead.
The following section explains how to use QUOTER. For more information on using the
IMPORT statement, see the Importing and Exporting Data section.
p-datf1.d
90 "Wind Chill Hockey" BBB
91 "Low Key Checkers" DKP
92 "Bings Ping Pong" SLS
Alternate I/O Sources
731
7.11.1 Using QUOTER to Format Data
The QUOTER utility formats character data in a file to the standard format so it can be used by
a Progress procedure. By default, QUOTER does the following:
Takes the name of a file as an argument
Reads input from that file
Places quotes () at the beginning and end of each line in the file.
Replaces any already existing quotes () in the data with two quotes ( ).
You can use the QUOTER utility directly from the operating system using the operating system
statement, as appropriate, to reformat data from a file that is not in the standard Progress input
format:
PARAMETERS
input-file-name
Specifies the file you are using.
> output-file-name
Specifies the file where you want to send the output.
-d character
Identifies a field delimiter. You can specify any punctuation character.
-c startcol-stopcol
Specifies the ranges of column numbers to be read as fields. Do not use any spaces in the
range list.
Operating
System
Syntax
UNIX
Windows
quoter input-file-name
[ > output-file-name
| -d character
| -c startcol-stopcol
] ...
Progress Programming Handbook
732
Suppose your data file looked like the following example:
You use QUOTER to put this file into standard format. Use commands shown in Table 73 to
run QUOTER from the Procedure Editor:
p-datf12.d
90
Wind Chill Hockey
BBB
91
Low Key Checkers
DKP
92
Bings Ping Pong
SLS
Table 73: Quoter Examples
Operating
System Using QuoterExamples
Windows
DOS quoter p-dat12.d >p-dat12.q
UNIX
UNIX quoter p-datf12.d >p-datf12.q
Alternate I/O Sources
733
In Figure 78, p-datfl2.d is the name of the data file you supply to QUOTER while
p-datfl2.q is the name of the file in which you want to store QUOTERs output.
Figure 78: How QUOTER Prepares a File
The p-datfl2.q file contains the QUOTER output:
Now this file is in the appropriate format to be used as input to a Progress procedure.
p-datf12.q
"90"
"Wind Chill Hockey"
"BBB"
"91"
"Low Key Checkers"
"DKP"
"92"
"Bings Ping Pong"
"SLS"
Quoter Prepares the File
90"
Wind Chill"
"Hockey
BBB
90
Wind Chill
Hockey
BBB
p-datfl2.d
p-datfl2.q
Progress Programming Handbook
734
What if each of the field values in your data file is not on a separate line (unlike p-datfl2.d)
and without quotes (unlike p-datf1.d)? That is, your data file looks like p-datfl3.d:
Suppose you wanted to use this file as the input source to create customer records for customers
90, 91, and 92. You need to make each line of the file into a character string and then assign a
substring of this value to each field. The procedure p-chgin3.p does that.
The numbers to the left of the procedure correspond to the following step-by-step descriptions:
1. The QUOTER utility takes the data from the p-datfl3.d file and produces data that looks
like the following example:
2. The INPUT FROM statement redirects the input stream to get input from the p-datfl3.q
file.
3. The CREATE statement creates an empty customer record.
p-datf13.d
90 Wind Chill Hockey BBB
91 Low Key Checkers DKP
92 Bings Ping Pong SLS
p-chgin3.p
DEFINE VARIABLE data AS CHARACTER FORMAT "x(78)".
/*1*/ OS-COMMAND SILENT quoter p-datfl3.d > p-datfl3.q.
/*2*/ INPUT FROM p-datfl3.q NO-ECHO.
REPEAT:
/*3*/ CREATE customer.
/*4*/ SET data WITH NO-BOX NO-LABELS NO-ATTR-SPACE WIDTH 80.
cust-num = INTEGER(SUBSTRING(data,1,2)).
name = SUBSTRING(data,4,17).
/*5*/ sales-rep = SUBSTRING(data,22,3).
END.
/*6*/ INPUT CLOSE.
p-datf13.q
"90 Wind Chill Hockey BBB"
"91 Low Key Checkers DKP"
"92 Bings Ping Pong SLS"
Alternate I/O Sources
735
4. The SET statement uses the first quoted line in the p-datfl3.q file as input and puts that
line in the data variable. Once that line of data is in the line variable, the next statements
break it up into pieces that get stored in individual customer fields.
5. The SUBSTRING functions take the appropriate pieces of the data in the line variable and
store the data in the custnum, name, and salesrep fields, as shown in Figure 79.
Figure 79: Extracting QUOTER Input with SUBSTRING
Because Progress assumes that all the data in the p-datfl3.q file is character data, you
must use the INTEGER function to convert the custnum data to an integer value.
6. The INPUT CLOSE statement closes the input stream coming from the p-datfl3.q file
and redirects the input stream to the terminal.
NOTE: With this method, all trailing blanks are stored in the database. To avoid this
problem, use the c or d option of QUOTER.
You can use QUOTER to prepare files formatted in other ways as well. For example, suppose
the field values in your data file are separated by a specific character, such as a comma (,), as in
p-datfl4.d:
p-datf14.d
90,Wind Chill Hockey,BBB
91,Low Key Checkers,DKP
92,Bings Ping Pong,SLS
Column
Number of
Characters
90 Wind Chill Hockey BBB
1 4 22
2 17 3
cust-num
name
sales-rep
IS
IS
IS
INTEGER (SUBSTRING(line,1,2))
(SUBSTRING(line,4,17))
(SUBSTRING(line,22,3))
Progress Programming Handbook
736
You can use a special option, d, (on UNIX) to tell QUOTER what character separates fields.
The procedure p-chgin4.p reads the comma-separated data from p-datfl4.d:
Here, the d option or the DELIMITER qualifier tells QUOTER that a comma (,) is the
delimiter between each field in the data file. The output of QUOTER is shown in p-datfl4.q:
This data file is in the standard blank-delimited Progress format. If your data file does not use a
special field delimiter that you can specify with the d QUOTER option or the /DELIMITER
qualifier, but does have each data item in a fixed column position, you can use another special
option, c, on Windows and UNIX.
You use the c option or /COLUMNS to identify the columns in which fields begin and end.
For example, suppose your file looks like p-datfl5.d:
p-chgin4.p
OS-COMMAND SILENT quoter -d , p-datfl4.d >p-datfl4.q.
INPUT FROM p-datfl4.q NO-ECHO.
REPEAT:
CREATE customer.
SET cust-num name sales-rep.
END.
INPUT CLOSE.
p-datf14.q
"90" "Wind Chill Hockey" "BBB"
"91" "Low Key Checkers" "DKP"
"92" "Bings Ping Pong" "SLS"
p-datf15.d
90 Wind Chill Hockey BBB
91 Low Key Checkers DKP
92 Bings Ping Pong SLS
Alternate I/O Sources
737
The procedure p-chgin5.p uses this data file to create customer records:
Because you used the c option, this procedure produces a data file without trailing blanks.
You can also use QUOTER interactively to reformat your data. You can access QUOTER
interactively through the Administration Tool or, in character interfaces, the Data Dictionary.
From the main menu, choose Utilities.
7.11.2 Importing and Exporting Data
Sometimes you send data to a file knowing that it will be used later by a Progress procedure. If
so, then you also know that the data file must be in standard format with character fields
surrounded by quotes. Therefore, instead of just redirecting the output to the file and using the
DISPLAY statement to send output to that file, you might use the EXPORT statement.
NOTE: Do not confuse the EXPORT statement with the EXPORT method of the SESSION
handle. The EXPORT statement converts data from one format to another while
redirecting it. The EXPORT method of the SESSION handle adds procedure names
to the export list (list of procedures a requesting procedure can access) of a Progress
AppServer. For more information on Progress AppServers, see Building Distributed
Applications Using the Progress AppServer.
p-chgin5.p
OS-COMMAND SILENT quoter -c 1-2,4-20,22-24 p-datfl5.d >p-datfl5.q.
INPUT FROM p-datfl5.q NO-ECHO.
REPEAT:
CREATE customer.
SET cust-num name sales-rep.
END.
INPUT CLOSE.
Progress Programming Handbook
738
Using the EXPORT Statement
The EXPORT statement sends data to a specified output destination, formatting it in a way that
can be easily used by another Progress procedure. For example, p-export.p writes customer
information to a file:
The output from p-export.p is written to p-datfl6.d:
Now this file is ready to be used as an input source by another Progress procedure. There is no
need to process it through QUOTER.
By default, the EXPORT statement uses the space character as a delimiter between fields. You
can use the DELIMITER option of the EXPORT statement to specify a different delimiter.
For example, p-exprt2.p writes to a file in which field values are separated by commas:
p-export.p
OUTPUT TO p-datfl6.d.
FOR EACH customer:
EXPORT cust-num name sales-rep;.
END.
OUTPUT CLOSE.
p-datf16.d
1 "Lift Line Skiing" "HXM"
2 "Urpon Frisbee" "DKP"
3 "Hoops Croquet Comp" "HXM"
4 "Go Fishing Ltd" "SLS"
5 "Match Point Tennis" "JAL"
.
.
.
p-exprt2.p
OUTPUT TO p-datfl7.d.
FOR EACH customer:
EXPORT DELIMITER "," cust-num name sales-rep.
END.
OUTPUT CLOSE.
Alternate I/O Sources
739
The output from p-exprt2.p is written to p-datfl7.d:
You can read this file by using the DELIMITER option of the IMPORT statement. More likely,
you would prepare a file like this to be read by another application.
For more information on the EXPORT statement, see the Progress Language Reference.
7.11.3 Using the PUT Statement
If you need to prepare a data file in a fixed format, perhaps for use by another system, you can
use the PUT statement. The following procedure uses PUT statement to unite to a file:
The output from p-putdat.p is written to p-datfl8.d:
p-datf17.d
1,"Lift Line Skiing","HXM"
2,"Urpon Frisbee","DKP"
3,"Hoops Croquet Comp","HXM"
4,"Go Fishing Ltd","SLS"
5,"Match Point Tennis","JAL"
.
.
.
p-putdat.p
OUTPUT TO p-datfl8.d.
FOR EACH customer:
PUT cust-num AT 1 name AT 10 sales-rep AT 40 SKIP.
END.
OUTPUT CLOSE.
p-datf18.d
1 Lift Line Skiing HXM
2 Urpon Frisbee DKP
3 Hoops Croquet Comp HXM
4 Go Fishing Ltd SLS
5 Match Point Tennis JAL
.
.
.
Progress Programming Handbook
740
The PUT statement formats the data into the columns specified with the AT options. Only the
data is output: there are no labels and no box. The SKIP option indicates that you want each
customers data to begin on a new line.
For more information on the PUT statement, see the Progress Language Reference.
7.11.4 Using the IMPORT Statement
The IMPORT statement is the counterpart of the EXPORT statement. It reads an input file into
Progress procedures, one line at a time.
Using IMPORT to Read Standard Data
The following example shows IMPORT reading the file exported by the procedure p-export.p:
This relies on the input being space separated. You can also use the DELIMITER option of the
IMPORT statement to read a file with a different separator.
For example, p-imprt2.p reads the file produced by p-exprt2.p in the previous section:
This example reads one line at a time from p-datfl7.d into the character-string variable data.
It then breaks the line into discrete values and assigns them to the fields of a customer record.
p-import.p
INPUT FROM p-datfl6.d.
REPEAT:
CREATE customer.
IMPORT cust-num name sales-rep.
END.
INPUT CLOSE.
p-imprt2.p
INPUT FROM p-datfl7.d.
REPEAT:
CREATE customer.
IMPORT DELIMITER "," cust-num name sales-rep.
END.
OUTPUT CLOSE.
Alternate I/O Sources
741
Using IMPORT to Read Non-standard Data
Although the IMPORT statement is used primarily to read data in the standard format written
by the EXPORT statement. However, you can use the UNFORMATTED and DELIMITER
options of IMPORT to read data in non-standard formats.
When you use the UNFORMATTED option, the IMPORT statement reads one line from the
input file. For example, suppose your input file is formatted as follows:
The lines containing custnum and salesrep values can be read with normal IMPORT
statements. However, if you try to read the customer name values with a normal IMPORT
statement, only the first word of each name is readthe space character is treated as a delimiter.
To prevent this, read the name with the UNFORMATTED option, as in p-impun1.p.
Now, suppose each line of the file contained a custnum, name, and salesrep value, but no
special delimiters are used. Instead, the fields are defined by their position within the line:
p-datf12.d
90
Wind Chill Hockey
BBB
91
Low Key Checkers
DKP
92
Bings Ping Pong
SLS
p-impun1.p
INPUT FROM p-datfl2.d.
REPEAT:
CREATE customer.
IMPORT customer.cust-num.
IMPORT UNFORMATTED customer.name.
IMPORT customer.sales-rep.
END.
INPUT CLOSE.
p-datf13.d
90 Wind Chill Hockey BBB
91 Low Key Checkers DKP
92 Bings Ping Pong SLS
Progress Programming Handbook
742
In p-datfl3.d, the first three character positions in each line are reserved for the cust-num
value, the next 17 positions for the name value, and the last three for the sales-rep value. Space
characters may occur between fields, but they may also occur within a field value. To process
this file with the IMPORT statement, use the UNFORMATTED option to read one line at a
time, as shown in p-impun2.p:
After p-impun2.p reads each line, it uses the SUBSTRING function to break the line into field
values. It then assigns these values to the appropriate fields in the customer record.
NOTE: If a line in your input file ends with a tilde (~), Progress interprets that as a
continuation character. This means, that line and the following line are treated as a
single line. Therefore, the IMPORT statement with the UNFORMATTED option
reads both lines into a single variable.
What if fields values are separated by a delimiter other than the space character? For example,
in p-datfl4.d, field values are separated by commas:
p-impun2.p
DEFINE VARIABLE file-line AS CHARACTER.
INPUT FROM p-datfl3.d.
REPEAT:
CREATE customer.
IMPORT UNFORMATTED file-line.
ASSIGN customer.cust-num = INTEGER(SUBSTRING(file-line, 1, 2))
customer.name = TRIM(SUBSTRING(file-line, 4, 17))
sales-rep = SUBSTRING(file-line, 22, 3).
END.
INPUT CLOSE.
p-datf14.d
90,Wind Chill Hockey,BBB
91,Low Key Checkers,DKP
92,Bings Ping Pong,SLS
Alternate I/O Sources
743
You could use the UNFORMATTED option of the IMPORT statement to read this file one line
at a time and then use the INDEX function to locate the commas and break the line into field
values. Another solution is to use the DELIMITER option of the IMPORT statement as shown
in p-impun3.p:
In this example, the DELIMITER option specifies that field values are separated by commas
rather than by spaces. Therefore, the IMPORT statement parses each line correctly and assigns
each value to the appropriate field.
NOTE: You can only specify a single character as a delimiter. If the value you give with the
DELIMITER option is longer than one character, then only the first character is used.
For more information on the IMPORT statement, see the Progress Language Reference.
p-impun3.p
INPUT FROM p-datfl4.d.
REPEAT:
CREATE customer.
IMPORT DELIMITER "," cust-num name sales-rep.
END.
INPUT CLOSE.
Progress Programming Handbook
744
8
The Preprocessor
This chapter discusses the Progress language preprocessor, a powerful tool that enhances your
programming flexibility. The Progress preprocessor allows you to write applications that are
easy to read, modify, and transport to other operating systems.
The preprocessor is a component of the Progress Compiler. Before the Compiler analyzes your
source code and creates r-code, the preprocessor examines your source code and performs text
substitutions. The preprocessor also conditionally includes blocks of source code to compile.
The preprocessor operates on a compilation unit, which is a group of files compiled together to
produce one completed program. You can think of the preprocessor as a tool that prepares a final
version of your source code just before it is compiled.
You control the preprocessor by placing preprocessor directives throughout your source code.
A preprocessor directive is a statement that begins with an ampersand (&) and is meaningful
only to the preprocessor. These directives are described in this chapter.
Progress Programming Handbook
82
8.1 &GLOBAL-DEFINE and &SCOPED-DEFINE Directives
The &GLOBAL-DEFINE and &SCOPED-DEFINE directives allow you to define
preprocessor names, which are compile-time constants. Their syntax is as follows:
The preprocessorname is a name that you supply, and definition is a string of characters. If
definition is longer that one line, you can use a tilde (~) to continue onto the next line. You must
place these directives at the beginning of a line, preceded only by blank or tab characters. The
preprocessor trims all leading and trailing spaces from definition. You can abbreviate
&GLOBAL-DEFINE and &SCOPED-DEFINE to &GLOB and &SCOP, respectively.
You can use reserved Progress keywords as preprocessor names. However, unless they are
defined as part of a preprocessor name, reserved Progress keywords cannot be placed in
preprocessor expressions.
8.1.1 Example Using &GLOBAL-DEFINE
After you define a preprocessor name, you can reference it within your source code. Wherever
a reference appears, the preprocessor substitutes the string of characters that you defined:
In this example, MAX-EXPENSE is the preprocessor name. Once it is defined, you can
reference MAX-EXPENSE from anywhere within your compilation unit (including internal
procedures and include files). A reference to a preprocessor name is specified in the form
{&preprocessorname}. (For more information on referencing preprocessor names, see the
Referencing Preprocessor Names section.) Wherever you reference MAX-EXPENSE in your
source code, Progress substitutes the text 5000 at compile time. For example, your source
code might contain a line like this:
SYNTAX
&GLOBAL-DEFINE preprocessor-name definition
SYNTAX
&SCOPED-DEFINE preprocessor-name definition
&GLOBAL-DEFINE MAX-EXPENSE 5000
IF tot-amount <= {&MAX-EXPENSE} THEN DO:
The Preprocessor
83
Before the Compiler analyzes your source code, the preprocessor performs the text substitution.
The line of code that the Progress Compiler analyzes and compiles is as follows:
You can see how the preprocessor makes your source code easier to maintain. A preprocessor
name like MAX-EXPENSE might be referenced many times in your source code. You only
have to change one line of code (the definition of MAX-EXPENSE) to change the value of all
of the references. In addition, you can see how preprocessor references make your code easier
to read: {&MAX-EXPENSE} is more readable and understandable than 5000. Note that the
characters in the name MAX-EXPENSE are all uppercase; this is not required, but it makes your
source code more readable. Preprocessor names are case insensitive.
If you want to use a preprocessor name as a text string within an application, you must put
quotes around it:
8.1.2 Preprocessor Name Scoping
Each preprocessor name that you define has a specific scope. The scope of a name is the area
within its compilation unit where you can access, or reference, the name. You determine the
scope of a preprocessor name by where you define it within the compilation unit and by whether
you define it with the &GLOBAL-DEFINE or &SCOPED-DEFINE directive. A name defined
with the &GLOBAL-DEFINE directive is globally defined; a name defined with the
&SCOPED-DEFINE directive is nonglobally defined.
The scope of a preprocessor name always begins at the directive that defines it; the name is not
accessible before the defining directive.
The scope of a globally defined name extends forward from the point of definition to the end of
the compilation unit. The scope of a nonglobally defined name extends forward from the point
of definition to the end of the file that contains its definition. It does not extend beyond the end
of the file to the rest of the compilation unit. It does, however, extend to any files included
within the defining file.
IF tot-amount <= 5000 THEN DO:
DISPLAY "{&MAX-EXPENSE}".
Progress Programming Handbook
84
If you define a name in a top-level file of a compilation unit, it makes no difference whether the
name is globally defined or nonglobally defined. In either case, the name is accessible from the
point of definition to the end of the compilation unit. However, if you define a name within an
include file, it does make a difference how the name is defined. Globally defined names are
accessible to the end of the compilation unit; nonglobally defined names are only accessible
within the defining file. See Figure 81.
main.p
include.i
Figure 81: Name Scoping
main.p
Compilation Unit
&GLOB SOURCE-NAME "main.p"
DISPLAY {&SOURCE-NAME}.
&SCOP SOURCE-NAME "include.i"
DISPLAY {&SOURCE-NAME}.
DISPLAY {&SOURCE-NAME}.
&SCOP SOURCE-NAME "include.i"
DISPLAY {&SOURCE-NAME}.
&GLOB SOURCE-NAME "main.p"
DISPLAY {&SOURCE-NAME}.
{include.i}
DISPLAY {&SOURCE-NAME}.
include.i
Resolves
To
Resolves
To
DISPLAY "main.p".
DISPLAY "include.i".
DISPLAY "main.p".
Global scope begins
Nonglobal scope begins
Nonglobal scope ends
The Preprocessor
85
In Figure 81, the preprocessor name SOURCE-NAME is defined both globally and
nonglobally. In such a situation, the nonglobally defined name takes precedence over the
globally defined name within its scope. The nonglobally defined names scope begins at the first
line and ends at the last line of the file include.i. Within this scope, all references to
SOURCE-NAME access the defined value include.i.
The innermost name definition always takes precedence within its scope. If you define a name
nonglobally and then redefine the name nonglobally within a nested include file, only the
innermost name definition is accessible within the nested include file.
You can limit the scope of a name with the &UNDEFINE directive. This directive undefines
the name and ends its scope.
8.2 &UNDEFINE Directive
To remove the definition of a preprocessor name, you can undefine the name. This is the syntax
for &UNDEFINE:
The preprocessorname is the name you want to undefine.
When you use the &UNDEFINE directive, Progress warns you if the name you want to
undefine was not previously defined. The &UNDEFINE directive undefines the most recently
defined name within whose scope the &UNDEFINE directive resides. Once you undefine a
name, you can redefine it.
The &UNDEFINE directive also undefines named include file arguments. For more
information, see the Arguments to Include Files section.
SYNTAX
&UNDEFINE preprocessor-name
Progress Programming Handbook
86
8.3 &IF, &THEN, &ELSEIF, &ELSE, and &ENDIF Directives
You use these directives to set logical conditions when compiling blocks of code. This is the
syntax for these directives:
The expression can contain the following elements: preprocessor name references, the operators
listed in Table 82, the Progress functions listed in Table 83, and the DEFINED( )
preprocessor function. The DEFINED( ) function indicates whether a preprocessor name is
defined. For more information on the DEFINED( ) function, see the DEFINED( ) Preprocessor
Function section.
When the preprocessor evaluates an expression, all arithmetic operations are performed with
32-bit integers. Preprocessor name references used in arithmetic operations must evaluate to
integers.
When encountering an &IF directive, the preprocessor evaluates the expression that
immediately follows. This expression can continue for more than one line; the &THEN
directive indicates the end of the expression. If the expression evaluates to TRUE, then the block
of code between it and the next &ELSEIF, &ELSE, or &ENDIF is compiled. If the expression
evaluates to FALSE, the block of code is not compiled and the preprocessor proceeds to the next
&ELSEIF, &ELSE, or &ENDIF directive. No include files referenced in this uncompiled block
of code are included in the final source. You can nest &IF directives.
SYNTAX
&IF expression &THEN
.
.
.
[ &ELSEIF expression &THEN ] ...
.
.
.
[ &ELSE ]
.
.
.
&ENDIF
.
. = block of source code
.
The Preprocessor
87
Table 81 indicates how preprocessor expressions are evaluated.
The expression that follows the &ELSEIF directive is evaluated only if the &IF expression tests
FALSE. If the &ELSEIF expression tests TRUE, then the block of code between it and the next
&ELSEIF, &ELSE, or &ENDIF directive is compiled. If the &ELSEIF expression tests
FALSE, the preprocessor proceeds to the next &ELSEIF, &ELSE, or &ENDIF directive.
The block of code between the &ELSE and &ENDIF directives is compiled only if the &IF
expression and the &ELSEIF expressions all test FALSE. If there are no &ELSEIF directives,
the block of code is compiled if the &IF expression tests FALSE.
Once any &IF or &ELSEIF expression evaluates to TRUE, no other block of code within the
&IF ... &ENDIF block is compiled.
The &ENDIF directive indicates the end of the conditional tests and the end of the final block
of code to compile.
Table 81: Preprocessor Expressions
Type of Expression TRUE FALSE
LOGICAL TRUE FALSE
CHARACTER Non-empty Empty
INTEGER Non-zero 0
DECIMAL Not supported Not supported
Table 82: Preprocessor Operators (1 of 2)
Operator Description
+ Addition
- Subtraction
* Multiplication
/ Division
= Equality
<> Inequality
Progress Programming Handbook
88
These operators have the same precedence as the regular Progress 4GL operators.
> Greater than
< Less than
>= Greater than or equal to
<= Less than or equal to
AND Logical and
OR Logical or
NOT Logical not
BEGINS Compares the beginning letters of two expressions
MATCHES Compares two character strings
Table 82: Preprocessor Operators (2 of 2)
Operator Description
The Preprocessor
89
You can use any of these expressions within a preprocessor expression; they are evaluated at
compile time.
Table 83: Functions Allowed in Preprocessor Expressions
ABSOLUTE LC RIGHTTRIM
ASC LENGTH RANDOM
DATE LIBRARY REPLACE
DAY LOG ROUND
DECIMAL LOOKUP SQRT
ENCODE MAXIMUM STRING
ENTRY MEMBER SUBSTITUTE
ETIME MINIMUM SUBSTRING
EXP MODULO TIME
FILL MONTH TODAY
INDEX NUMENTRIES TRIM
INTEGER OPSYS TRUNCATE
KEYWORD PROPATH WEEKDAY
KEYWORDALL PROVERSION YEAR
LEFTTRIM RINDEX
Progress Programming Handbook
810
8.4 DEFINED( ) Preprocessor Function
The DEFINED( ) preprocessor function takes a preprocessor name or include file parameter as
an argument. The preprocessor name argument is not quoted, does not have an ampersand in
front of it, and is not in the reference form, {&preprocessorname}. For example, if you had
defined the preprocessor name MAXEXPENSE, the argument would appear as follows:
The DEFINED( ) function returns a value of 1 if the argument was a name defined with the
&GLOBALDEFINE directive; a value of 2 if the argument was passed as an include file
parameter; and a value of 3 if the argument was a name defined with the &SCOPEDDEFINE
directive. If the argument was not defined and was not an include file parameter, this function
returns a value of 0. The value returned refers to the definition that is current at the point
of the call.
8.5 The &MESSAGE Directive
The &MESSAGE directive allows you to display a message at compile time. This is the syntax
for &MESSAGE:
The textstring is a string of characters, preprocessor name references, named include file
arguments, or any combination of these that results in a character string to display. The
argument textstring does not have to be quoted.
DEFINED(MAX-EXPENSE)
SYNTAX
&MESSAGE text-string
The Preprocessor
811
8.6 Referencing Preprocessor Names
Use this syntax to reference a preprocessor name:
The preprocessorname is a name defined in a &GLOBALDEFINE or &SCOPEDDEFINE
directive, or is a built-in preprocessor name. (For information on built-in preprocessor names,
see the Using Built-in Preprocessor Names section.) Note that this syntax is identical to the
syntax used for referencing argument names in an include file. For more information on how
the preprocessor handles interactions between preprocessor names and include file argument
names, see the Arguments to Include Files section.
A reference to a preprocessor name can occur anywhere within your Progress source code:
When you reference a preprocessor name in your source code,Progress replaces the reference
with the value that you defined in the &GLOBALDEFINE or &SCOPEDDEFINE directive.
In this example, Progress replaces {&FIELDLIST} with the following code:
8.7 Expanding Preprocessor Names
When Progress encounters a reference to a preprocessor name, Progress replaces the reference
with the value of the preprocessor names definition. This is called expanding the reference:
SYNTAX
{ &preprocess-name }
&GLOBAL-DEFINE FIELD-LIST cust-name cust-num addr city
.
.
.
DISPLAY {&FIELD-LIST}.
cust-name cust-num addr city
&GLOBAL-DEFINE TABLE customer
&GLOBAL-DEFINE FIELD cust-name
&GLOBAL-DEFINE WHERE-CLAUSE WHERE {&FIELD} <> "Als Antiques"
FIND NEXT {&TABLE} {&WHERE-CLAUSE}.
Progress Programming Handbook
812
The first reference in this example is {&FIELD}. The curly brace ( { ) and ampersand ( & )
direct Progress to expand the reference. By checking the definition of the preprocessor name
FIELD, Progress determines that its value is custname. Progress therefore replaces the
reference with custname. As a result, WHERECLAUSE is defined as follows:
The second reference is {&TABLE}. Progress replaces this reference with customer; customer
is the definition of the preprocessor name TABLE.
The third reference is {&WHERECLAUSE}. Progress replaces WHERECLAUSE with its
definition, which has already been expanded.
After Progress has made all of these evaluations, the preprocessing phase of compilation is
complete. This is the FIND NEXT statement that Progress compiles:
In the previous example, the reference {&FIELD} was evaluated first. Progress also allows you
to defer the evaluation of a reference. By placing a tilde (~) in front of a preprocessor name
reference, you indicate that you do not want to expand the reference when it is first encountered.
(On UNIX, you can also use the backslash character ( \ ) to defer evaluation.):
Progress does not expand the reference {&OTHERFIELDS} in the definition of
FORMLIST, because {&OTHERFIELDS} has a tilde in front of it. In this instance, the curly
brace has no significance to Progress because of the tilde.
This is how Progress defines FORMLIST.
Instead of trying to expand the reference {&OTHERFIELDS}, Progress simply includes it in
the definition of FORMLIST.
WHERE cust-name <> "Als Antiques"
FIND NEXT customer WHERE cust-name <> "Als Antiques".
&GLOBAL-DEFINE FORM-LIST cust-num ~
FORMAT >>>9 ~{&OTHER-FIELDS} WITH FRAME X
&GLOBAL-DEFINE OTHER-FIELDS cust-name SKIP city state
FIND FIRST customer.
DISPLAY {&FORM-LIST}.
&SCOPED-DEFINE OTHER-FIELDS phone.
DISPLAY {&FORM-LIST}.
cust-num FORMAT >>>9 {&OTHER-FIELDS} WITH FRAME X
The Preprocessor
813
When the preprocessor reaches the first DISPLAY statement, Progress expands the
{&FORMLIST} reference. Since the definition of FORMLIST includes the
{&OTHERFIELDS} reference, Progress recursively expands {&OTHERFIELDS}. As a
result, Progress replaces {&FORMLIST} with this.
The &SCOPEDDEFINE directive changes the value of OTHERFIELDS. As a result, the
second {&FORMLIST} reference expands like this:
When you defer the evaluation of a reference, make sure that the reference will have a value at
the point of expansion. The following example does not do this:
In this example, the mistaken intention was to defer the evaluation of {&field_list} until
{&fields} is expanded. However, since field_list is nonglobally defined, its value does not
extend beyond the end of p-incl.i. As a result, there is no value assigned to field_list at the
point of expansion of {&fields}.
cust-num FORMAT >>>9 cust-name SKIP city state WITH FRAME X
cust-num FORMAT >>>9 phone WITH FRAME X.
p-proc.p
&GLOB file customer
{p-incl.i}
FOR EACH {&file}:
DISPLAY {&fields}.
END.
p-incl.i
&GLOB fields ~{&field_list}
&SCOP field_list name max-credit
Progress Programming Handbook
814
8.8 Using Built-in Preprocessor Names
Progress automatically provides the following preprocessor names:
BATCHMODE
OPSYS
FILENAME
LINENUMBER
SEQUENCE
WINDOWSYSTEM
You can reference these built-in preprocessor names anywhere within your source code,
including preprocessor expressions. They are read-only; you cannot redefine them with
&GLOBALDEFINE or &SCOPEDDEFINE directives. However, you can pass them as
include file arguments.
8.8.1 Built-in Preprocessor Name References
Table 84 lists the name reference for each built-in preprocessor name, and describes the value
it provides.
Table 84: Built-in Preprocessor Name References (1 of 2)
The Reference . . . Expands to an Unquoted String . . .
{&BATCHMODE} Equal to yes if the Batch (b) startup parameter was used to
start the client session. Otherwise, it expands to no.
{&FILENAME}
That contains the pathname of the file being compiled.
1
If you
want only the name of the file as specified in the {}Include File
Reference, the RUN statement, or the COMPILE statement,
use the argument reference {0}.
{&LINENUMBER} That contains the current line number in the file being
compiled. If you place this reference in an include file, the line
number is calculated from the beginning of the include file.
The Preprocessor
815
{&OPSYS} That contains the name of the operating system on which the
file is being compiled. The OPSYS name can have the same
values as the OPSYS function. The possible values are
"UNIX" and "WIN32".
2
{&SEQUENCE} Representing a unique integer value that is sequentially
generated each time the SEQUENCE preprocessor name is
referenced. When a compilation begins, the value of
{&SEQUENCE} is 0; each time {&SEQUENCE} is
referenced, the value increases by 1. To store the value of a
reference to SEQUENCE, you must define another
preprocessor name as {&SEQUENCE} at the point in your
code you want the value retained.
{&WINDOWSYSTEM} That contains the name of the windowing system in which the
file is being compiled. The possible values include
"MSWIN95","MSWINDOWS", and "TTY".
3
1
When running the source code of a procedure file loaded into the Procedure Editor or the AppBuilder,
{&FILENAME} expands to a temporary filename, not the name of the file under which the source code might
be saved.
2
Progress supports an override option for the &OPSYS preprocessor name that enables applications that need to
return the value of MS-DOS for all Microsoft operating systems to do so. For example, if you do not want the
value WIN32 to be returned when either Windows 95 or Windows NT operating systems are recognized, you
can override this return value by defining the Opsys key in Startup section of the current environment, which can
be in the registry or in an initialization file. If the Opsys key is located, the OPSYS preprocessor name returns
the value associated with the Opsys key on all platforms.
3
Progress supports an override option for the &WINDOWSYSTEM preprocessor name that provides backward
compatibility. This option enables applications that need the WINDOWSYSTEM preprocessor name to return
the value of MSWINDOWS for all Microsoft operating systems to do so. To establish this override value,
define the WindowSystem key in Startup section of the current environment, which can be in the registry or in
an initialization file. If the WindowSystem key is located, the WINDOWSYSTEM preprocessor name returns
the value associated with the WindowSystem key on all platforms.
Table 84: Built-in Preprocessor Name References (2 of 2)
The Reference . . . Expands to an Unquoted String . . .
Progress Programming Handbook
816
8.8.2 Quoting Built-in Names and Saving {&SEQUENCE}
Values
Note that all built-in name values are returned as unquoted strings. You must quote or not quote
the name reference according to the 4GL or preprocessor context of the reference.
Thus, a first reference to {&SEQUENCE}:
expands to:
A second reference to {&SEQUENCE}:
expands to:
Third through sixth references to {&SEQUENCE}, with a value-saving definition:
expands to:
DEFINE VARIABLE nexti AS INTEGER.
nexti = {&SEQUENCE}.
DEFINE VARIABLE nexti AS INTEGER.
nexti = 0.
DEFINE VARIABLE nextc AS CHARACTER.
nextc = "{&SEQUENCE}".
DEFINE VARIABLE nextc AS CHARACTER.
nextc = "1".
&GLOBAL-DEFINE OCCURENCE {&SEQUENCE} + 1
DEFINE VARIABLE triple AS INTEGER EXTENT 3
INITIAL [{&SEQUENCE}, {&SEQUENCE}, {&SEQUENCE}].
MESSAGE "Definition of triple complete as variable number" {&OCCURENCE}
VIEW-AS ALERT-BOX.
DEFINE VARIABLE triple AS INTEGER EXTENT 3
INITIAL [3, 4, 5].
MESSAGE "Definition of triple complete as variable number" 2 + 1
VIEW-AS ALERT-BOX.
The Preprocessor
817
8.9 Nesting Preprocessor References
During compilation, Progress recursively expands nested references, moving from the
innermost reference to the outermost reference. To see how Progress expands nested references,
look at the following procedure:
Progress expands the nested reference {&{&{&{pinclde.i}}}} by starting with the include file
p-inclde.i. For this example, p-inclde.i is as follows:
Progress replaces {pinclde.i} with the contents of p-inclde.i. After replacing {p-inclde.i}
with X, Progress then has to expand the following nested reference:
Progress expands this reference to the following code:
This expands to the following code:
Finally, this expands to the following code:
This last DISPLAY statement is the one that Progress finally compiles into your r-code.
p-main.p
&GLOBAL-DEFINE X Y
&GLOBAL-DEFINE Y Z
&GLOBAL-DEFINE Z "Hello"
DISPLAY {&{&{&{p-inclde.i}}}}.
p-inclde.i
X
DISPLAY {&{&{&X}}}.
DISPLAY {&{&Y}}.
DISPLAY {&Z}.
DISPLAY "Hello".
Progress Programming Handbook
818
8.10 Arguments to Include Files
If a named argument to an include file and a preprocessor name are the same, the named include
file argument overrides any previous preprocessor names. However, inside the include file any
preprocessor names override the include file arguments.
You can use the &UNDEFINE directive to undefine a named include file argument. However,
you cannot undefine an include file argument of the form {1}, {2}, etc.
8.11 Sample Application
The following application illustrates the use of preprocessor names. It allows you to update all
or some of the fields in a given database table. By changing a few preprocessor name
definitions, you can compile the application to modify different tables.
The Preprocessor
819
In addition, you can supply different FORM statements to display your data:
p-editrc.p
/* A procedure to illustrate the use of preprocessor names */
/* This procedure includes p-editrc.i, an include file that uses */
/* preprocessor names to allow you to easily access and modify any */
/* file in the demo database. Before you compile this procedure, */
/* you must set the following preprocessor definitions: */
/* */
/* NOTE: This file does NOT set a value to the form_file */
/* preprocessor name. */
/* */
/* PREPROCESSOR DESCRIPTION */
/* NAME */
/* */
/* file_name : The database file to modify. */
/* This name must have a value. */
/* file_fields : The list of fields in the database file to modify. */
/* This name must have a value. */
/* form_file : The name of an external form definition include (.f) */
/* file. This name is optional. */
/* frame_name : The name of the frame to be used in accessing the */
/* database file. */
/* frame_title : The title to be added to the frame upon display. If a */
/* form include file is used, TITLE should not be part of */
/* the form statement. */
/* use-buts : A comma-separated list of actions you may perform */
/* against the database in the browse utility. Available */
/* actions are Next, Prev, Update, Create, and Delete. */
/* banner : An example of deferred evaluation, this name references */
/* the ifile_ver preprocessor definition, which is set */
/* in the include file p-editrc.i. */
&GLOBAL-DEFINE file_name customer
&GLOBAL-DEFINE file_fields customer.cust-num customer.name~
customer.address customer.address2~
customer.city customer.st customer.postal-code~
customer.contact customer.phone
&GLOBAL-DEFINE form_file p-cust.f
&GLOBAL-DEFINE frame_name cust_fr
&GLOBAL-DEFINE frame_title Customer
&GLOBAL-DEFINE Banner File Modification Utility, Version ~{&ifile_ver}
&GLOBAL-DEFINE use-buts Next,Prev,Update,Create,Delete
{p-editrc.i}
Progress Programming Handbook
820
The p-editrc.p procedure defines form_file to be a reference to p-cust.f. It also includes
p-editrc.i, which references form_file.
p-editrc.i (1 of 6)
/* An include file to illustrate the use of preprocessor names. */
/* PREPROCESSOR DESCRIPTION */
/* NAME */
/* */
/* ifile_ver : The version of this file. The name is used with the */
/* preprocessor name banner defined in the main */
/* procedure through deferred evaluation. */
/* display : Combines the file field list and the frame name to make */
/* a more manageable statement when displaying and updating */
/* database records. */
/* but-list : A list of allowable actions that can be performed */
/* during execution of the browse utility. */
/* but-nums : The number of user-specified actions allowed. */
/* Generated through the use of {&SEQUENCE}. */
/* Displayed during compile time using &MESSAGE. */
DEFINE BUFFER {&file_name} FOR {&file_name}.
DEFINE VARIABLE do_return AS LOGICAL INITIAL FALSE.
/* Do some initial setup work */
PAUSE 0 BEFORE-HIDE.
&IF "{&frame_name}" = "" &THEN /* Make up a default frame name */
&MESSAGE A frame name was not supplied, using default name
&SCOPED-DEFINE frame_name Default_Frame
&ENDIF
&IF "{&form_file}" <> "" &THEN /* Define a frame for the user */
{{&form_file}} /* This includes the form
definition file if one is given */
&ELSE
&MESSAGE A Form file was not supplied, using default form
FORM {&file_fields}
WITH FRAME {&frame_name} ROW 7 CENTERED.
&ENDIF
&SCOPED-DEFINE ifile_ver 1.1a
&SCOPED-DEFINE display {&file_fields} WITH FRAME {&frame_name}30
The Preprocessor
821
/* Define buttons to use in browse as indicated by developer through */
/* the preprocessor name {&use-buts} */
DEFINE BUTTON next_but LABEL "Next"
TRIGGERS:
ON CHOOSE RUN getrec (INPUT "Next")./* On choose find next record */
END TRIGGERS.
&IF LOOKUP("Next", "{&use-buts}") <> 0 &THEN /* Will enable button
for NEXT */
&SCOPED-DEFINE but-list next_but
&SCOPED-DEFINE but-nums {&SEQUENCE}
&ENDIF
DEFINE BUTTON prev_but LABEL "Prev"
TRIGGERS:
ON CHOOSE RUN getrec (INPUT "Prev"). /* On choose find
prev record */
END TRIGGERS.
&IF LOOKUP("Prev", "{&use-buts}") <> 0 &THEN /* Will enable button
for PREV */
&SCOPED-DEFINE but-list {&but-list} prev_but
&SCOPED-DEFINE but-nums {&SEQUENCE}
&ENDIF
DEFINE BUTTON up_but LABEL "Update" /* On choose update record */
TRIGGERS:
ON CHOOSE UPDATE {&display}.
END TRIGGERS.
&IF LOOKUP("Update", "{&use-buts}") <> 0 &THEN /* Will enable button
for UPDATE */
&SCOPED-DEFINE but-list {&but-list} up_but
&SCOPED-DEFINE but-nums {&SEQUENCE}\
&ENDIF
p-editrc.i (2 of 6)
Progress Programming Handbook
822
DEFINE BUTTON cr_but LABEL "Create" /* On choose create and update */
TRIGGERS:
ON CHOOSE
DO:
DEFINE BUFFER localbuf FOR {&file_name}.
/* First save current location in the file */
FIND FIRST localbuf WHERE ROWID(localbuf) = ROWID({&file_name}).
/* Make the new record */
CREATE {&file_name}.
UPDATE {&display}.
/* Restore the location in the file */
FIND FIRST {&file_name} WHERE ROWID({&file_name}) = ROWID(localbuf).
END.
ON END-ERROR RETURN NO-APPLY.
/* If end-error, restores current location */
END TRIGGERS.
&IF LOOKUP("Create", "{&use-buts}") <> 0 &THEN
/* Enable button for CREATE */
&SCOPED-DEFINE but-list {&but-list} cr_but
&SCOPED-DEFINE but-nums {&SEQUENCE}
&ENDIF
DEFINE BUTTON del_but LABEL "Delete" /* On choose, delete record */
TRIGGERS:
ON CHOOSE RUN delrec.
END TRIGGERS.
&IF LOOKUP("Delete", "{&use-buts}") <> 0 &THEN
/* Enable button for DELETE */
&SCOPED-DEFINE but-list {&but-list} del_but
&SCOPED-DEFINE but-nums {&SEQUENCE}
&ENDIF
DEFINE BUTTON ret_but LABEL "Return"
TRIGGERS:
ON CHOOSE do_return = TRUE.
END TRIGGERS.
DEFINE BUTTON quit_but LABEL "Quit"
TRIGGERS.
ON CHOOSE QUIT.
END TRIGGERS.
&SCOPED-DEFINE but-list {&but-list} ret_but quit_but
&MESSAGE {&but-nums} Out of 5 user definable buttons are active
p-editrc.i (3 of 6)
The Preprocessor
823
FORM next_but Prev_but up_but cr_but Del_but ret_but quit_but
WITH FRAME but_fr ROW 4 CENTERED
TITLE "Work With {&file_name} Records" .
DISPLAY "{&Banner}" WITH FRAME ban_fr CENTERED .
VIEW FRAME but_fr.
ENABLE {&but-list} WITH FRAME but_fr.
REPEAT: /* Cycle through records, or add one. */
WAIT-FOR CHOOSE OF next_but,Prev_but,up_but,cr_but,Del_but,
ret_but, quit_but IN FRAME but_fr.
IF do_return THEN
RETURN.
DISPLAY {&display}.
END.
p-editrc.i (4 of 6)
Progress Programming Handbook
824
/**********************************************************************/
/* Procedure to find records in the database file being modified. */
/* It takes one parameter, action, which indicates whether you move */
/* forward or backward in the file. */
/**********************************************************************/
PROCEDURE getrec.
DEFINE INPUT PARAMETER action AS CHARACTER.
DEFINE BUFFER localbuf FOR {&file_name}.
IF action = "Next" OR action = "" THEN
DO:
FIND LAST localbuf.
IF ROWID(localbuf) = ROWID({&file_name}) THEN
DO:
BELL.
MESSAGE "You are already at the end of this file.".
RETURN ERROR.
END.
ELSE
FIND NEXT {&file_name} .
END.
ELSE
DO:
FIND FIRST localbuf.
IF ROWID(localbuf) = ROWID({&file_name}) THEN
DO:
BELL.
MESSAGE "You are already at the start of this file.".
RETURN ERROR.
END.
ELSE
DO:
FIND PREV {&file_name} .
END.
END.
END PROCEDURE. /* getrec */
p-editrc.i (5 of 6)
The Preprocessor
825
/************************************************************************/
/* Procedure to delete a record from the file. Cannot delete the last */
/* record from the file if it is the only record in the file. When the */
/* record is deleted, the next record is found. If this is not available*/
/* the previous record is found. */
/************************************************************************/
PROCEDURE delrec.
DEFINE BUFFER localbuf FOR {&file_name}.
/* Before delete of record, find another record to display after */
FIND localbuf WHERE ROWID(localbuf) = ROWID({&file_name}).
/* find the next record */
FIND NEXT {&file_name} NO-ERROR.
IF NOT AVAILABLE {&file_name} THEN
DO:
/* reset location in file */
FIND {&file_name} WHERE ROWID({&file_name}) = ROWID(localbuf).
/* find previous record */
FIND PREV {&file_name} NO-ERROR.
END.
IF NOT AVAILABLE {&file_name} THEN
DO:
BELL.
MESSAGE "You may not delete the last record in the file.".
RETURN ERROR.
END.
ELSE
DELETE localbuf.
END PROCEDURE. /* delrec */
p-editrc.i (6 of 6)
Progress Programming Handbook
826
p-cust.f
/* Form definition for updating the customer file. */
/* Please note that there is no TITLE in the frame phrase. */
FORM
customer.cust-num AT 5
customer.name AT 5
customer.address AT 5
customer.address2 AT 5
customer.city AT 5 customer.st AT 25 customer.postal-code AT 37
SKIP (1)
customer.contact AT 5
customer.phone AT 5
WITH FRAME cust_fr SIDE-LABELS ROW 9 CENTERED.
9
Database Access
This chapter describes how to access records in a Progress database. It includes information on
the following topics:
Connecting and disconnecting databases
Logical database names and aliases
Data-handling statements
Adding and deleting records
Fetching records and field lists
Joining tables
Word indexes
Sequences and how to access them
Database trigger considerations
Using the RAW data type
Multi-database programming techniques
Creating and using a schema cache file
Using query, buffer, and buffer-field objects
Progress Programming Handbook
92
9.1 Database Connections
A Progress application can access one or more Progress or non-Progress databases
simultaneously. The databases can be located on different operating systems using different
networking protocols. To access non-Progress databases, you must use the appropriate
DataServer, such as ORACLE, or C-ISAM. You must connect to a database before you can
access it. There are four ways to connect to a database:
As an argument when starting Progress
With the CONNECT statement (in the Progress Procedure Editor or in a Progress
procedure)
With the Progress Data Dictionary
Using the auto-connect feature
Note that a multi-user application can simultaneously connect to a database for only as many
times as specified in the Number of Users (n) startup parameter. For information on exceeding
this user count, see the Connection Failures and Disruptions section. For more information on
database connection management, see the Progress Startup Command and Parameter
Reference, the Progress Installation and Configuration Guide Version 9 for Windows or
Progress Installation and Configuration Guide Version 9 for UNIX and the Progress Client
Deployment Guide.
9.1.1 Connection Parameters
All of the database connection techniques let you use connection parameters. For more
information on these parameters, see the Progress Startup Command and Parameter Reference.
For multiple database programming, the most important connection parameter is the Physical
Database Name (db) startup parameter, which allows you to specify multiple databases.
The Number of Databases (h) startup parameter lets you set the number of connected databases
allowed during a Progress session, up to a maximum of 240 (the default is 5).
You can group connection parameters in a text file called a parameter file. The Parameter File
(pf) connection parameter invokes the parameter file when you connect to a database.
Parameter files ordinarily have .pf file extensions. For more information on parameter files, see
the Progress Startup Command and Parameter Reference.
Database Access
93
9.1.2 The CONNECT Statement
The CONNECT statement allows you to connect to a database from a Progress procedure or
directly from the Progress Procedure Editor. The CONNECT statement has the following
syntax:
physical-name
An argument that represents the actual name of the database on a disk. The first
physicalname argument you specify in a CONNECT statement does not require the
Database Name (db) parameter. All subsequent physicalnames must be preceded by the
db parameter.
parameter-file
The name of a parameter file that contains database connection information. See the
Progress Startup Command and Parameter Reference for more information on parameter
files.
parameters
One or more database connection parameters. Each connection parameter applies to the
most recently specified database (db). For the parameters you can specify, see the
information on client connection parameters in the Progress Startup Command and
Parameter Reference.
NO-ERROR
This argument suppresses the error condition, but still displays the error message when an
attempt to CONNECT to a database fails.
SYNTAX
CONNECT { physical-name [ parameters ]
| -db physical-name [ parameters ]
| -pf parameter-file [ parameters ]
}
[
{ -db physical-name [ parameters ]
|-pf parameter-file [ parameters ]
}
] ... [ NO-ERROR ]
Progress Programming Handbook
94
Although you can connect to several databases within one CONNECT statement, it is a good
idea to connect only one database per CONNECT statement, because a connection failure for
one database causes a termination of the current CONNECT statement. However, databases
already connected when the statement terminates stay connected. In cases like this, it is a good
idea to use the CONNECTED functions to see which databases were connected and which were
not.
Here is an example of using a parameter file with the CONNECT statement:
In this example, the CONNECT statement uses the parm3.pf file to connect to the appldb1
database.
A single procedure cannot connect to and then access a database. The following code fragment
does not run:
By splitting this example into a procedure and a subprocedure, you can connect to the database
in the main procedure, and then access it in the subprocedure:
For more information on the CONNECT statement, see the Progress Language Reference.
CONNECT -pf parm3.pf.
parm3.pf
-db appldb1
/* NOTE: this code does NOT work */
CONNECT sports.
FOR EACH sports.customer:
DISPLAY customer.
END.
topproc.p
CONNECT sports.
RUN p-subproc.p.
subproc.p
FOR EACH sports.customer:
DISPLAY customer.
END.
Database Access
95
9.1.3 Auto-connect
The auto-connect feature uses information stored in one database to connect to a second
database at run time. The database that contains the connect information is the primary
application database; it must be connected before Progress can execute the auto-connect.
Progress executes the auto-connect when a precompiled procedure references data from the
second database. It executes immediately prior to running the precompiled procedure. It does
not work with procedures run from the Editor or otherwise compiled on-the-fly.
If you use a CONNECT statement while you are connected to the primary database, Progress
merges the information in the CONNECT statement with the information in the primary
databases auto-connect list. If there is a conflict, the information in the CONNECT statement
takes precedence. For more information, see the CONNECT Statement reference entry in the
Progress Language Reference.
For information on how to set up an auto-connect list in the primary database, see the Progress
Client Deployment Guide.
9.1.4 Multi-database Connection Considerations
When you develop a multi-database application, keep in mind the following information on
database connections:
Connect all databases accessed by the application at compile time. Once compiled, the
application can run no matter where you store the databases or how you connect to them.
Establish unique logical names for each of the databases you connect. Use these names to
reference the databases within your application. These names are stored in the r-code of
the application at compilation time. For more information on logical database names, see
the Logical Database Names section.
9.1.5 Run-time Connection Considerations
As mentioned in the Database Connections section, there are various ways to connect at run
time. For multi-database applications, you must decide which technique to use.
When connecting in the run-time environment, you must use the same logical database names
that you used during compilation. If you need to use a different logical database name for some
reason, you may add an alias to the database to allow r-code files with other names to run.
Progress Programming Handbook
96
9.1.6 Connection Failures and Disruptions
Database connections may fail for a number of reasons:
The database server for multi-user database access is not availableperhaps the network
is down or it has not been started or the network parameters are incorrect.
The logical name of the application database already exists for the current session.
The syntax for a connection parameter is wrong.
The connection exceeds the maximum number of users per database (n).
Table 91 lists the default failure behavior for each of the run-time connection techniques
.
Progress displays any connection error messages at the point of failure.
Before running a procedure or subprocedure, Progress checks the procedure for database
references. For each reference, Progress searches through all of the connected databases, trying
to find the corresponding database. If the database is not found, Progress again searches all of
the connected databases for an auto-connect list with an entry for that database. If an entry is
found, Progress connects the database. If no entry is found, Progress does not run the procedure
and raises a stop or break condition in the calling procedure.
Table 91: Connection Failure Behavior
Connection Technique Default Connection Failure Behavior
At Progress startup The Progress session does not run.
CONNECT statement The procedure executes up to the CONNECT
statement where the connection failure occurs. The
connection failure raises the error condition for the
procedure. Progress error processing does not undo
database connections or disconnections. Any
database connected in the procedure before the
failed database connection remains connected. See
Chapter 5, Condition Handling and Messages, for
more information on error handling.
Auto-connect The procedures that contain a reference to the
auto-connect database do not run, and a stop or
break condition results in the calling procedure.
Database Access
97
Server, network, or machine failures can disrupt an application, even after a successful
connection. If a failure occurs while a subprocedure is accessing a database, Progress raises a
stop or break condition in the calling procedure.
The following sections present a number of recommendations to help you manage database
connection failures and disruptions in an application.
Using CONNECT with NOERROR
If you designate a startup procedure with the Startup Procedure (p) parameter, always use the
NOERROR option with the CONNECT statement. If a CONNECT statement fails, the
NOERROR option suppresses the resulting error condition, allowing the procedure to continue
executing. Although the NOERROR option bypasses ordinary error processing, Progress still
displays an error message. For more information on using the NOERROR option, see Chapter
5, Condition Handling and Messages.
After a connection failure, if a subprocedure tries to access the unconnected database, Progress
raises a stop or break condition in the calling procedure. Therefore, before you execute any
subprocedures that access a database, you test whether the database is connected. You can do
this with the CONNECTED function.
Using the CONNECTED Function
The CONNECTED function tests whether a database is connected. This function helps you to
route program control around portions of an application that might be affected by database
connection failures and disruptions. This example tests a database connection with the
CONNECTED function.
If the database is not connected, the above code attempts to connect the database. This example
only runs the procedure if the database is connected. Auto-connect precludes the use of the
CONNECTED function to test for database connections. For more information on the
CONNECTED function, see the Progress Language Reference.
IF NOT CONNECTED(logical-name)
THEN DO:
MESSAGE "CONNECTING logical-name".
CONNECT logical-name ... NO-ERROR.
END.
IF CONNECTED(logical-name)
THEN RUN procedure.
ELSE MESSAGE "DATABASE NOT AVAILABLE".
Progress Programming Handbook
98
9.1.7 Progress Database Connection Functions
Table 92 lists the Progress functions that allow you to test database connections, get
information on connected databases, and get information on the types of databases that you can
access.
Some of these functions take arguments; for more information on these functions, see the
Progress Language Reference.
Table 92: Progress Database Functions
Progress Function Description
CONNECTED Tests whether a given database is connected.
DATASERVERS Returns a character string containing a list of database
types supported by the installed Progress product. For
example, Progress,ORACLE.
DBTYPE Returns the database type of a currently connected
database. For example Progress, ORACLE, etc.
DBRESTRICTIONS Returns a character string that describes the Progress
features that are not supported for a particular database.
For example, if the database is an ORACLE database,
the return string is:
LAST,PREV,RECID,SETUSERID.
DBVERSION Returns a 7 if a connected database is a Version 7
database and an 8 if it is a Version 8 database. For
non-Progress databases, you see the appropriate version
number of your database.
FRAMEDB Returns a character string that contains the logical name
of the database for the field in which the cursor was last
positioned for input.
NUMDBS Returns the number of connected databases.
LDBNAME Returns the logical name of a currently connected
database.
PDBNAME Returns the physical name of a currently connected
database.
SDBNAME Returns the logical name of a schema holder for a
database.
Database Access
99
Use these functions to perform various tasks related to connection, such as determining
connection status. The following procedure displays a status report for all connected databases:
9.1.8 Conserving Connections vs. Minimizing Overhead
As more and more users connect to a database, the number of available connections decreases.
You can conserve the number of connections by connecting temporarily, and then
disconnecting when the application is done accessing the database. This frees up the connection
for another user. If the number of available connections is scarce, an application should release
connections wherever possible.
However, each connect and disconnect generates connection overhead, which is the sum of
operations necessary for an application to connect and disconnect a database. The time used for
connection overhead depends on the nature of the database connections. A connection to a
database over a network generally takes longer than a connection to a local database. While the
application connects to a database, the end user waits.
When there are plenty of available connections, you might want to reduce the number of
connects and disconnects to minimize overhead. The best way to do this is to connect all
application databases only once at application startup. With this technique, all connection
overhead occurs once at application startup and does not occur again throughout the life of the
application.
p-infor.p
DEFINE VARIABLE x AS INTEGER FORMAT "99".
DO x = 1 TO NUM-DBS WITH DOWN:
DISPLAY PDBNAME(x) LABEL "Physical Database"
LDBNAME(x) LABEL "Logical Name"
DBTYPE(x) LABEL "Database Type"
DBRESTRICTIONS(x) LABEL "Restrictions"
SDBNAME(LDBNAME(x)) LABEL "Schema Holder DB".
END.
Progress Programming Handbook
910
9.2 Disconnecting Databases
By default, Progress disconnects all databases at the end of a session. You can explicitly
disconnect a database with the DISCONNECT statement, which has the following syntax:
The logicaldatabasename represents the logical name of a connected database. It can be an
unquoted string, a quoted string, or a character expression.
A DISCONNECT statement does not execute until all active procedures that reference the
database end or stop.
In these procedures, the mydb database is not disconnected until the end of the p-subproc1.p
procedure.
SYNTAX
DISCONNECT logical-database-name
mainprc1.p
CONNECT -db mydb -1.
RUN p-subproc1.p.
subproc1.p
RUN subproc2.p.
FOR EACH mydb.customer:
UPDATE name address city state postal-code.
END.
subproc2.p
DISCONNECT mydb.
Database Access
911
9.3 Logical Database Names
When you connect to a database, Progress automatically assigns that database a default logical
name for the current Progress session. The default logical name consists of the physical database
name without the .db file extension. For example, if you connect to a database with the physical
name mydb1.db, the default logical database name is mydb1. Progress uses the logical database
name mydb1 to resolve database references, and stores it in the compiled r-code of any
procedures that you compile that reference the mydb1.db database.
The Logical Database Name (ld) connection parameter allows you to specify a logical database
name other than the default.
The example below establishes the logical name firstdb for the physical database mydb1.db
during the current Progress session:
When you develop and compile an application to run on the mydb1.db database, it is the logical
name, not the physical name, that Progress stores in the r-code. You must use the logical name
firstdb in your procedures (.p) to reference the mydb1.db database.
Logical database names allow you to change physical databases without recompiling an
application. To run a compiled application on a new physical database without recompiling, the
new database must have identical structure and time stamp or Cyclic Redundancy Check (CRC)
values for the tables accessed by the application and must be connected with the same logical
name (or alias) used to compile the application:
The example above establishes the logical name firstdb for a new physical database mydb2.db.
NOTE: Progress does not allow you to run the Progress Data Administration tool or character
Data Dictionary against a database connected with the logical name DICTDB.
A database connection fails if the logical database name of the database that you connect to has
the same logical name as an already connected database of the same database type (Progress,
ORACLE, etc.). If you try to do this, Progress assumes that database is already connected and
ignores the request.
For information about the characters allowed in the logical name, see the Progress Startup
Command and Parameter Reference.
pro mydb1 -ld firstdb
pro mydb2 -ld firstdb
Progress Programming Handbook
912
9.4 Database Aliases
An alias is a synonym for a logical database name. You use an alias as a database reference in
Progress procedures in place of a logical database name.
You establish a logical database name when you connect a Progress session to a physical
database. You create and assign an alias to a logical database name of an already connected
database using the CREATE ALIAS statement. By reassigning an alias to different logical
database names, you can run a compiled procedure on other connected databases that have
identical structure and time stamps or CRC values for the tables referenced by the procedure. A
logical database name can have more than one alias, but each alias refers to only one logical
database name at a time.
The Progress Data Dictionary offers an example of alias usage. The Progress Data Dictionary
is a general-purpose Progress application that works on any database that has DICTDB as an
alias. The first database connected during a Progress session automatically receives the alias
DICTDB. During a Progress session, you can reassign the DICTDB alias to another connected
database with the Select Working Databases option on the Database menu in the Progress Data
Dictionary.
NOTE: Because of the need to reassign of the DICTDB alias, Progress does not allow you to
run the Progress Data Dictionary against a database connected with the logical name
DICTDB.
Use the CREATE ALIAS statement during a Progress session to assign or reassign an alias to
a connected database. You can use this statement from the Progress editor or from an
application procedure. This is the syntax for the CREATE ALIAS statement:
The alias argument can be an unquoted string, a quoted string, or an expression. The
logicalname argument represents the existing logical name of a connected database. It can be
an unquoted string, a quoted string, or an expression. You cannot create an alias that is the same
as the logical database name of a connected database. The named database must be connected
unless you use the NOERROR option.
SYNTAX
CREATE ALIAS alias FOR DATABASE logical-name [ NO-ERROR ]
Database Access
913
When you create an alias, Progress logs the alias assignment in a table that remains in memory
for the current session. If you use the DISCONNECT statement to disconnect a database from
within an application, all existing aliases assigned to the logical database name remain in the
alias table until the end of the Progress session. Later, if you connect to a database with the same
logical database name during the same Progress session, you can use the same aliases to
reference that logical database name. If you create an alias that already exists in the session alias
table, Progress replaces the existing alias with the new alias. This allows you to reassign existing
aliases to new logical database names.
The DELETE ALIAS statement allows you to delete an alias from the alias table of the current
Progress session:
The alias argument represents an alias that exists in the current alias session table.
9.4.1 Creating Aliases in Applications
You cannot assign and reference an alias in the same procedure. You must assign an alias to a
logical database name prior to compiling and running procedures that use that alias. For
example, alias1.p below fails to compile when it reaches the FOR EACH statement, because
you cannot assign and reference the alias myalias in a single procedure:
SYNTAX
DELETE ALIAS alias
alias1.p
/* Note that this procedure does not work */
CREATE ALIAS myalias FOR DATABASE sports.
FOR EACH myalias.customer:
DISPLAY customer.
END.
Progress Programming Handbook
914
To solve this problem, split alias1.p into two procedures, as in the following examples:
9.4.2 Compiling Procedures with Aliases
As a general rule, you should not compile procedures while using aliases. This potentially leads
to confusion about what database name ends up in the r-code. For example, the Progress
dictionary programs which contain DICTDB as a qualifier must be compiled in a session where
the database concerned has the logical name DICTDB. That way, when the dictionary r-code is
run, it can run for any database name as long as the database uses the DICTDB alias. In
summary, logical names are useful for compiling, aliases are useful at run time.
However, you can still compile procedures using aliases. Suppose you have three databases
called eastdb, centraldb, and westdb, all of which contain customer tables with identical
structure and time stamps or CRC values. Your application requires a general report procedure
that can run against any of these customer tables. To begin developing your general report
procedure, start Progress and connect to the eastdb, centraldb, or westdb database using the
logical name myalias. Develop and compile the customer report procedure using myalias to
prefix table and field references as shown in the previous procedure dispcust.p. All
unqualified table and field references in the report procedure dispcust.p resolve to the myalias
logical name at compilation time. When you finish compiling your procedure, disconnect from
the database represented by the myalias logical database name.
You must assign an alias to the logical database name of a connected database prior to running
any procedure that uses that alias as a database reference. Therefore, you need to develop a
procedure that uses the CREATE ALIAS statement to assign the myalias alias to a logical
database name and run the report procedure (dispcust.p) as a subprocedure. See the alias2.p
procedure above.
alias2.p
CREATE ALIAS myalias FOR DATABASE sports.
RUN dispcust.p.
dispcust.p
FOR EACH myalias.customer: /* myalias.customer */
DISPLAY customer. /* myalias.customer */
END.
Database Access
915
Although it is not recommended because of the possible confusion it can cause, you may need
to compile a procedure with an alias. To do this, you must know how Progress places database
references in the r-code of the procedure at compilation time. Remember, you must assign the
alias to a logical database name prior to compiling the procedure. In general, use only the alias
as a database prefix for all table and field references in the general-purpose procedures, and
always fully qualify every database reference within such a procedure. Use the following
syntax:
If you use this syntax for every table or field reference in your procedure, only the alias will be
represented as the database reference in the procedures r-code after compilation. Note that this
is the only exception to the rule that you should never compile using aliases.
Unqualified table and field references within procedures may cause both the alias and the
logical database name for a particular physical database to be represented in the r-code for the
procedure at compilation time.
SYNTAX
alias.table-name alias.table-name.field-name
Progress Programming Handbook
916
9.4.3 Using Shared Record Buffers with Aliases
Be careful when using shared buffers with aliases. If you reference a shared buffer after
changing the alias that initially was used in defining it, a run-time error results:
In this example, procedure main2.p calls makebuf.p, which in turn calls disp.p. The alias
myalias is created in main.p, with reference to database sports1. In makebuf.p, the shared buffer
mybuf is defined for the table myalias.customer. Then, in the next line, myalias is changed, so
that it now refers to database sports2. When an attempt is made to reference shared buffer mybuf
in procedure disp.p, a run-time error occurs, with the message disp.p unable to find shared
buffer for mybuf.
9.5 Data-handling Statements
Statements that move data from one location to another are called data-handling statements.
Progress stores data in various locationsa database, a record buffer, a screen buffer, etc. A
database stores application data on disk. A record buffer stores data temporarily while a
procedure accesses the data, and stores the values of variables used in the procedure. A screen
buffer stores data being displayed on the screen or being sent to another output destination; it
also stores data that is being entered from the terminal or other input source.
main2.p
CREATE ALIAS myalias FOR DATABASE sports1.
RUN makebuf.p.
makebuf.p
DEFINE NEW SHARED BUFFER mybuf FOR myalias.customer.
CREATE ALIAS myalias FOR DATABASE sports2.
RUN disp.p
disp.p
DEFINE SHARED BUFFER mybuf FOR myalias.customer.
FOR EACH mybuf:
DISPLAY mybuf.
END.
Database Access
917
Figure 91 shows how the data-handling statements move data.
Figure 91: Data Movement
To use Figure 91, read each statement from top to bottom. The beginning of the first arrow (the
dot) indicates the source of the data. The arrowhead of the last arrow indicates where the data
is finally stored. For example, the DISPLAY statement gets data from a record buffer and moves
it into a screen buffer. Once in the screen buffer, the data is displayed on screen. Also, note that
INSERT creates a new database record, moves it to the record buffer, and then to the screen
buffer where the user enters data, which is then moved back to the record buffer.
ASSIGN
CREATE
DELETE
DISPLAY
ENABLE
FIND
FOR EACH
GET
INSERT
RELEASE
SET
UPDATE
Statement
Database
Record
Record
Buffer
Screen
Buffer User
OPEN QUERY
(with BROWSE)
PROMPT-FOR
REPOSITION
(with BROWSE)
Progress Programming Handbook
918
Some statements are made up of other statements. For example, the UPDATE statement is made
up of the DISPLAY, PROMPTFOR, and ASSIGN statements, and performs the same steps as
these other statements. First, it copies data from the record buffer to the screen buffer
(DISPLAY). Second, it allows data to be entered into the screen buffer (PROMPTFOR).
Third, it copies the data back to the record buffer (ASSIGN).
You can use statements as building blocks, using only as many as you need to do specific tasks.
For example, the INSERT statement is very powerful and combines several processing steps
into one statement. But in some situations, it is less flexible than using the CREATE, DISPLAY,
and SET statements individually (or the CREATE and UPDATE statements).
Figure 92 shows the primary data-handling statements and shows which statements are
composed of other statements.
Figure 92: The Primary Data-handling Statements
Note that the UPDATE, SET, and ASSIGN statements do not actually write records to the
database. However, at the end of a transaction (or at the end of the record scope or after an
explicit RELEASE), Progress writes all modified records to the database.
If you modify a record using INSERT, UPDATE, or SET, Progress assigns the change to the
record buffer (and, hence, eventually to the database). However, if you use the ENABLE
statement, you must explicitly assign any changes with the ASSIGN statement.
For more information on the statements in Figure 92, see the Progress Language Reference.
UPDATE
SET
PROMPT-FOR
DISABLE
VIEW
DISPLAY
ASSIGN
WAIT-FOR ENABLE
Copy to
Screen Buffer
Refresh Assign Disable Wait Activate Top
Database Access
919
9.6 Adding and Deleting Records
To add records to a database, you can use either the CREATE or INSERT statements. The
CREATE statement places a newly created record in the database, but does not display the
record or request user input. All fields in the record are set to the initial values specified in the
Data Dictionary. The CREATE statement causes any CREATE trigger associated with the table
to execute. This trigger may set fields in the record to new values. The INSERT statement not
only creates the record, but also displays the record and requests input.
The INSERT statement is composed of four other statementsthe CREATE, DISPLAY,
PROMPTFOR and ASSIGN statements. Three of these statements (DISPLAY,
PROMPTFOR, and ASSIGN) comprise the UPDATE statement. You can emulate an INSERT
statement by using the four statements, or by using the CREATE and UPDATE statements. This
is more flexible than using the INSERT statement alone.
For example, the INSERT statement always displays fields in the order they are defined in the
schema. The UPDATE statement lets you specify the order they are displayed. Note that if you
use an INSERT statement, the CREATE trigger is executed before the record is displayed.
To delete records from a database, use the DELETE statement. The DELETE statement causes
any DELETE trigger associated with the table to execute. For more information on database
triggers, see Chapter 11, Database Triggers.
9.7 Defining a Set of Records to Fetch
To fetch records, you must first define the set of records that you want Progress to fetch. For
example, the following statement defines the set of all customer records:
Progress allows you to define a set of records in a variety of ways. You define the set of records
in a Record phrase. For more information on the syntax of Record phrases, see the Progress
Language Reference. You can specify a Record phrase for the following Progress statements:
FIND
FOR [ EACH ]
OPEN QUERY
DO PRESELECT
REPEAT PRESELECT
FOR EACH Customer:
Progress Programming Handbook
920
The examples given below illustrate some of the flexibility that you have when defining a set
of records. The Record phrases are highlighted. You can build complex Record phrases using
the AND and OR operands.
This example defines a set of one record (customer 11):
This example uses the word-indexed field Comments to define a set of records (all customer
records containing the word ship):
This example creates the subset of all Order records with the Customer number = 11:
This example defines a set of customer records (those between 25 and 50) to be preselected:
FIND Customer WHERE cust-num = 11.
FOR EACH Customer WHERE Comments CONTAINS "ship":
OPEN QUERY ordqry FOR EACH Order WHERE Cust-Order = 11.
REPEAT PRESELECT EACH Customer WHERE cust-num > 25 AND cust-num < 50:
Database Access
921
9.8 Fetching Records
After you define a set of records, Progress must fetch the records. How Progress fetches records
depends in part on which statements you use to fetch the records. Table 93 summarizes the
differences in record fetching between the FIND, FOR EACH, OPEN QUERY, and
PRESELECT statements.
9.8.1 Order of Fetched Records
The FOR EACH statement, OPEN QUERY statement, and PRESELECT option may use
multiple indexes to satisfy a query. When multiple indexes are used, the order of returned
records is not guaranteed. You can enforce an order by using the BY option.
The following example returns the selected customer records in ascending creditlimit order
and within credit limit in name order:
Table 93: Record Fetching
Statement
to Define Set of
Records
Statement
to Fetch
the Records
Structure Used
to Locate Records
FIND FIND Index
FOR EACH FOR EACH Results List or Index
OPEN QUERY GET Results List or Index
DO PRESELECT or
REPEAT PRESELECT
FIND Results List
FOR EACH customer BY Credit-limit BY Name:
Progress Programming Handbook
922
9.8.2 Sample Record Fetches
The following examples show some of the many ways you can access records. These examples
are not meant to be exhaustive, but merely to show some of the flexibility provided by Progress.
Specify the FIRST, LAST, NEXT, or PREV options to step through all records in a
particular sequence:
Specify boolean expressions to describe the record or records to be fetched:
Specify a constant value of the primary index for the record. This works only if the primary
index is single-component. Also, this technique is not supported for the OPEN QUERY
statement:
Specify one or more field values that are currently in the screen buffer or record buffer:
Specify a previously found related record from another table (The two tables must share a
field with the same name. That field must have a UNIQUE index in at least one of the
tables.):
FIND FIRST Customer.
FOR EACH Customer WHERE Credit-Limit > 5000 AND Balance < 12000:
FIND Item 12.
PROMPT-FOR Customer.Cust-num WITH FRAME abc.
FIND Customer USING FRAME abc Customer.Cust-num.
DISPLAY Customer.Name.
FIND FIRST Customer.
FOR EACH Order OF Customer: /* This uses the cust-num field */
Database Access
923
Specify a CONTAINS clause on a word-indexed field:
You cannot use a CONTAINS clause with the FIND statement. You can use CONTAINS
only with the OPEN QUERY and FOR EACH statements.
9.8.3 ROWID and RECID Data Types
Progress provides two structure types to support record fetches. One structure type, the index,
you define in the schema of your database. The other structure type, a results list, is temporary;
Progress builds it at run time. The results list associated with a DO, REPEAT, or OPEN
QUERY statement with the PRESELECT option is sometimes called a preselect list.
In addition, there are two data types, ROWID and RECID, that allow you to retrieve a pointer
to a fetched record. You can use this pointer to:
Position to and retrieve a record from a results list.
Refetch a record and modify its lock status.
Store as a future record reference.
In addition to the examples in this section, you can see ROWID at work in the chapters that
describe the following Progress features:
Browse interactions Chapter 10, Using the Browse Widget.
Transactions and updates Chapter 12, Transactions.
General lock management Chapter 13, Locks.
ROWID versus RECID
ROWID is supported by all DataServers. Earlier Progress versions provide RECID as the only
way to fetch a record pointer (supported in this version for backward compatibility). RECID is
limited to a 4-byte record address supported by only a few DataServers and standard Progress.
ROWID provides a variable byte string that can represent a record address for any type of
DataServer. For DataServers that use the 4-byte address supported by RECID (including
Progress), ROWID also uses a four-4 value. Thus, there is no loss in performance using the
more portable ROWID instead of RECID.
FOR EACH Customer WHERE Comments CONTAINS "ship":
Progress Programming Handbook
924
Returning Record ROWID Values
Progress provides a function named after the ROWID data type to return ROWID values. Given
a buffer name, the ROWID function returns the ROWID of the current record in the buffer. This
example fetches the first customer record, and if it has a balance, refetches it to lock it for
update:
Storing and Retrieving ROWID and RECID Values
As shown in the previous example, you can store ROWID values in ROWID variables. You can
also store them in work table fields. Thus, the following are valid ROWID storage definitions:
You cannot store ROWID values in database or temporary tables, but you can store their
hexadecimal string representations using the STRING function. You can then retrieve the string
as a ROWID value using the TOROWID function:
You can store RECID values directly in a database or temporary table.
DEFINE VARIABLE custrid AS ROWID.
FIND FIRST customer NO-LOCK.
custrid = ROWID(customer).
IF balance > 0 THEN DO:
FIND customer WHERE ROWID(customer) = custrid EXCLUSIVE-LOCK.
UPDATE customer.
END.
DEFINE VARIABLE wkrid AS ROWID EXTENT 20.
DEFINE WORK-TABLE wtrid FIELD wkrid AS ROWID.
DEFINE TEMP-TABLE ttrid
FIELD ridfld AS CHARACTER.
FOR EACH customer FIELDS (balance) WHERE balance = 0 NO-LOCK:
CREATE ttrid.
ASSIGN ttrid.ridfld = STRING(ROWID(customer)).
END.
DO TRANSACTION:
FOR EACH ttrid:
FIND customer WHERE ROWID(customer) = TO-ROWID(ttrid.ridfld).
DELETE customer.
END.
END.
Database Access
925
Additional 4GL Support
Several additional statements use ROWID and RECID values directly. For example, the
REPOSITION statement sets the query position to a record given by its ROWID or RECID. For
more information, see the Results Lists section.
Also, because RECID is not supported by all DataServers, Progress provides the
DBRESTRICTIONS function to indicate whether a particular DataServer supports it.
Converting from RECID to ROWID
When changing an application to use ROWID that currently uses RECID, you can complete the
change with only a keyword substitution if your application:
Does not reference RECID values as integers
Does not store RECID values in database or temporary tables
Otherwise, after you change all RECID references to ROWID, you must rewrite your
integer references to use character strings. If you use database or temporary tables, you must
also convert the relevant fields to CHARACTER fields, and use the STRING and TOROWID
functions to store and retrieve ROWID values. However, note that some DataServers build a
string for a single ROWID that can reach up to several hundred bytes (including a complete
WHERE clause).
All DataServer tables support ROWID references except those, such as views, that do not have
unique row identifiers. DataServers from earlier Progress versions also support ROWID
references. Versions 7.3A and later use an internal RECID that transparently converts to a
ROWID in the client.
Writing DataServer-portable Applications
The least portable feature of ROWID references is the scope of a ROWID reference and when
it changes for each DataServer. To maximize portability, follow these rules:
Always assign values to unique keys before returning the ROWID value of a record. Some
DataServers use a unique key to generate the ROWID value.
If you UNDO a DELETE of a record for which you have stored the ROWID value, return
the ROWID value again after the UNDO. It might be different after the record is recreated.
If you update a unique key value for a record, return its ROWID again to replace any prior
value.
Never expect a record to have the same ROWID value between sessions.
Progress Programming Handbook
926
Note that each DataServer uses a different method to generate a ROWID for a table, and
sometimes for different tables in the same database. Therefore, never expect ROWID values to
be identical, or even compatible, between otherwise duplicate tables from different
DataServers.
For more information on ROWID value construction and scope for your DataServer, see your
Progress DataServer guide.
9.8.4 Results Lists
A results list is a list of ROWIDs that satisfy a query. The results list allows you to quickly
access the records in the record set you define, and allows Progress to make the records
available one at a time, as needed.
When more than one row satisfies a query, Progress doesnt lock all of the records and hold
them until you release them. Instead, when a specific record is needed, Progress uses the
records ID to go directly to the record, locking only that record. By going directly to the record,
Progress also ensures that you have the latest copy of the record.
When you open a query, Progress does not normally build the entire results list. Instead it
initializes the results list and adds to it as needed. The NUMRESULTS function returns the
number of records currently in the results list. This is not necessarily the total number of records
that satisfy the query.
Whether and when Progress builds the results list depends on the type of the query. As shown
in Table 93, the DO or REPEAT PRESELECT statements always use a results list, while the
FOE EACH and OPEN QUERY statements sometimes use a results list.
Queries have the following characteristics:
Scrolling versus non-scrolling A query is scrolling if you specify SCROLLING in the
DEFINE QUERY statement or if you define a browse for the query. You can use the
REPOSITION statement to change your current position within the results list. For a
non-scrolling query, you can only move sequentially through the rows by using the FIRST,
LAST, NEXT, and PREV options of the GET statement. Scrolling queries must use a
results list (often initially empty); non-scrolling queries might not.
Index-sorted If the order of the rows in the query can be determined by using a single
index, the query is index-sorted. For an index-sorted query, Progress can use the index to
order records without a results list. However, if the query requires additional sorting it
must also be presorted.
Presorted If the query requires any sorting without an index or with multiple indexes,
it must be presorted. For a presorted query, Progress must read all the records, sort them,
and build the complete results list before any records are fetched.
Database Access
927
Preselected versus non-preselected You can force Progress to build a preselected
results list by specifying PRESELECT on the OPEN QUERY, DO, or REPEAT
statement.
Table 94 summarizes how the results list is built for each type of query.
There are two cases where Progress has to build the entire results list when you first open the
query:
When you have explicitly used the PRESELECT option. In this case, Progress performs a
preselection phase in which it reads each record of the query. You can specify the lock type
to use during the preselection phase. (For an OPEN QUERY, the lock type you specify in
the OPEN QUERY statement is used for the preselection phase.) These locks are released
immediately unless the preselection occurs within a transaction.
When you have not used the PRESELECT option, but have specified sort criteria that
cannot be performed using an index. In this case, Progress performs a presort phase in
which it reads each record of the query with NOLOCK and builds the results list.
For example, the following statement explicitly uses the PRESELECT option. This forces
Progress to build the entire results list immediately:
If you had used FOR instead of PRESELECT, Progress would not have had to build the entire
results list because it uses the primary index to fetch the records. It could use this index to find
the first or last record for the query; it only needs to search forward or backward through the
index until it finds a record that satisfies the WHERE clause.
Table 94: Results Lists for Specific Query Types
Query Type Results List
Non-scrolling, index-sorted, no preselection None.
Scrolling, no sorting, no preselection
Empty list
1
established when query is
opened. Records are added to the results
list as needed.
Presorted or preselected Complete list built when query is opened.
1
If a browse is defined for the query, the results list initially contains one row.
OPEN QUERY cust-query PRESELECT EACH customer WHERE credit-limit > 1500.
Progress Programming Handbook
928
You can use the PRESELECT option of the OPEN QUERY statement when you need to know
immediately how many records satisfy the query or you can use it to immediately lock all the
records that satisfy the query.
Progress also builds a complete results list when you open a query with a sort condition that
cannot be resolved using a single index. Suppose you open a query on the customer table as
follows:
Because there is no index for the city field, Progress must retrieve all the records that satisfy the
query (in this case, all the customer records), perform the sort, and build the entire results list
before any records can be fetched. Until it performs this sort, Progress cannot determine the first
or last record for the query. If an index were defined on the city field, Progress could use that
index to fetch the records in sorted order (forwards or backwards) and would not need to build
the results list in advance.
If the sort conditions for a query can be resolved using a single index, you can use the GET
statement with the FIRST, LAST, NEXT, and PREV options on that query. For example, the
following query is sorted using the primary index:
Because the sorting is done with a single index, you can move freely forwards and backwards
within the query.
NOTE: If you want to use the REPOSITION statement on a query, you must make the query
scrolling by specifying the SCROLLING option in a DEFINE QUERY statement.
OPEN QUERY cust-query FOR EACH customer BY city.
OPEN QUERY custqry FOR EACH customer.
GET FIRST custqry.
DISPLAY cust-num name. /* Display first record */
PAUSE.
GET NEXT custqry. /* Display second record */
DISPLAY cust-num name.
PAUSE.
GET LAST custqry. /* Display last record */
DISPLAY cust-num name.
PAUSE.
GET PREV custqry.
DISPLAY cust-num name. /* Display second-to-last record */
Database Access
929
Navigating a Results List
As shown in Table 93, results lists are associated with the OPEN QUERY and GET statements.
However, Progress only guarantees a results list if you first define the query with the
SCROLLING option:
This option indicates to Progress that you want to use the results list for multi-directional
navigation.
You can use the REPOSITION statement to specify how many places forward or backward you
want to move, so that you can skip over a given number records. It also allows you to move to
a specific ROWID.
The REPOSITION statement changes your location in the results list but does not actually fetch
the record (unless the query is associated with a browse widget). To actually fetch records in a
results list, you use the GET statement. The following example illustrates how the
REPOSITION statement works:
After a record is fetched (with a GET statement), the results list position is on the ROWID, so
that GET NEXT gets the next record, and GET PREV gets the previous record. After a
REPOSITION, the position is always between two records. Thus, REPOSITION
FORWARD 0 repositions the results list immediately after the current record. GET NEXT
fetches the next record; GET PREV fetches the previous record. REPOSITION FORWARD 1
repositions the results list between the next record and the record after it.
DEFINE QUERY custqry FOR customer SCROLLING.
DEFINE QUERY q FOR customer SCROLLING.
DEFINE VARIABLE rid AS ROWID. /* to save the ROWID of cust 4 */
OPEN QUERY q FOR EACH cust.
GET NEXT q. /* gets cust no. 1 */
GET NEXT q. /* gets cust no. 2 */
/* query is positioned ON cust 2 */
GET PREV q. /* gets cust no. 1 */
REPOSITION q FORWARD 0. /* query is positioned BETWEEN cust 1 and 2 */
GET NEXT q. /* gets cust no. 2 */
/* query is positioned ON cust 2 */
REPOSITION q FORWARD 1. /* query is positioned BETWEEN cust 3 and 4 */
GET NEXT q. /* gets cust no. 4 */
rid = ROWID(cust). /* query is positioned ON cust 4 */
REPOSITION q BACKWARD 2. /* query is positioned BETWEEN cust 2 and 3 */
GET PREV q. /* gets cust no. 2 */
REPOSITION q TO ROWID(rid). /* query is positioned BETWEEN cust 3 and 4 */ GET
NEXT q. /* gets cust no. 4 */
Progress Programming Handbook
930
To find the total number of rows in a results list, you can use the NUMRESULTS function. To
find the current position within a results list, you can use the CURRENTRESULTROW
function.
As Table 93 shows, Progress also creates results lists for FOR EACH statements and for DO
and REPEAT statements with the PRESELECT phrase. However, you cannot navigate freely
through a results list created for the FOR EACH statement. If the results list was created for the
FOR EACH statement, then Progress automatically steps through the results list in sorted order:
Within a PRESELECT block, you can use the FIND statement to move backwards and forwards
through the results list:
NOTE: A powerful way of navigating a record set is with the browse widget. For more
information, see Chapter 10, Using the Browse Widget.
FOR EACH customer BY name:
DISPLAY name city state.
END.
/* This code fragment displays all customers in descending order */
DO PRESELECT EACH customer:
FIND LAST customer. /* last position in list */
DISPLAY cust-num name WITH FRAME a DOWN.
REPEAT:
FIND PREV customer. /* move backward through list */
DOWN WITH FRAME a.
DISPLAY cust-num name WITH FRAME a.
END.
END.
Database Access
931
9.8.5 FIND Repositioning
After executing FOR EACH statements or FIND statements, Progress might reposition
subsequent FIND statements to the last record fetched (except for FIND statements occurring
in PRESELECT blocks). For repositioning to occur, the same record buffer must be used. Also,
repositioning after FOR EACH statements can differ between Version 8.0B and Versions 8.0A
and earlier, depending on the options you use.
NOTE: Repositioning does not occur for a subsequent FIND if the FIND specifies a unique
key (that is, the FIND does not use the NEXT or PREV options).
Repositioning After FIND Fetches
Progress uses index cursors to keep track of what record you last fetched. This is important if
you use the FIND statement to fetch a record. For example, depending on what was the last
record fetched, the following statement returns a different record:
If you had last fetched the first customer record, this statement would fetch the second customer
record. If you had just fetched the fourth customer record, this statement would fetch the fifth
customer record.
A table can have multiple indexes, and the cursor position in each index dictates what the next
record is in that index. For example, the following code fragment fetches customers 1 and 21:
In the countrypost index, the next record after customer 1 is customer 21. Progress uses the
index cursor to establish the correct context.
Sometimes cursor repositioning is tricky. For example, the following code fragment returns
customer 6 and customer 7 (you might expect customer 6 and customer 2):
FIND NEXT customer.
FIND FIRST customer.
DISPLAY cust-num name country postal-code.
PAUSE.
FIND NEXT customer USE-INDEX country-post.
DISPLAY cust-num name country postal-code.
FIND FIRST customer WHERE cust-num > 5.
DISPLAY cust-num name.
PAUSE.
FIND NEXT customer WHERE cust-num > 1.
DISPLAY cust-num name.
Progress Programming Handbook
932
The first FIND statement causes Progress to reposition the custnum index cursor to point to
customer 6. The second FIND statement begins the search from that location, not from the
beginning of the custnum index.
9.9 Fetching Field Lists
When fetching records with a FOR EACH statement or query, Progress typically retrieves all
the fields of a record, whether or not your application needs them. This can have a costly impact
on performance, especially when browsing records over a network.
Progress automatically optimizes preselected and presorted fetches from remote Progress
databases using field lists. A field list is a subset of the fields that define a record and includes
those fields that the client actually requires from the database server. For preselected and
presorted fetches, Progress can deduce this field list at compile time from the code. You can also
specify field lists explicitly for many types of Progress record fetches, including:
Queries
FOR statements
DO PRESELECT statements
REPEAT PRESELECT statements
SQL SELECT statements
This section explains how to use field lists in the Progress 4GL. For information on specifying
field lists in SQL SELECT statements, see the Progress SQL-89 Guide and Reference.
9.9.1 Field List Benefits
Field lists can provide significant performance benefits when:
Browsing over a network With the reduction in network traffic, tests show that field
lists can increase the performance of fetches from both remote Progress and DataServer
databases by factors of from 2 to 10, depending on the record size and number of fields in
the list.
Fetching from local DataServers Some local DataServers can yield significant
improvements in fetch performance when only a portion of the record is read.
Fetching from remote DataServers that send multiple rows at a time DataServers
that package multiple rows per network message show noticeable performance gains using
field list fetches.
Database Access
933
In general, these benefits mean that a multi-user query application can handle more network
clients when fetching field lists than when fetching whole records. For more information on the
availability and benefits of field list fetches with DataServers, see the Progress DataServer
guides.
9.9.2 Specifying Field Lists in the 4GL
You can specify a field list in two different 4GL contexts:
Following each buffer name specified in a DEFINE QUERY statement
Following the buffer name specified for fetching in each Record phrase of a FOR, DO
PRESELECT, or REPEAT PRESELECT statement
This is the syntax for specifying a field list in all cases:
The recordbufname reference specifies the table buffer you are using for the fetch, and a field
reference specifies a field in the table. If field is an array reference, the whole array is fetched.
The FIELDS form lists the fields included in the fetch, and the EXCEPT form lists the fields
excluded from the fetch. FIELDS without field references fetches enough information to return
the ROWID of a record, and EXCEPT without field references or recordbufname alone fetches
a complete record.
Queries versus Record Selection Blocks
For a query, you must specify the field lists in the DEFINE QUERY statement, not the Record
phrase of the OPEN QUERY statement. Thus, the following two procedures, p-fldls1.p and
p-fldls2.p, are functionally equivalent:
SYNTAX
record-bufname
[ FIELDS [ ( [ field ... ] ) ]
| EXCEPT [ ( [ field ... ] ) ]
]
Progress Programming Handbook
934
Shared Queries
If you specify field lists in a NEW SHARED query, the matching SHARED query definitions
in external procedures only have to include the FIELDS or EXCEPT keyword as a minimum
field list reference. The complete field list is optional in the SHARED query definitions, but
required in the NEW SHARED query definition.
However, Progress raises the ERROR condition when you run a procedure with a SHARED
query if you:
Specify a field list in the SHARED query to match a NEW SHARED query that has no
field list.
Do not specify a field list reference in the SHARED query to match a NEW SHARED
query that has a field list.
p-fldls1.p
DEFINE QUERY custq FOR customer FIELDS (name cust-num balance).
OPEN QUERY custq PRESELECT EACH customer.
REPEAT:
GET NEXT custq.
IF AVAILABLE(customer)
THEN DISPLAY name cust-num balance.
ELSE LEAVE.
END.
p-rldls2.p
REPEAT PRESELECT EACH customer FIELDS (name cust-num balance):
FIND NEXT customer.
DISPLAY name cust-num balance.
END.
Database Access
935
9.9.3 Avoiding Implied Field List Entries
Under certain conditions, Progress adds fields to a specified field list when they are required by
the client to complete the record selection. The most common case is when you specify a join
condition with the OF option and you do not include the join field in the field list.
In this case, Progress adds customer.custnum to the list because the client requires it to
complete the join between the customer and invoice tables.
However, never rely on implied entries in a field list to provide the fields you need. If you
reference an unfetched field, Progress raises the ERROR condition at run time. Future versions
of Progress might change the criteria used to distribute record selection between the client and
server. Thus in the previous example, if the server does not return customer.custnum to the
client to complete the join, the DISPLAY statement executes with ERROR. This might happen,
for example, if the DataServer you use or a future version of Progress actually completes the
specified join on the server.
Therefore, always specify all the fields you plan to reference in your field lists. There is no extra
cost for specifying a field that Progress can also add implicitly.
9.9.4 Updating and Deleting with Field Lists
After fetching a field list, if your procedure updates the fetched record, Progress always rereads
the complete record before completing the update. In fact, if you fetch a field list with
EXCLUSIVELOCK, Progress reads the complete record anyway. This is to ensure proper
operation of updates and the before-image (BI) file. (For information on BI files, see the
Progress Client Deployment Guide.)
Also, if you delete a record after fetching a field list for it, Progress rereads the complete record
for the following cases:
If you delete from a Progress database, Progress always rereads the complete record.
If you delete from a DataServer database, Progress rereads the complete record if the
delete occurs in a subtransaction, in order to create the local before-image (LBI) note. (For
information on LBI files, see the Progress Client Deployment Guide.)
FOR EACH customer FIELDS (name),
EACH invoice FIELDS (invoice-num amount) OF customer:
DISPLAY customer.name customer.cust-num
invoice.invoice-num invoice.amount.
Progress Programming Handbook
936
Thus, if you fetch with NOLOCK or SHARELOCK, avoid using field lists if you expect to
perform a high ratio of updates or deletes to fetches. For example, this is an inefficient
construction with a Progress database:
This procedure rereads the complete record for each field list that it fetches, and thus fetches
twice per record. Without the field list, the same procedure fetches only once per record.
Updating and Deleting with Query Field Lists
For queries, especially those attached to a browse widget, there is little concern about updates
and deletes, because the complete results list for a query is built before any updates or deletes
take place. In this case, updates and deletes are selective over the entire query. Therefore, field
lists can greatly enhance query performance, no matter how many updates or deletes a browse
user completes. For more information on browse widgets, see Chapter 10, Using the Browse
Widget.
9.9.5 Cursor Repositioning and Field Lists
FOR and relative FIND statements reposition all open index cursors for the same buffer. (For
more information, see the FIND Repositioning section.) However, in order to reposition a
buffers index cursors, Progress must have all the index fields available in the buffer. If you
fetch a field list that excludes some of these fields, Progress marks the relevant indexes as being
incorrectly positioned.
This does not matter for the query or fetch loop that uses the field list, because Progress might
never reference the relevant indexes. However, if you later execute a FIND NEXT or FIND
PREV using one of the badly positioned indexes, Progress raises the ERROR condition and the
FIND fails.
To avoid this error, always specify the fields in your field lists that participate in the indexes you
reference.
FOR EACH customer FIELDS (name balance):
DISPLAY "Deleting customer" name "with balance:" balance.
DELETE customer.
END.
Database Access
937
9.9.6 Field List Handling in Degenerate Cases
When you specify field lists, Progress is very flexible where it cannot make use of them.
Progress either returns complete records automatically or allows you to bypass field list
processing completely for the following cases:
Progress server versions Earlier database servers interpret field list requests as
requests for complete records. Progress sends queries in such a way that earlier servers do
not need to know that the field list requests are included.
DataServers that do not support SHARELOCK If you execute a SHARELOCK
fetch from a DataServer that does not support SHARELOCK, Progress ignores the field
list and retrieves the complete record. In some circumstances, Progress must replace the
fetched record with a new version. Because the SHARELOCK is meaningless, the new
version can be different from the previous one, and Progress requires the complete record
to ensure that the user receives the correct data.
NOTE: You can avoid SHARELOCK fetches with the help of the
CURRENTCHANGED function. For more information, see Chapter 13,
Locks.
Multiple queries returning the same record If you specify two queries that return
two different field lists for the same record, Progress cannot always consolidate the two
lists. In that case, Progress must reread the complete record. Such occurrences are rare, but
they can impose a minor performance penalty with little impact on overall performance.
Deployment problems While programmers must ensure that their field lists are
complete, run-time errors can still occur during application deployment. This is especially
likely when a new database (schema) trigger is defined that references an unfetched field.
To work around this type of problem, Progress provides the Field List Disable (fldisable)
client session parameter. This is a run-time parameter that causes Progress to ignore field
lists in the r-code and fetch complete records. This might degrade performance, but allows
the application to run until a fix can be made.
Thus, while using field lists can lead to difficulties, Progress provides a way around most of
them. And when used optimally, the performance gains can make field lists well worth the extra
attention they might require.
Progress Programming Handbook
938
9.10 Joining Tables
When you read from multiple tables using a single statement, such as a FOR EACH or OPEN
QUERY statement, Progress returns the results as a join of the tables. A join is a binary
operation that selects and combines the records from multiple tables so that each result in the
results list contains a single record from each table. That is, a single join operation combines the
records of one table with those of another table or combines the records of one table with the
results of a previous join. Figure 93 shows how you can join three tables.
FOR EACH Table1, EACH Table2 WHERE C11 = C21, EACH Table3 WHERE C22 = C31:
DISPLAY C11 C12 C21 C22 C31 C32 WITH TITLE "Join123".
Database Access
939
Figure 93: Inner Joins
A table or prior join can be either on the left- or right-hand side of a join operation. Thus, the
results of joining the three tables in Figure 93 depends on two join operationsone join
between Table1 (left-hand side) and Table2 (right-hand side) and one join between the first join
(left-hand side) and Table3 (right-hand side). The relations C11 = C21 and C22 = C31 represent
join conditions, conditions that determine how one table is related to the other (that is, which
records selected from one table join with the records in the other table). How the records from
joined tables are combined depends on the order of the tables in the join, the type of join
operation used, and the selection criteria applied to each table.
Table2
C21 C22
1 6
2 2
2 5
3 9
Table3
C31 C32
1 3
5 2
6 1
8 7
Table1
C11 C12
1 2
2 4
5 7
6 9
Join12
C11 C12
1 2
2 4
2 4
C21 C22
1 6
2 2
2 5
Join123
C31 C32
6
1
2
5
C11 C12
1 2
2 4
C21 C22
1 6
2 5
Join
Where
C11 = C21
Join
Where
C22 = C31
Progress Programming Handbook
940
9.10.1 Specifying Joins in the 4GL
Progress supports two types of joins in the 4GL:
Inner join Supported in all statements capable of reading multiple tables, including the
FOR, DO, REPEAT, and OPEN QUERY statements. An inner join returns the records
selected for the table (or join) on the left side combined with the related records selected
from the table on the right. For any records not selected from the right-hand table, the join
returns no records from either the left or right sides of the join. Thus, only related records
that are selected from both sides are returned for an inner join. Table 93 shows an
example of inner joins.
Left outer join Supported only in the OPEN QUERY statement. A left outer join
returns the records selected for an inner join. In addition, for each set of records selected
from the table (or join) on the left side, a left outer join returns unknown values (?) from
the table on the right where there is no record selected or otherwise related to the records
on the left. That is, records from the left-hand table (or join) are preserved for all
unmatched records in the right-hand table. Figure 94 shows an example of left outer joins
using the same tables as in Figure 93.
NOTE: For versions earlier than V8, Progress supports only the inner join.
Specifying the Type of Join
The Record phrase that specifies the right-hand table of a join also indicates the type of join
operation. A Record phrase specifies an inner join by default. To specify a left outer join, you
include the [LEFT] OUTERJOIN option anywhere in the Record phrase. Where you specify
a list of multiple Record phrases in a record-reading statement, the join logic allows you to
specify only one set of contiguous inner joins at the beginning (left side) of the list and one set
of contiguous left outer joins at the end (right side) of the list. Each right-hand Record phrase
of a left outer join must contain the OUTERJOIN option up to and including the last left outer
join in the list. For more information, see the Mixing Inner and Left Outer Joins section.
Database Access
941
Relating and Selecting Tables
You can specify join conditions (table relations) using the OF option or WHERE option of the
Record phrase that specifies the join. The OF option specifies an implicit join condition based
on one or more common field names in the specified tables. The common field names must
participate in a unique index for at least one of the tables. The WHERE option can specify an
explicit join based on any field relations you choose, and you can use this option further to
specify selection criteria for each table in the join. For an inner join, if you do not use either
option, Progress returns a join of all records in the specified tables. For a left outer join, you
must relate tables and select records using the OF option, the WHERE option, or both options.
NOTE: Work tables and temporary tables can also participate in joins. However, work tables
do not have indexes. So, if you specify join conditions using the OF option with a
work table, the other table in the join must be a database or temporary table with a
unique index for the fields in common. For more information on work tables and
temporary tables, see Chapter 15, Work Tables and Temporary Tables.
The following code fragment generates the left outer joins shown in Figure 94. Note that the
Record phrase for the right-hand table of each join specifies the OUTERJOIN option. As
Figure 94 shows, the primary benefit of a left outer join is that it returns every record on the
left-hand side, whether or not related data exists on the right.
DEFINE QUERY q1 FOR Table1, Table2, Table3.
OPEN QUERY q1 FOR EACH Table1, EACH Table2 OUTER-JOIN WHERE C11 = C21,
EACH Table3 OUTER-JOIN WHERE C22 = C31.
GET FIRST q1.
DO WHILE AVAILABLE(Table1):
DISPLAY C11 C12 C21 C22 C31 C32 WITH TITLE "Join123".
GET NEXT q1.
END.
Progress Programming Handbook
942
Figure 94: Left Outer Joins
5 2
?
?
?
?
2 4
5 7
6 9
2 5
? ?
? ?
Join123
C31 C32
6
1
?
?
C11 C12
1 2
2 4
C21 C22
1 6
2 2
Table2
C21 C22
1 6
2 2
2 5
3 9
Left Outer
Join
Where
C11 = C21
Table1
C11 C12
1 2
2 4
6 9
Table3
C31 C32
1 3
5 2
6 1
8 7 5 7
Join12
C11 C12
1 2
2 4
2 4
C21 C22
1 6
2 2
2 5
6 9
? ?
? ?
Left Outer
Join
Where
C22 = C31
5 7
Database Access
943
Why Use Joins Instead of Nested Reads?
Using joins provides an opportunity for Progress to optimize the retrieval of records from
multiple related tables using the selection criteria you specify. When you perform a nested read,
for example using nested FOR EACH statements for different tables, you are actually
implementing a join in a 4GL procedure. However, by specifying one or more contiguous joins
in a single FOR EACH statement or in the PRESELECT phrase of single DO or REPEAT
statement, you minimize the complexity of your 4GL code and leave the complexity of joining
tables to the Progress interpreter.
For a single 4GL query (OPEN QUERY statement), there is no other way to retrieve data from
multiple tables except by using joins. With both inner and left outer join capability, you can use
the OPEN QUERY statement to implement most queries that are possible using nested FOR
EACH, DO, or REPEAT statements. As such, query joins provide the greatest opportunity for
optimized multi-table record retrieval in the 4GL. Also, because browse widgets read their data
from queries, you must use query joins to display multiple related tables in a browse. (For more
information on browsing records, see Chapter 10, Using the Browse Widget.)
However, use nested FOR EACH, DO, and REPEAT blocks wherever you require much finer
control over how you access and manipulate records from multiple tables.
9.10.2 Using Inner Joins
The inner join is the default join type in a multi-table read or query. Use this type of join where
you are only concerned with the data on the left side of the join for which there is related data
on the right. For example, you might want to see only those customers whose purchase of a
single item comes close to their credit limit.
The query in p-join1.p performs three inner joins on the sports database to return this data.
These three items correspond to the /*1*/, /*2*/, and /*3*/ that label the joins in the
p-join1.p example:
1. To each Customer, join all related Order records.
2. To each Order in the previous join, join each related OrderLine record whose total
purchase is greater than two-thirds the customers credit limit.
3. To each OrderLine in the previous join, join the related Item record to get the item name.
Progress Programming Handbook
944
When executed, you get the output shown in Figure 95. Thus, the relation of Order to Customer
and the selection criteria on OrderLine reduces the total number of query rows from thousands
of possible rows to four.
Figure 95: Inner Join Example
p-join1.p
DEFINE QUERY q1 FOR Customer, Order, Order-Line, Item.
DEFINE BROWSE b1 QUERY q1
DISPLAY Customer.Name Customer.Credit-Limit Order.Order-num
Item.Item-Name
WITH 10 DOWN.
OPEN QUERY q1 PRESELECT EACH Customer,
EACH Order OF Customer,
EACH Order-Line OF Order
WHERE (Order-Line.Price * Order-Line.Qty) >
(.667 * Customer.Credit-Limit),
EACH Item OF Order-Line.
ENABLE b1 WITH SIZE 68 BY 10.
WAIT-FOR WINDOW-CLOSE OF CURRENT-WINDOW.
Database Access
945
9.10.3 Using Left Outer Joins
A left outer join is useful where you want to see all the data on the left side, whether or not there
is related data on the right. For example, you might want to see the proportion of customers who
are ordering close to their credit limit as against those who are not.
The query in p-join2.p is identical to the one in p-join1.p (see the Using Inner Joins
section) except that all the joins are left outer joins instead of inner joins. Thus, you see all
customers, whether or not they order close to their credit limit. These three items correspond to
the /*1*/,/*2*/, and /*3*/ that label the joins in the p-join2.p example:
1. To each Customer, join all related Order records, or join a null Order record if the customer
has no orders.
2. To each Order in the previous join, join each related OrderLine record whose total
purchase is greater than twothirds the customers credit limit, or join a null OrderLine
record if all item purchases are less than or equal to twothirds the customers credit limit.
Also, join a null OrderLine record if the order is null (the customer has no orders).
3. To each OrderLine in the previous join, join the related Item record to get the item name,
or join a null Item record if the the OrderLine is null (no Order or selected purchase for
that customer).
When executed, you get the output shown in Figure 96. In this example, you see the same golf
club order as in Figure 95 along with many other orders that do not meet the selection criteria
and some customers who have no orders at all.
p-join2.p
DEFINE QUERY q1 FOR Customer, Order, Order-Line, Item.
DEFINE BROWSE b1 QUERY q1
DISPLAY Customer.Name Customer.Credit-Limit Order.Order-num
Item.Item-Name
WITH 10 DOWN.
OPEN QUERY q1 PRESELECT EACH Customer,
EACH Order OUTER-JOIN OF Customer,
EACH Order-Line OUTER-JOIN OF Order
WHERE (Order-Line.Price * Order-Line.Qty) >
(.667 * Customer.Credit-Limit),
EACH Item OUTER-JOIN OF Order-Line.
ENABLE b1 WITH SIZE 68 BY 10.
WAIT-FOR WINDOW-CLOSE OF CURRENT-WINDOW.
Progress Programming Handbook
946
Figure 96: Left Outer Join Example
9.10.4 Implementing Other Outer Joins
In addition to left outer joins, there are right and full outer joins. A right outer join reverses the
join order for the same tables joined with a left outer join. In the 4GL, you can implement a right
outer join by doing a left outer join with the tables in reverse order, but leaving the order of
displayed fields the same as for the left outer join. Thus, unknown values from the right side
appear on the left side of each displayed row, as if the tables were joined from right to left.
A full outer join combines the results of a left and right outer join into a single join. This is rarely
used, but you can implement a full outer join by building one temporary table from the results
of both a left and right outer join. For more information on temporary tables, see Chapter 15,
Work Tables and Temporary Tables.
Database Access
947
9.10.5 Mixing Inner and Left Outer Joins
You might want to mix inner and left outer joins in order to filter and reduce the amount of data
you need on the left side of your left outer joins. When mixing these two types of join, keep in
mind that the last inner join in a query forces the results of all prior joins in the query to be inner
joins. This is because any rows that contain unknown values from a prior left outer join are
eliminated by the following inner join. The effect is that the work of the prior left outer joins is
wasted, and the same result is achieved as with contiguous inner joins, but much less efficiently.
Therefore, in any query, keep your inner joins contiguous on the left with any left outer joins
contiguous on the right.
9.11 The CONTAINS Operator, Word Indexes, and Word-break Tables
You can use the CONTAINS operator of the WHERE option of the record phrase to query a
Progress database. For example, the following query displays each item record of the sports
database whose catdescription field contains the string hockey:
Using the CONTAINS operator involves word indexes and word-break tables. This section
explains why and tells you how to use word indexes and word-break tables. Specifically, the
section covers:
Understanding the CONTAINS operator, word indexes, and word-break tables
Using word indexes
Using word-break tables
Using the CONTAINS operator
Word indexing external documents
Word indexing non-Progress databases
NOTE: Word indexes and word-break tables have international implications. To understand
these implications, first read this section, then see the Progress Internationalization
Guide.
FOR EACH item WHERE cat-description CONTAINS "hockey":
DISPLAY item.
END.
Progress Programming Handbook
948
9.11.1 Understanding the CONTAINS Operator, Word Indexes,
and Word-break Tables
To process queries containing the CONTAINS operator, Progress uses word indexes. Similarly,
to create, maintain, and use word indexes, Progress uses word-break tables. Let us examine this
relationship chain one link at a time.
The CONTAINS Operator and Word Indexes
For Progress to process a query that uses the CONTAINS operator, the field mentioned in the
WHERE option must participate in a word index. For example, to process the following query:
Progress looks for the word indexes associated with the item record and its catdescription field.
If no such word indexes are found, Progress reports a run-time error. Otherwise, Progress uses
the word indexes to retrieve the item records whose catdescription field contains the string
"hockey."
Word Indexes and Word-break Tables
In order for Progress to use word indexes, it must first build and maintain them, which it does
as you add, delete, and modify records that have fields that participate in them. Consider the
sports databases item table, whose catdescription field participates in a word index. Every
time an item record is added, Progress examines the contents of the catdescription field, breaks
it down into individual words, and, for each individual word, creates or modifies a word index.
To break down the contents of a field into individual words, Progress must know which
characters act as word delimiters. To get this information, Progress consults the databases
word-break table, which lists characters and describes the word-delimiting properties of each.
9.11.2 Creating and Viewing Word Indexes
To define a new word index or to view the word indexes already defined on a database table,
use the Progress Data Dictionary utility. For example, you can use this utility to view the word
indexes of the sports database, including the word index associated with the item table and its
catdescription field. For more information on creating and viewing word indexes using the
Progress Data Dictionary utility, see the Progress Basic Development Tools manual or the
Progress Data Dictionary utility online help.
FOR EACH item WHERE cat-description CONTAINS "hockey":
DISPLAY item.
END.
Database Access
949
Word Indexing Very Large Fields
NOTE: If a word index is defined on a CHARACTER field that is extremely large, you might
have to increase the value of the Stash Area (stsh) startup parameter. Doing so
increases the amount of space Progress uses to temporarily storage of modified
indexes. For more information, see the reference entry for the Stash Area (stsh)
startup parameter in the Progress Startup Command and Parameter Reference.
Word Indexing Double-byte and Triple-byte Code Pages
You can use word indexing with double-byte and triple-byte code pages. For more information,
see the Progress Internationalization Guide.
9.11.3 Using Word-break Tables
You can create word-break tables that specify word separators using a rich set of criteria. To
specify and work with word-break tables involves:
Specifying word delimiter attributes
Understanding the syntax of word-break tables
Compiling word-break tables
Associating compiled word-break tables with databases
Rebuilding word indexes
Providing access to compiled word-break tables
Specifying Word Delimiter Attributes
As mentioned previously, to break down the contents of a word-indexed field into individual
words, Progress needs to know which characters delimit words and which do not. The
distinction can be subtle and sometimes depends on context. For example, consider the function
of the dot in the character strings in Table 95.
Progress Programming Handbook
950
In the first character string, the dot functions as a decimal point and does not divide one word
from another. Thus, you can query on the word $25,125.95. In the second character string, by
contrast, the dot functions as a period, dividing the word received from the word call.
To help define word delimiters systematically while allowing for contextual variation, Progress
provides eight word delimiter attributes, which you can use in word-break tables. The eight
word delimiter attributes appear in Table 96.
Table 95: Is the Dot a Word Delimiter?
Character String Function of the Dot
Is the Dot a Word
Delimiter?
Balance is $25,125.95 Decimal separator No
Shipment not
received.Call customs
broker
Period at end of sentence. Yes
Table 96: Word Delimiter Attributes (1 of 2)
Word Delimiter
Attribute
Description Default
LETTER Always part of a word. Assigned to all characters
that the current attribute
table defines as letters.
In English, these are the
uppercase characters AZ
and the lowercase characters
az.
DIGIT Always part of a word. Assigned to the characters
09.
Database Access
951
USE_IT Always part of a word. Assigned to the following
characters:
Dollar sign ($)
Percent sign (%)
Number sign (#)
At symbol (@)
Underline (_)
BEFORE_LETTER Part of a word only if
followed by a character with
the LETTER attribute.
Else, treated as a word
delimiter.
Frame 1 is an unnamed
frame. It is the default
frame scoped to the outer
FOR EACH block.
If you explicitly name a
default frame for this
block, you can access the
frame from the inner FOR
EACH block.
Frame 2 is an unnamed
frame. It is the default
frame scoped to the inner
FOR EACH block.
You cannot access this
frame from the outer FOR
EACH block.
Progress Programming Handbook
1916
19.6.1 Example Procedures
The following examples show different ways that Progress scopes frames.
In the procedure p-frm10.p, the scope of frame aaa is the procedure block because that is the
first block that references frame aaa. The scope of frame bbb is the REPEAT block. The
DISPLAY statement in the REPEAT block uses frame bbb because it is the default frame for
the REPEAT block. (The DISPLAY statement could override this by explicitly naming a frame:
DISPLAY WITH FRAME.)
In the procedure p-frm11.p, the scope of frame bbb is the procedure block because that is where
it is first referenced. The FORM statement counts as a use of a frame. The default frame of the
REPEAT block is bbb, since bbb is specified in the blocks header statement. However, frame
bbb is not scoped to the REPEAT block:
Progress can scope a frame to only one block. You cannot reference or use a frame outside its
scope except in HIDE and VIEW statements.
p-frm10.p
DISPLAY "Customer Display" WITH FRAME aaa.
REPEAT WITH FRAME bbb.
PROMPT-FOR customer.cust-num WITH FRAME aaa.
FIND customer USING cust-num.
DISPLAY customer WITH 2 COLUMNS.
END.
p-frm11.p
FORM WITH FRAME bbb.
DISPLAY "Customer Display" WITH FRAME aaa.
REPEAT WITH FRAME bbb:
PROMPT-FOR customer.cust-num WITH FRAME aaa.
FIND customer USING cust-num.
DISPLAY customer WITH 2 COLUMNS.
END.
Frames
1917
In the procedure p-frm12.p, the scope of frame a is the REPEAT block, since that is the first
block that references frame a. If you try to run this procedure, Progress displays as error saying
that you cannot reference a frame outside its scope. The FOR EACH block names frame a, but
the FOR EACH block is outside the scope of frame a. You could explicitly scope frame a to the
procedure and use it in both the REPEAT and FOR EACH blocks. To do this, you could add a
DO WITH FRAME block that encompasses both the REPEAT and FOR EACH blocks:
19.7 Triggers and Frames
User-interface and session triggers can reference frames in the procedure that defines them and
can use these frames to display widgets or character strings. In addition, triggers can use default
frames to display data (if no frames are named within the trigger), and can name frames that do
not exist within the defining procedure.
If a trigger displays data without specifying a frame, the trigger receives a default frame. Each
time the trigger executes, Progress creates a new default frame. The trigger does not reuse a
frame created during a previous execution.
If the trigger names a frame not named in the defining procedure, the new frame is created when
the trigger executes.
Whenever a trigger creates a frame, the frame is local to the trigger and its scope ends when the
trigger finishes executing. Each time the trigger executes, a new frame is created.
p-frm12.p
/* This procedure will not run */
REPEAT WITH FRAME a:
PROMPT-FOR customer.cust-num.
FIND customer USING cust-num.
DISPLAY name.
END.
FOR EACH customer WITH FRAME a:
DISPLAY name.
END.
Progress Programming Handbook
1918
19.8 Frame Flashing
A phenomenon called frame flashing occurs when an iterating block displays to a frame that is
scoped to an enclosing block. To see frame flashing, run this procedure:
Iterating blocks repeatedly display data to the screen. If the data is displayed to a down frame
that iterates and advances for each pass through the block, no frame flashing occurs. However,
since custframe is not scoped to the iterating block, Progress does not provide the appropriate
frame services. (For more information on frame services, see the Frame Services section.)
Therefore, each iteration of the block overwrites the data from the previous iteration, which
produces flashing.
You must take two steps to correct frame flashing. First, make the flashing frame a down frame
with the DOWN frame phrase (for more information on frame phrases, see the Frame phrase
reference entry in the Progress Language Reference). Second, use a DOWN statement to
advance the cursor to the next iteration of the frame. When you apply these steps to the previous
procedure, frame flashing no longer occurs:
p-frm13.p
/* This procedure produces flashing */
FORM customer.cust-num customer.name
customer.credit-limit
WITH FRAME cust-frame.
FOR EACH customer:
DISPLAY cust-num name credit-limit
WITH FRAME cust-frame.
END.
p-frm14.p
FORM customer.cust-num customer.name
customer.credit-limit
WITH FRAME cust-frame DOWN.
FOR EACH customer:
DISPLAY cust-num name credit-limit
WITH FRAME cust-frame.
DOWN WITH FRAME cust-frame.
END.
Frames
1919
19.9 Frame Families
By default, when Progress allocates a frame or when you display and accept input from an
explicitly defined frame, Progress parents that frame to either the current window or the window
you specify.
NOTE: In character interfaces Progress provides only the current window. You cannot create
additional windows.
For example, the following procedure parents frame custframe to the current window with the
first DISPLAY statement. It then parents custframe to the window specified by hwin with the
second DISPLAY statement:
Note that a frame can be parented to only one window at a time. So, when the second DISPLAY
statement parents custframe to the new window, it is removed from the current window. For
more information on window management, see Chapter 21, Windows.
Frame Relationships
You can also parent a frame (child frame) to another frame (parent frame). This causes Progress
to view the child frame within the display area of the parent frame. Frames that are parented by
a frame, which in turn is parented by a window, form a frame family. The frame parented by the
window is the root frame of the frame family. Frames parented by any child frame, in turn, form
a child frame family. All frames in a frame family are viewed within the display area of the root
frame, and all frames of a child frame family are displayed within the display area of the
parenting child frame. As with root frames and their parent windows, a child frame can have
only one parent frame at a time.
p-frmwin.p
DEFINE VARIABLE hwin AS WIDGET-HANDLE.
CREATE WINDOW hwin
ASSIGN TITLE = "New Window".
CURRENT-WINDOW:TITLE = "Current Window".
FIND FIRST customer.
DISPLAY name balance WITH FRAME cust-frame.
DISPLAY name balance WITH FRAME cust-frame IN WINDOW hwin.
PAUSE.
Progress Programming Handbook
1920
Figure 196 and Figure 197 show a typical frame family with three child frames. The root
frame is the Customer Data frame, which is contained by the Update window.
Figure 196: Frame Family (Windows Graphical Interface)
Frames
1921
Figure 197: Frame Family (Character Interface)
The three child frames include the Contact Information, Account Information, and Find and
Update Customer frames. Note how these three frames are nested between the top and bottom
fields of the parent Customer Data frame.
Frame Family Behavior
Child frames behave like field-level widgets in a frame, as the remaining sections in this chapter
explain. In fact, a frame is parented to a frame in exactly the same way that a field-level widget
is parented to a frame, using a field group. For more information on field groups, see the
Field-group Widgets section.
Progress Programming Handbook
1922
19.9.1 Defining a Frame Family
The first step in defining a frame family is to define a child frame of a root frame. To define a
child frame of any frame, you set the child frames FRAME attribute to the widget handle of the
parent frame, as shown in the following procedure (p-fof1.p):
By default, Progress positions a child frame at row 1, column 1 (the upperleft corner) of the
parent frames display area. Thus, when you specify position options for a child frame, they are
relative to the parent frames display area, not the windows. In this way, you can define and
position child frames of child frames (descendant frames) to any practical depth.
19.9.2 Using Frame Families
Child frames help to organize the display area of the parent frame. They can contain their own
field-level widgets, and like any frame parented to a window, they can be referenced in frame
I/O statements. Child frames can be viewed and hidden like field-level widgets and also
participate in the tab order of the parent frame.
You can choose a default button or Cancel button from a child frame. If a child frame does not
have the chosen button, invoking that button in the child frame will cause the first default or
Cancel button found in the frame family (and in no special order) to be chosen.
You can also parent child frames to a dialog box, which can serve as a root frame for a frame
family. However, child frames cannot be viewed as dialog boxes, themselves. For more
information on dialog boxes, see Chapter 25, Interface Design.
p-fof1.p
DEFINE FRAME fparent WITH SIZE 60 by 10 TITLE "Parent".
DEFINE FRAME fchild WITH SIZE 40 by 5 TITLE "Child".
FRAME fchild:FRAME = FRAME fparent:HANDLE.
DISPLAY WITH FRAME fparent.
ENABLE ALL WITH FRAME fparent.
ENABLE ALL WITH FRAME fchild.
WAIT-FOR WINDOW-CLOSE OF CURRENT-WINDOW.
Frames
1923
Child frames have some additional restrictions:
You cannot use them as down frames; they can only be 1 down.
They must fit at least partially within the virtual display area of their parent frame.
They do not inherit the attributes of their parent frame.
If you set SESSION:DATAENTRYRETURN to TRUE, the last fill-in of a child frame
can trigger a GO event for the frame if it is the last fill-in field in the frame family tab order.
For more information, see the section on fillin fields in Chapter 17, Representing Data.
You must display, enable, and disable the field-level widgets of child frames explicitly.
Doing so for a parent frame has no effect on the fields displayed in its child frames.
The following procedure (p-fof2.p) uses a frame family to organize a window for updating
customer records. It displays the Update window shown previously in Figure 196 and Figure
197.
Progress Programming Handbook
1924
p-fof2.p (1 of 3)
DEFINE QUERY custq FOR customer.
DEFINE VARIABLE cnt AS INTEGER.
DEFINE BUTTON bprev LABEL "<".
DEFINE BUTTON bnext LABEL ">".
DEFINE BUTTON bupdate LABEL "Update".
DEFINE BUTTON bcommit LABEL "Commit".
DEFINE FRAME cust-fr SKIP(.5)
SPACE(8) customer.name customer.cust-num customer.sales-rep
customer.comments AT COLUMN 6 ROW 13.5
WITH SIDE-LABELS TITLE "Customer Data" KEEP-TAB-ORDER
&IF "{&WINDOW-SYSTEM}" <> "TTY" &THEN
SIZE 80 BY 15.
&ELSE
SIZE 80 BY 16.
&ENDIF
DEFINE FRAME cont-fr SKIP(.5)
customer.address COLON 17 SKIP
customer.address2 COLON 17 SKIP
customer.city COLON 17 SKIP
customer.state COLON 17 SKIP
customer.postal-code COLON 17 SKIP
customer.country COLON 17 SKIP
customer.contact COLON 17 SKIP
customer.phone COLON 17
WITH SIDE-LABELS TITLE "Contact Informaion"
SIZE 40 BY 10 AT COLUMN 1 ROW 3.
DEFINE FRAME ctrl-fr SKIP(.12)
SPACE(.5) bprev bnext bupdate bcommit
WITH TITLE "Find and Update Customer"
&IF "{&WINDOW-SYSTEM}" <> "TTY" &THEN
SIZE 27.5 BY 2 AT COLUMN 46 ROW 10.5.
&ELSE
SIZE 29 BY 3 AT COLUMN 45 ROW 10.
&ENDIF
DEFINE FRAME acct-fr SKIP(.5)
customer.balance COLON 15 SKIP
customer.credit-limit COLON 15 SKIP
customer.discount COLON 15 SKIP
customer.terms COLON 15
WITH SIDE-LABELS TITLE "Account Information"
SIZE 39.8 BY 6 AT COLUMN 41 ROW 3.
ON CHOOSE OF bnext IN FRAME ctrl-fr DO:
GET NEXT custq.
IF NOT AVAILABLE customer THEN GET FIRST custq.
RUN display-proc IN THIS-PROCEDURE.
END.
Frames
1925
ON CHOOSE OF bupdate IN FRAME ctrl-fr DO:
DISABLE ALL WITH FRAME ctrl-fr.
ENABLE bcommit WITH FRAME ctrl-fr.
ENABLE ALL EXCEPT customer.cust-num WITH FRAME cust-fr.
ENABLE ALL WITH FRAME cont-fr.
ENABLE ALL EXCEPT customer.balance WITH FRAME acct-fr.
END.
ON CHOOSE OF bcommit IN FRAME ctrl-fr DO:
DISABLE ALL WITH FRAME cust-fr.
DISABLE ALL WITH FRAME cont-fr.
DISABLE ALL WITH FRAME acct-fr.
ENABLE ALL WITH FRAME ctrl-fr.
DISABLE bcommit WITH FRAME ctrl-fr.
GET CURRENT custq EXCLUSIVE-LOCK.
IF NOT CURRENT-CHANGED(customer) THEN DO:
ASSIGN customer EXCEPT customer.cust-num customer.balance.
RELEASE customer.
END.
ELSE DO:
GET CURRENT custq NO-LOCK.
RUN display-proc IN THIS-PROCEDURE.
DO cnt = 1 TO 12: BELL. END.
MESSAGE "Customer" customer.name SKIP
"was changed by another user." SKIP
"Please try again..." VIEW-AS ALERT-BOX.
END.
END.
FRAME cont-fr:FRAME = FRAME cust-fr:HANDLE.
FRAME acct-fr:FRAME = FRAME cust-fr:HANDLE.
FRAME ctrl-fr:FRAME = FRAME cust-fr:HANDLE.
IF NOT SESSION:WINDOW-SYSTEM = "TTY" THEN CURRENT-WINDOW:TITLE = "Update
...".
OPEN QUERY custq PRESELECT EACH customer NO-LOCK BY customer.name.
GET FIRST custq.
RUN display-proc IN THIS-PROCEDURE.
ENABLE ALL WITH FRAME ctrl-fr.
DISABLE bcommit WITH FRAME ctrl-fr.
WAIT-FOR WINDOW-CLOSE OF FRAME cust-fr.
p-fof2.p (2 of 3)
Progress Programming Handbook
1926
Note how each frame is managed individually for input and output. Displaying frame custfr
displays its child frames, but not their field values. Likewise, enabling the fields of custfr for
input has no effect on the fields in contfr, acctfr, or ctrlfr. Each of these child frames must
have their field-level widgets enabled and disabled individually.
When enabled, you can tab among all the field-level widgets in the custfr frame family. For
more information on frame family layout and tabbing behavior, see the remaining sections of
this chapter and Chapter 25, Interface Design.
19.10 Frame Services
Progress provides the following services for each frame in a block:
Viewing and hiding
Advancing and clearing
Retaining previously entered data during RETRY of a block
Creating field-group widgets
PROCEDURE display-proc:
DISPLAY
customer.name customer.cust-num customer.sales-rep
customer.comments WITH FRAME cust-fr.
DISPLAY
customer.address customer.address2 customer.city customer.state
customer.postal-code customer.country customer.contact
customer.phone WITH FRAME cont-fr.
DISPLAY
customer.balance customer.credit-limit
customer.discount customer.terms WITH FRAME acct-fr.
END.
p-fof2.p (3 of 3)
Frames
1927
19.10.1 Viewing and Hiding Frames
Progress brings a frame into view when you display data in that frame. You can also use the
VIEW statement (or you can set the frames VISIBLE attribute to TRUE) to explicitly bring a
frame into view. You can use the HIDE statement to explicitly hide a frame (or you can set the
frames VISIBLE attribute to FALSE).
NOTE: If a parent or ancestor window of a frame has its HIDDEN attribute set to TRUE, you
can set the frames attributes (directly or indirectly with VIEW and data display
statements) to display the frame, but Progress does not display the frame until all
ancestor windows have their HIDDEN attributes set to FALSE.
When displaying frames within a window, Progress tiles the frames, starting at the top of the
window and proceeding to the bottom until the window is full. By default, every frame begins
at column 1. If there are more frames than fit in the window, Progress hides (erases) the frames
closest to the bottom of the window until enough space is cleared to display the next frame. If
ready to hide a frame, Progress pauses, by default, and displays the message Press space bar to
continue.
Within a window, you can display one frame over another using the OVERLAY option, and you
can make a frame fit in a display area that is smaller than the frame itself, using the SIZE option
of the Frame phrase. For more information on the SIZE option, see Chapter 25, Interface
Design.
Bringing Frames into View
You can bring a frame into view by displaying data in that frame, using the VIEW statement, or
setting the frames VISIBLE attribute to true. In the following procedure, the DISPLAY
statement brings the frame into view:
p-form1.p
FORM customer.name contact AT 40 SKIP
customer.address credit-limit AT 40 SKIP
customer.city customer.state NO-LABEL
customer.postal-code NO-LABEL balance AT 40 SKIP(1)
phone
HEADER "Customer Maintenance" AT 25 SKIP(1)
WITH SIDE-LABELS NO-UNDERLINE FRAME a.
FOR EACH customer WITH FRAME a:
DISPLAY curr-bal.
UPDATE name address city state postal-code phone contact credit-limit.
END.
Progress Programming Handbook
1928
The FORM statement describes frame a. To bring the frame into view before the DISPLAY
statement, you can use the VIEW statement. In the next example, the VIEW statement brings
frame b into view.
In the next example, Progress automatically removes frames from the screen by hiding them:
The output of this procedure is as follows:
p-form2.p
DEFINE FRAME b WITH ROW 7 CENTERED SIDE-LABELS
TITLE "Customer Info".
VIEW FRAME b.
REPEAT:
PROMPT-FOR customer.cust-num WITH FRAME a ROW 1.
FIND customer USING cust-num NO-LOCK.
DISPLAY customer EXCEPT comments WITH FRAME b.
END.
p-hide.p
FOR EACH customer:
DISPLAY name WITH FRAME f1 DOWN.
DISPLAY credit-limit WITH FRAME f2.
FOR EACH order OF customer WITH FRAME f3:
DISPLAY order-num.
END.
END.
Frames
1929
At this point, the procedure has displayed the customer name. But there is no more vertical
space on the screen in which to display the maxcredit and order numbers. Press SPACEBAR.
You can see that Progress automatically hides frame f1 to make room for frames f2 and f3.
When Progress runs out of space, it hides frames starting from the bottom of the window.
Progress Programming Handbook
1930
Using Overlay Frames
Progress allows you to create a frame that overlays another frame. To display one frame over
another, set the top frames OVERLAY attribute to true (or use the OVERLAY frame phrase
option). Also, make sure that the bottom frames TOPONLY attribute is set to false. Progress
displays a message before it displays the overlay frame.
For example, you might want to display all the information for a customer, then see that
customers orders one at a time while keeping at least some of the customer information in view.
The following procedure uses an overlay frame to accomplish this:
This procedure produces the following output:
p-ovrlay.p
FOR EACH customer:
DISPLAY customer WITH 2 COLUMNS TITLE "CUSTOMER INFORMATION".
FOR EACH order OF customer:
DISPLAY order-num order-date ship-date promise-date carrier
instructions WITH 2 COLUMNS 1 DOWN OVERLAY
TITLE "CUSTOMERS ORDERS" ROW 7 COLUMN 10.
END.
END.
Frames
1931
When you press SPACEBAR, if the customer has orders, you see the customers first order in a
frame that overlays the first frame on your screen, as shown in the following figure:
When you press SPACEBAR, the next customer appears, the order frame clears, and the next
order for the customer appears. Progress automatically refreshes a covered frame when an
overlay frame clears.
The p-ovrlay.p procedure displays the frame with the order information over the frame with
the customer information because the frame phrase for the order frame includes the OVERLAY
option. You can use the ROW and COLUMN options to control where the overlay frame is
placed on the screen. If you do not include the ROW and COLUMN options, the overlay frame
displays with the upper left corner in row 1, column 1 (unless you use some other option to
affect its placement, such as the CENTERED option).
Progress Programming Handbook
1932
Working with Multiple Overlay Frames
If every frame is an overlay frame and no frame has its TOPONLY attribute set to TRUE, you
can bring any overlay frame to the top by giving focus to one of its field-level widgets. You can
also prevent all overlay frames from changing their current overlay position (Z order) by
setting the windows KEEPFRAMEZORDER attribute to TRUE. No matter how this
attribute is set, you can always change the Z order of an overlay frame using these methods:
MOVETOTOP( ) Moves the frame to the top of the Z order.
MOVETOBOTTOM( ) Moves the frame to the bottom of the Z order.
The default Z order of multiple overlay frames is the order in which they are enabled.
Child frames are actually permanent overlay frames of their parent frame. When you overlay
several child frames, they maintain their collective Z order on top of the parent frame. However,
you can change the relative Z order of the child frames within the parent using these methods.
For more information on the OVERLAY option of the Frame phrase and the methods changing
frame Z order, see the Progress Language Reference.
NOTE: Dialog boxes also appear on top of other frames and may be preferable to overlay
frames. For more information on dialog boxes, see Chapter 25, Interface Design,
and the Progress Language Reference.
Viewing and Hiding Frame Families
When you view and hide a parent frame, all of its child frames and field-level widgets whose
HIDDEN attributes are set to FALSE are viewed and hidden with it. The viewing and hiding of
child frames also works as it does for field-level widgets.
When you use the VIEW statement to view a child frame, Progress views the child frame and
all of its ancestor frames, even if their HIDDEN attributes are set to TRUE. If necessary,
Progress resets their HIDDEN attributes to FALSE. However, if you use the DISPLAY or
ENABLE statement to view a child frame, Progress sets the attributes appropriately to display
the child frame, but only displays it on the screen if none of its ancestor frames have their
HIDDEN attributes set to TRUE. In this case, when all ancestor frames have their HIDDEN
attributes set to FALSE, the previously displayed or enabled child frame is then displayed on
the screen.
When you hide a child frame, like a field-level widget, its HIDDEN attribute is set to TRUE.
Thus, if you later hide and redisplay an ancestor frame, the hidden child frame stays hidden until
you explicitly view or display it.
Frames
1933
When you run the sample procedure p-fof3.p, the following window appears, which lets you
view and hide frame families:
Figure 198: Frame Family Viewing Demo
You can hide and view each frame and fill-in, displaying the HIDDEN and VISIBLE attribute
values in the message area as appropriate.
Progress Programming Handbook
1934
19.10.2 Advancing and Clearing Frames
When a block iterates, Progress advances any down frames scoped to the block to the next
iteration. Progress also clears the field values in the screen buffer for all one-down frames
scoped to the block. These actions take place only if you displayed or entered data into at least
one of the fields in the frame during the iteration.
When a down frame is full, Progress clears the entire frame before displaying more data. You
can use the RETAIN frame phrase option to specify a number of iterations of data that you do
not want Progress to clear.
19.10.3 Retaining Data During Retry of a Block
When the procedure retries a block, either because of an implicit or explicit UNDO or RETRY,
Progress does not clear the frame scoped to that block. That way, the data are still available
when the user enters new data or changes the data entered on the first try.
19.10.4 Creating Field-group Widgets
For each frame, Progress automatically creates one or more field-group widgets to hold the
field-level widgets. For more information on field groups, see the Field-group Widgets
section.
Frames
1935
19.11 Using Shared Frames
Sometimes you want several procedures to use the same frame. You might use the same frame
throughout an application or with a few different procedures in an application. Progress lets you
define a frame as shared, so that many procedures can use it. You use the DEFINE SHARED
FRAME statement to define a shared frame. You must describe the shared frame in a FORM
statement.
You can write a procedure to display customer information, another procedure to update order
information, and you can display all the information in the same frame. One way to do this is to
define a shared frame.
Figure 199 shows how two procedures can use the same frame.
p-dicust.p
p-updord.p
p-dicust.i
Figure 199: Shared Frames
DEFINE SHARED FRAME cust-frame
{p-dicust.i}
DEFINE NEW SHARED FRAME cust-frame
{p-dicust.i}
p-dicust.p p-updord.p
p-dicust.i
FORM
WITH FRAME cust-frame
Menu
Screen
Display
Progress Programming Handbook
1936
The FORM statement in this example is in an include file, p-dicust.i. This file sets up and
names the custframe:
Although you cannot run the p-dicust.i file by itself because it is only a FORM statement, the
file describes the following frame:
p-dicust.i
FORM
xcust.cust-num COLON 10 LABEL "Cust #"
xcust.name COLON 10 xcust.phone COLON 50
xcust.address COLON 10 xcust.sales-rep COLON 50
csz NO-LABEL COLON 12 xcust.credit-limit COLON 50
SKIP (2)
order.order-num COLON 10 order.order-date COLON 30
order.ship-date COLON 30
order.promise-date COLON 30 WITH SIDE-LABELS 1 DOWN CENTERED ROW 5
TITLE " CUSTOMER/ORDER FORM " FRAME cust-frame.
Frames
1937
The p-dicust.p procedure displays customer information in this frame:
The p-dicust.p procedure displays customer information in the shared frame custframe and
lets the user update the order information for the customer on the screen. The procedure defines
the new shared frame named custframe and a new shared buffer, xcust, to store customer
information. (You can use only one DEFINE SHARED FRAME statement per frame in a
procedure.) The name custframe corresponds to the frame in the FORM statement in the
p-dicust.i include file.
p-dicust.p
DEFINE NEW SHARED FRAME cust-frame.
DEFINE NEW SHARED VARIABLE csz AS CHARACTER FORMAT "x(22)".
DEFINE NEW SHARED BUFFER xcust FOR customer.
REPEAT:
{p-dicust.i} /* include file for layout of shared frame */
PROMPT-FOR xcust.cust-num WITH 1 DOWN FRAME cust-frame.
FIND xcust USING xcust.cust-num NO-ERROR.
IF NOT AVAILABLE(xcust)
THEN DO:
MESSAGE "Customer not found".
NEXT.
END.
DISPLAY
name phone address sales-rep
city + ", " + state + " " + STRING(postal-code) @ csz
credit-limit WITH FRAME cust-frame.
RUN p-updord.p. /* External procedure to update customers orders */
END.
Progress Programming Handbook
1938
The procedure prompts the user for a customer number and displays the customer information
for that customer using the frame defined in the p-dicust.i file. Then the procedure calls the
p-updord.p procedure, shown below:
The p-updord.p procedure defines the shared frame custframe and the shared variable csz,
which was first defined in the p-dicust.p procedure. It also defines the shared buffer to store
customer information. You must use the DEFINE SHARED VARIABLE statement for the csz
variable because variables named in a shared frame are not automatically shared variables. You
must also use the DEFINE SHARED FRAME statement for the custframe so that Progress can
recognize the frame.
The p-updord.p procedure finds the customer that the p-dicust.p procedure is using in the
shared buffer, xcust. The procedure then displays the information for each order of the customer
in the shared frame called custframe. Finally, the procedure allows the user to update the order
information.
When you use shared frames, remember these important points:
In the first procedure where you reference a shared frame, use the DEFINE NEW
SHARED FRAME statement. In subsequent procedures where you reference the shared
frame, use the DEFINE SHARED FRAME statement.
You must completely describe the shared frame in the initial DEFINE NEW SHARED
FRAME statement or an additional FORM statement. Procedures that share this frame
only have to define fields that correspond to the fields in the initial definition plus any
specified ACCUM option. Other Frame phrase options for the SHARED frames are
allowed but are ignored, except for the ACCUM option. This allows you to make use of
the same FORM statement in an include file for both the NEW SHARED and matching
SHARED frames.
p-updord.p
DEFINE SHARED FRAME cust-frame.
DEFINE SHARED VARIABLE csz AS CHARACTER FORMAT "x(22)".
DEFINE SHARED BUFFER xcust FOR customer.
FOR EACH order OF xcust:
{p-dicust.i} /* include file for layout of shared frame */
DISPLAY order.order-num WITH FRAME cust-frame.
UPDATE order.order-date order.ship-date
order.promise-date WITH FRAME cust-frame.
END.
Frames
1939
A shared frame is scoped to the procedure or the block within the procedure that defines
it as NEW. For example, in the p-updord.p procedure above, the p-dicust.i include file
has no effect on scope.
A shared frame is scoped only once, and the scope is in the procedure where the frame is
defined as NEW.
If you want to share a frame family, you must make all parent and child frames in the
family shared frames.
See the Progress Language Reference for more information on the DEFINE FRAME statement.
19.12 Field-group Widgets
When you display field-level widgets in a frame or assign a child frame to a parent frame,
Progress creates one or more field-group widgets to hold the field-level widgets and child
frames. (For information on assigning a child frame to a parent frame, see the Frame Families
section.)
Progress uses field groups to collect field-level widgets and child frames. You might find it
helpful to think of a field group as a kind of geometrical representation of a record (or an
iteration). For example, this code fragment displays five records, each contained in a field
group.
The output of this fragment is shown below.
All field-level widgets, such as buttons, are placed in field groups. A field group is a collector
of field-level widgets and child frames. However, Progress also allocates empty field groups.
For example, Progress allocates a field group to the background of a frame, whether or not the
field group contains any widgets.
FOR EACH customer WITH 5 DOWN.
DISPLAY cust-num name.
END.
Field Group
Progress Programming Handbook
1940
Field groups are the children of frames and the real parents of field-level widgets and child
frames. This makes frames the grandparents of field-level widgets and child frames. Thus, if
you want an application to perform an operation on field-level widgets in a frame without
knowing beforehand what widgets are in the frame, the application must access them by going
through their respective field groups. For more information on accessing field groups, see the
Accessing Field Groups section.
Field groups widget are unique in that Progress, not the application, is completely responsible
for their creation and deletion. An application can access the field groups, but cannot create or
delete them.
In most cases, it is not necessary to access field groups from within an application. Like child
frames, you can move individual field-level widgets in and out of frames by setting their
FRAME attribute.
Finally, field groups organize the tab order of the field-level widgets and child frames that they
contain. For more information, see Chapter 25, Interface Design.
Frames
1941
19.12.1 Field-group Types
Progress allocates two types of field groups:
Foreground
Background
Progress can allocate multiple foreground field groups per frame but only one background field
group. Figure 1910 shows the different field groups.
Figure 1910: Field-Group Allocation
Background
Field Group
Foreground
Field Groups Frame
X-Axis
Z-Axis
Y-Axis
Spare Field Group
Progress Programming Handbook
1942
For a one-down frame, Progress allocates one foreground field group and one background field
group. For a down frame, Progress allocates one foreground field group for each iteration (plus
one extra if the number of visible iterations in the down frame is less than the number of records
that need to be displayed) and one background field group.
19.12.2 Field-group Characteristics
Field groups have the following characteristics:
They have geometric attributes (an x and y position, width, and height).
They have no visible representation (color, bounding box, or font).
They take no events.
They are created implicitly by Progress (you cannot create or define a field group).
Once Progress has created a field group, you can access it from within an application. For more
information, see the Accessing Field Groups section.
19.12.3 Field-group Size
Foreground field groups are as wide as the interior of the frame. The background field group is
also as wide as the interior of the frame.
Foreground field groups vary in height. In a dynamic one-down frame (and a static one-down
frame without column labels), the foreground field group is as high as the interior of the frame.
In a static one-down frame with column labels, the foreground field group starts below the
column headers; as a result, its height is less than the interior of the frame. In a down frame, the
foreground field groups are high enough to contain the field-level widgets displayed in one
iteration.
You can display dynamic field-level widgets within a field group. However, it is not
recommended that you place dynamic widgets in the foreground field groups of a down frame.
Not only is the height of these field groups determined by Progress (the application cannot
control the height), but the field group may also scroll off the screen (either in a scrolling frame
or if the frame retains a number of iterations). After it reappears, the field group will still contain
the dynamic widget, but also may contain a new record. For more information, see the
Field-group Visibility and Scrolling section.
You cannot mark, move, or resize field groups directly. However, if you resize a frame,
Progress automatically resizes the field groups by default.
Frames
1943
19.12.4 Field-group Position Relative to Frame
Frames contain field groups, which contain field-level widgets. Field groups are therefore
positioned relative to frames, while field-level widgets are positioned relative to field groups.
Consistent with this, the X, Y, ROW, and COLUMN attributes of a field group return values
relative to the frame. The X, Y, ROW, and COLUMN attributes of a field-level widget
(including radio buttons) return values relative to the parent field group.
To access the location of a field-level widget relative to its frame, query the field-level widgets
FRAMEX and FRAMEY attributes (for pixel values), or FRAMECOL and FRAMEROW
attributes (for character unit values).
For a list of field-group attributes, see the FIELD-GROUP Widget reference entry in the
Progress Language Reference.
19.12.5 Z order and Field Groups
If you overlap widgets in an application, Progress must determine which widget to place on top.
Within a frame, there are three layers:
The foreground layer, with all the nondrawable field-level widgets in the foreground field
group. This is the topmost layer. (A drawable field-level widget is a rectangle or image.)
The background layer, with all the nondrawable field-level widgets in the background
field group. This is the next topmost layer.
The drawable layer, with all the drawable field-level widgets from all of the field groups.
This is the bottommost layer.
Within each z layer, widgets also have a z order (that is, one foreground button may be on top
of another foreground button). However, Progress places all nondrawable foreground widgets
on top of all nondrawable background widgets, and places all drawable widgets on the bottom
layer. For example, if you have a text widget in the background and an image in the foreground,
the text widget appears on top of the image.
NOTE: Figure 1910 shows the relative z-order positions of the foreground and background
field groups (ZAxis), but does not show the separate drawable layer derived from
both of these field groups. The drawable layer is not a field group, itself, but a z-order
layer under the background field group (extreme left of Figure 1910) containing
drawable widgets from both field group layers.
If you want an application to be portable to character displays, you should avoid using
overlapping widgets. In a character display, if two field-level widgets overlap, one of the
widgets may appear, both may appear, or some part of each may appear.
Progress Programming Handbook
1944
19.12.6 Field-group Visibility and Scrolling
Figure 1910 shows a spare field group. Progress allocates a spare field group if the number of
visible iterations is less than the number of records that need to be displayed. At any given time,
one of the foreground field groups in a down frame can be the spare. That is, the foreground
field group can scroll off the screen.
The frames FIRSTCHILD/NEXTSIBLING chain includes all of the field groups in the
frame (including the spare). Its order is not affected by scrolling (see the Accessing Field
Groups section).
However, the frame also has an GETITERATION( ) method, which along with the
NUMITERATIONS attribute, simulates an array of all of the visible foreground field groups.
Thus, if you want to find only the visible foreground field groups at any instant, you can use the
GETITERATION( ) method with the NUMITERATIONS attribute.
The GETITERATION( ) method takes an integer argument n and, starting at the top of the
frame, returns the widget handle of the field group that displays the nth visible iteration. You
can access all of these field groups by using a loop that starts at one (GETITERATION(1)) and
runs through the total number of visible iterations.
If the frame is named X, you can access the last visible iteration as follows:
If you have a widget handle variable named X, you can access the last visible iteration as
follows:
As the frame scrolls, these attributes return different values. Thus, if an application wants to
cycle through the visible iterations using the iteration loop, it should not do anything during the
cycle that would scroll the frame.
This code fragment produces a frame with five visible iterations.
GET-ITERATION(FRAME X:NUM-ITERATIONS)
GET-ITERATION(X:NUM-ITERATIONS)
FOR EACH customer WITH 5 DOWN.
DISPLAY cust-num name.
END.
Frames
1945
Before Progress executes the FOR EACH loop, NUMITERATIONS equals 0 (because
Progress hasnt created any field groups yet). The first time through the loop, after Progress
displays the first custnum and name, NUMITERATIONS equals 1. The second time through,
NUMITERATIONS equals 2. Once Progress has displayed the fifth custnum and name,
NUMITERATIONS equals 5. The count will remain 5 until the frame is terminated. Note that
after Progress starts scrolling, there will be 6 field groups5 plus 1 spare (the spare will be
different as the frame scrolls). The NUMITERATIONS attribute will stay at 5, but there will
actually be 6 field groups.
Progress does not create new field groups (and all their underlying field-level widgets) as they
are scrolled off the screen. Instead, it clears the existing field groups and then reuses them.
If an application uses the UNDERLINE statement, Progress uses one foreground field group to
underline the fields in the field group directly above it. Also, if the application uses a DOWN
statement to advance one or more iterations in the frame without displaying any data, Progress
uses one of the foreground field groups to display a blank iteration. For example, this code
fragment uses a DOWN statement to display a blank iterations.
If you insert dynamic field-level widgets in a foreground field group of a down frame, you
should be aware of the following defaults:
As a frame scrolls, Progress moves the foreground field groups around in the frame. Thus,
if an application places a dynamic widget in a field group, the dynamic widget will jump
around in the frame as the field group moves.
Any foreground field group may become the spare field group as the field groups are
moved around in the frame. When a field group becomes the spare, it will not be visible
on screen.
If Progress uses a field group to display a blank iteration or uses an underline field group,
it hides all of the field groups children (including any dynamic widgets).
When a field group scrolls off the top or bottom of the frame, Progress clears the field
group. All fill-in widgets are reset to blanks, including any dynamic fill-in widgets.
NOTE: Because of the complex nature of these defaults, PSC recommends that you do not
place dynamic widgets in a foreground field group of a down frame.
REPEAT WITH FRAME a DOWN:
FIND NEXT customer.
DISPLAY cust-num name.
DOWN 1 WITH FRAME a.
END.
Progress Programming Handbook
1946
19.12.7 Accessing Field Groups
Different applications may want to access all of the field groups in a frame, the foreground field
groups, or the visible foreground field groups. The following examples show how an
application can access these different field groups.
Example 1
This example shows how an application might access all of the field groups (and their field-level
widgets and child frames) in a frame.
This example uses the frames FIRSTCHILD/NEXTSIBLING chain to access all of the field
groups, field-level widgets, and child frames in frame FRX (including the spare field group, if
one exists). FLW is set to each sibling field-level widget and child frame of the frame. Note that
the order in which this loop accesses the field groups is not connected with the order in which
the field groups are displayed on screen.
NOTE: This example does not access any widgets parented by child frames. To accomplish
that requires a recursive version of this procedure.
DEFINE VARIABLE GRP AS WIDGET-HANDLE. /* the field group */
DEFINE VARIABLE FLW AS WIDGET-HANDLE. /* the field-level widget */
GRP = FRAME FRX:FIRST-CHILD.
DO WHILE (GRP <> ?).
FLW = GRP:FIRST-CHILD.
DO WHILE (FLW <> ?). /* loop through field-level widgets in
field-group */
/* insert code here to access the widget */
FLW = FLW:NEXT-SIBLING.
END.
GRP = GRP:NEXT-SIBLING.
END.
Frames
1947
Example 2
This example shows how an application might access all of the foreground field groups (and
their field-level widgets and child frames) in a frame.
This example uses the frames FIRSTCHILD/NEXTSIBLING chain and the
FOREGROUND attribute to access all of the foreground field groups, field-level widgets, and
child frames in frame FRX (including the spare field group, if one exists).
NOTE: This example does not access any widgets parented by child frames. To accomplish
that requires a recursive version of this procedure.
DEFINE VARIABLE GRP AS WIDGET-HANDLE. /* the field group */
DEFINE VARIABLE FLW AS WIDGET-HANDLE. /* the field-level widget */
GRP = FRAME FRX:FIRST-CHILD.
DO WHILE (GRP <> ?).
IF (GRP:FOREGROUND) THEN DO:
FLW = GRP:FIRST-CHILD.
DO WHILE (FLW <> ?). /* loop through field-level widgets in
field-group */
/* insert code here to access the widget */
FLW = FLW:NEXT-SIBLING.
END.
END.
GRP = GRP:NEXT-SIBLING.
END.
Progress Programming Handbook
1948
Example 3
This example shows how an application might access all of the visible foreground field groups
(and their field-level widgets) in a down frame.
This example uses the frames FIRSTCHILD/NEXTSIBLING chain and the
GETITERATION( ) method to access all of the foreground widgets in the visible field groups
frame FRX.
DEFINE VARIABLE IDX AS INTEGER. /* index of visible iterations */
DEFINE VARIABLE GRP AS WIDGET-HANDLE. /* the field-group */
DEFINE VARIABLE FLW AS WIDGET-HANDLE /* the field-level widget */
DO IDX = 1 TO FRAME FRX:NUM-ITERATIONS:
GRP = FRAME FRX:GET-ITERATION(IDX).
FLW = GRP:FIRST-CHILD.
DO WHILE (FLW <> ?). /* loop through field-level widgets in
field-group */
/* insert code to access the widget */
FLW = FLW:NEXT-SIBLING.
END.
END.
Frames
1949
19.13 Validation of User Input
Progress allows you to define validation expressions and user validation messages directly in
the Data Dictionary or programmatically with the VALIDATE option of the Format phrase and
the Browse Format phrase. The validation expression establishes the Boolean condition that
defines valid data. Progress displays the validation message to the user (in the message area or
in an alert box, depending on the configuration) if the new data fails the validation expression.
Progress executes validation expressions when the user attempts to leave a field. Once the user
begins to enter new data in, the user cannot leave the field without entering valid data or
cancelling the input.
Validation of user input is fundamental to guaranteeing the integrity of your database. Progress
provides all the language and system options you require to provide seamless validation of all
user input. First, this section outlines the most efficient and straightforward way to provide
complete validation. Then, it discusses the role of each language and system option that
contribute to the validation story and scenarios that can lead to validation holes.
19.13.1 Recommended Validation Techniques
Follow the short list of recommendations below for complete validation of user input:
Put as much of your validation expressions in the database schema with the Data
Dictionary.
For custom validation, add the VALIDATE option to the field reference in the DEFINE
FRAME, FORM, or DEFINE BROWSE statements. While the VALIDATE option can
legally be added in many places, placing it in one of these statements guarantees that it will
be compiled into the frame or browse definition.
Do not add fields to a frame after the initial definition (not possible for a browse). Or, if
you do, use programmatic validation.
Do not use the field:SENSITIVE = YES syntax to enable fields for input. Data Dictionary
validation is not compiled into a frame when this syntax is used.
Progress Programming Handbook
1950
19.13.2 The Rules of Frame-based Validation
In Progress, validation of user input is a frame-based activity. The compiler adds validation
expressions to a frame from the Data Dictionary the first time a field is referenced in that frame
in an input statement (ENABLE, PROMPTFOR, SET, UPDATE). The compiler adds
programmatic validation (VALIDATE option) to the frame when it is encountered, as long as
it has not already compiled in Data Dictionary validation. So, if you want programmatic
validation to override existing Data Dictionary validation it is safest to place it in the DEFINE
FRAME, FORM, or DEFINE BROWSE statement. After the compiler adds initial validation
for a field in a frame, all other validation for that field in that frame is ignored.
To take advantage of this default behavior, add all of your default validation to database schema
and use the VALIDATE option for special cases. Since the default behavior is frame-based, you
can use different validation for the same field in different frames.
The ENABLE statement with the ALL option poses a special problem for the compiler, since it
cannot know syntactically which fields in the frame require Data Dictionary validation. To
avoid possible validation holes, the compiler adds Data Dictionary validation for all fields in the
frame, even if they are defined as non-sensitive widgets. If you add a field to a frame after the
compiler encounters the first ENABLE ALL statement, Data Dictionary validation will not be
added to the frame for this field. To avoid creating a possible validation hole, do not add new
fields to frames, or use the VALIDATE option to provide programmatic validation.
If you use the EXCEPT option with ENABLE ALL, the compiler will not add Data Dictionary
validation to the frame for the fields listed with EXCEPT.
Finally, note that Progress does not have any default validation behavior for the
widget:SENSITIVE = YES syntax. Using this backdoor technique for enabling fields for input
creates a validation hole. If you use this technique, then you will have to force frame-wide
validation to close any possible validation holes. The next section discusses frame-wide
validation techniques.
19.13.3 Forcing Frame-wide Validation
To make sure that your application does not have any validation holes, you have three options:
Add the USEDICTEXPS option on the Frame phrase for each affected frame.
Use the Dictionary Expressions startup parameter to apply the USEDICTEXPS option
to every frame in the application.
Use the frame VALIDATE( ) method to check all the fields in a frame before committing
the changes.
Frames
1951
Using the USEDICTEXPS Frame Phrase Option
The USEDICTEXPS option of the frame phrase forces all Data Dictionary validation and
help strings to be compiled into the frame on the first reference of a field in the frame. Note that
this is different from the normal behavior. Data dictionary validation is compiled in on the first
reference of the field in an input statement. For this reason, programmatic VALIDATE or HELP
options must be added to the DEFINE FRAME, FORM, or DEFINE BROWSE statements to
override existing Data Dictionary expressions. (The first reference of a field in the frame is
normally the reference in one of these statements, unless the field is added later.)
This option may add a lot of non-essential code to a frame, so its use should be restricted to
where necessary. The option is intended to be used with frames that might have fields enabled
with the widget:SENSITIVE = YES syntax. It is not necessary in any other case.
Using the Dictionary Expressions (dictexps) Startup Parameter
The Dictionary Expressions (dictexps) startup parameter is intended to be used as a temporary
step to close possible validation holes in existing applications. It has the effect of adding a
USEDICTEXPS option to every frame in the application, including those that are not used
for input. While this parameter quickly closes all possible validation holes, it is a highly
inefficient way of doing it.
For best long-term results, search for and replace widget:SENSITIVE = YES constructs, or use
the USEDICTEXPS option on effected frames.
For more information on the dictexps parameter, see the Progress Startup Command and
Parameter Reference.
Progress Programming Handbook
1952
Using the Frame VALIDATE( ) Method
However, you can force Progress to perform all established variable and field validations at any
time by executing the VALIDATE( ) method for the frame. The method returns TRUE if all
validate checks in the frame succeed. It returns FALSE for the first validate check that fails in
the order that data representation widgets are specified in the frame definition. A typical
application of this method is in a trigger for an event that accepts the current updates in the
frame. If the method fails, Progress gives input focus to the widget that failed the test after the
trigger returns to the blocking WAITFOR statement.
NOTE: You can also apply the VALIDATE( ) method to a single data representation widget
to perform any validate check for the underlying field or variable of that widget. If
the test fails, focus returns to the specified widget.
The following procedure shows the VALIDATE( ) method applied to a frame that is used to
update existing item records in the sports database. Enter an item number and press RETURN.
The procedure displays and enables fields from the selected record, if it is available. To update
the record in the database and enter a new item number, choose the bContinue button.
Note that once an item record is enabled for update, the procedure does not allow the record to
be written back to the database unless all the relevant fields pass the specified validate checks.
This is true even if no input fields have been entered and no data has changed from the original
record.
Frames
1953
p-frmval.p
DEFINE BUTTON bContinue LABEL "Continue".
DEFINE BUTTON bQuit LABEL "Quit".
DEFINE FRAME ItemFrame
item.item-num
item.item-name
VALIDATE(item-name BEGINS "B",
"Must enter item-name starting with B")
item.on-hand
VALIDATE(on-hand > 0, "Must enter items on-hand > 0.")
item.allocated
VALIDATE(allocated <= on-hand,
"Allocated cannot be greater than on-hand items.")
bContinue
bQuit
WITH SIDE-LABELS.
ON RETURN OF item.item-num IN FRAME ItemFrame DO:
FIND FIRST item
WHERE item-num = INTEGER(item-num:SCREEN-VALUE) NO-ERROR.
IF AVAILABLE(item) THEN DO:
DISABLE item-num WITH FRAME ItemFrame.
DISPLAY item-name on-hand allocated WITH FRAME ItemFrame.
ENABLE ALL EXCEPT item-num WITH FRAME ItemFrame.
END.
ELSE DO:
MESSAGE "Item not on file. Enter another.".
RETURN NO-APPLY.
END.
END.
ON CHOOSE OF bContinue IN FRAME ItemFrame DO:
IF NOT FRAME ItemFrame:VALIDATE() THEN
RETURN NO-APPLY.
ASSIGN item.item-name item.on-hand item.allocated.
DISABLE ALL EXCEPT bQuit WITH FRAME ItemFrame.
ENABLE item.item-num WITH FRAME ItemFrame.
END.
FIND FIRST item NO-ERROR.
DISPLAY
item.item-num item.item-name item.on-hand item.allocated
bContinue bQuit
WITH FRAME ItemFrame.
ENABLE item.item-num bQuit WITH FRAME ItemFrame.
WAIT-FOR CHOOSE OF bQuit IN FRAME ItemFrame.
Progress Programming Handbook
1954
19.13.4 Disabling Data Dictionary Validation on a Frame or Field
To disable all Data Dictionary validation for the fields in a frame, add the NOVALIDATE
option to the Frame phrase. NOVALIDATE only disables Data Dictionary validationit has
no effect on programmatic validation.
To disable Data Dictionary validation for a field, use the VALIDATE option as shown in the
code fragment below.
customer.credit-limit VALIDATE(TRUE, "")
20
Using Dynamic Widgets
Most applications do not require dynamic widgets other than windows, because widget
definitions can be determined statically, at compile time. However, sometimes an application
does not have all the information it needs to define a widget until run time. One example is an
application that allows the user to add and remove buttons that duplicate menu functions.
Another is an application that designs user interfaces for other applications, such as the Progress
AppBuilder. All such applications require that you create and delete widget definitions at run
time. For applications like these, you must use dynamic widgets.
This chapter describes the basic requirements and techniques for creating and managing
dynamic widgets in a user interface, and how they differ from static widgets. These include
techniques for managing dynamic widgets individually and as a group. For an introduction to
defining static and dynamic widgets, see Chapter 16, Widgets and Handles.
Some dynamic widgets require a more focused explanation than the general information
described here. For information on dynamic menus and submenus, see Chapter 22, Menus.
For information on dynamic windows (all windows are dynamic except for the static default
window), see Chapter 21, Windows.
This chapter describes techniques for:
Managing dynamic widgets
Managing dynamic widget pools
Creating and using dynamic queries
Creating and using a dynamic browse
Progress Programming Handbook
202
20.1 Managing Dynamic Widgets
Although you can create and delete a dynamic widget at run time, you must specify the
attributes and environment of a dynamic widget much more explicitly than for a static widget.
Progress provides attributes that allow you to specify for dynamic widgets at run time most of
the options that you specify for static widgets at compile time. While Progress automatically
sets read-only attributes for dynamic widgets just as it does for static widgets, Progress provides
fewer defaults for the attributes that you can set. In addition, you must use different 4GL
statements to work with dynamic widgets than you typically do with static widgets.
Note that dynamic widgets do not adhere to scoping rules. That is, they exist until you delete
them or the session ends. Thus, if you create a dynamic button in the FOR EACH loop of a
subprocedure, that button continues to exist after the subprocedure returns. However, note that
any widget handle variables are locally or globally scoped as you define them. Also, while
dynamic widgets are not scoped, trigger definitions are scoped. Thus, for dynamic widgets,
define persistent triggers or put your trigger definitions in a persistent procedure.
20.1.1 Static Versus Dynamic Widget Management
Table 201 compares the major 4GL actions involving static and dynamic widgets and
summarizes their differences.
Table 201: Static Versus Dynamic Widget Management (1 of 2)
4GL
Widget Action
Accomplished for
Static Widgets by . . .
Accomplished for
Dynamic Widgets by . . .
Create DEFINE widget statement,
VIEWAS phrase, FORM
statement (for frames), and
default scoping (for frames).
CREATE widget statement
Delete N/A DELETE WIDGET or DELETE
WIDGETPOOL statement
1
Reference Definition name at compile time
and run time; widget handle at
run time
Widget handle at run time
View on Display Frame I/O statements, including
ENABLE, DISPLAY, INSERT,
UPDATE, SET, or
PROMPTFOR; VIEW
statement
VIEW statement or setting the
VISIBLE attribute of each
widget to TRUE
2,3
Using Dynamic Widgets
203
Hide from Display HIDE statement HIDE statement or setting the
VISIBLE attribute of each
widget to FALSE
2
Make Sensitive to
Input
ENABLE, INSERT, UPDATE,
SET, or PROMPTFOR
statement
Setting the SENSITIVE
attribute of each widget to
TRUE
2
Make Insensitive to
Input
DISABLE statement Setting the SENSITIVE
attribute of each widget to
FALSE
2
Block for Input WAITFOR, INSERT,
UPDATE, SET or
PROMPTFOR statement
WAITFOR statement
Move Data from
Screen to Record
Buffer
ASSIGN, SET, INSERT, or
UPDATE statement applied to
corresponding field or variable
Explicit assignment from the
SCREENVALUE attribute of
the widget to a specified field or
variable
2
Move Data from
Record to Screen
Buffer
DISPLAY, INSERT, or
UPDATE statement applied to
corresponding field or variable
Explicit assignment from a
specified field, variable, or
constant to the
SCREENVALUE attribute of
the widget
2
1
For more information on widget pools, see the Managing Dynamic Widget Pools section.
2
You can also use these techniques with static widgets.
3
The behavior of the VISIBLE attribute also depends on the setting of the HIDDEN attribute.
Table 201: Static Versus Dynamic Widget Management (2 of 2)
4GL
Widget Action
Accomplished for
Static Widgets by . . .
Accomplished for
Dynamic Widgets by . . .
Progress Programming Handbook
204
20.1.2 Setting Up Dynamic Field-level Widgets
When setting up dynamic field-level widgets, it helps to keep these points in mind:
Frame parenting You can place a dynamic field-level widget in either a static or
dynamic frame (see the Setting Up Dynamic Frames section). Assign the frame handle
to the FRAME attribute of the field-level widget. For a field-level widget, the PARENT
attribute points to the field group, not the frame, of the widget. Progress automatically puts
the widget in a field group when you set its frame, and also assigns the widget a default
tab position if it is an input widget.
Widget positioning To space them out in a frame, you must explicitly position all
dynamic widgets by setting the appropriate vertical (ROW or Y) and horizontal
(COLUMN or X) placement attributes. However, Progress does assume the top-most and
left-most position in the frame if you do not set a placement attribute for the widget.
Widget sizing You can size a widget, depending on its widget type and data type, using
either the various height and width attributes or the FORMAT attribute. For example, a
fill-in field with its FORMAT attribute set to "x(20)" is exactly the same size as one with
its WIDTHCHARS attribute set to 20.
Label handling Not every field-level widget gets its label by setting the LABEL
attribute. You must provide separate text widgets as labels for some data representation
widgets. For side labels, you set the SIDELABELHANDLE attribute of the specified
data representation widget to the handle of the text widget containing the label in its
SCREENVALUE attribute. For any other type of label, such as for vertical columns, you
must create and manage the text widget completely separately. You must also position text
widgets used as labels explicitly, even for side labels. Progress assigns no positioning
information for dynamic side labels, as it does for button or toggle box labels.
Using Dynamic Widgets
205
Data handling Unlike static data representation widgets, dynamic widgets have no
field or variable implicitly associated with them. You must explicitly assign data between
a widgets SCREENVALUE attribute and the field or variable that you choose for data
storage. Thus, you can use a single widget to represent several fields and variables at
different times, depending on the widget and data type.
Data typing Some dynamic widgets support entry and display data types other than
CHARACTER, and some, such as fill-in fields and combo boxes support the full range of
Progress entry and display data types. Note that for dynamic widgets this support is for
entry and display purposes only. The SCREENVALUE attribute always stores the data
in character format, no matter what the widget data type. You must make all necessary data
type conversions using the appropriate functions (STRING, INTEGER, etc.) when
assigning data between the widget SCREENVALUE attribute and the field or variable
that you choose for data storage.
Progress Programming Handbook
206
The following procedure creates a dynamic fill-in with the INTEGER data type when you
choose the Customer Number button. You can then enter a value according to the >>>>9"
format. The entered integer value is stored as a character string in the screen buffer. Pressing
RETURN displays this value in the message area. Choosing the Delete Field button deletes the
fill-in, removing it from the display:
p-dynfl1.p (1 of 2)
DEFINE BUTTON bCustNumber LABEL "Customer Number".
DEFINE BUTTON bDelete LABEL "Delete Field".
DEFINE VARIABLE fCustHandle AS WIDGET-HANDLE.
DEFINE VARIABLE lCustHandle AS WIDGET-HANDLE.
DEFINE FRAME CustFrame
SKIP(3)
SPACE (1) bCustNumber bDelete
WITH SIZE 40 BY 5 SIDE-LABELS.
ON CHOOSE OF bCustNumber IN FRAME CustFrame
DO:
IF fCustHandle <> ? THEN
DO:
MESSAGE bCustNumber:LABEL "field already exists.".
RETURN.
END.
CREATE TEXT lCustHandle
ASSIGN
FRAME = FRAME CustFrame:HANDLE
DATA-TYPE = "CHARACTER"
FORMAT = "x(16)"
SCREEN-VALUE = "Customer Number:"
ROW = 2
COLUMN = 2
.
CREATE FILL-IN fCustHandle
ASSIGN
FRAME = FRAME CustFrame:HANDLE
DATA-TYPE = "INTEGER"
FORMAT = ">>>>9"
SIDE-LABEL-HANDLE = lCustHandle
ROW = 2
COLUMN = lCustHandle:COLUMN + lCustHandle:WIDTH-CHARS + 1
SENSITIVE = TRUE
VISIBLE = TRUE
TRIGGERS:
ON RETURN PERSISTENT RUN SetFieldTrig.
END TRIGGERS
.
END.
Using Dynamic Widgets
207
20.1.3 Setting Up Dynamic Frames
Progress allows you to create dynamic one-down frames. You cannot create dynamic down
frames. You can populate a dynamic frame only with dynamic field-level widgets and you must
explicitly specify their position within that frame. When Progress builds a static frame and
populates it with static widgets, it creates an intelligent default size for the frame and intelligent
default placement for the widgets. Progress does not do this with dynamic frames. You must
explicitly define frame size and widget placement.
ON CHOOSE OF bDelete IN FRAME CustFrame
DO:
IF fCustHandle <> ? THEN
DO:
DELETE WIDGET fCustHandle.
fCustHandle = ?.
DELETE WIDGET lCustHandle.
END.
END.
ENABLE ALL WITH FRAME CustFrame.
WAIT-FOR GO OF FRAME CustFrame.
PROCEDURE SetFieldTrig:
MESSAGE "You entered" lCustHandle:SCREEN-VALUE
fCustHandle:SCREEN-VALUE.
END PROCEDURE.
p-dynfl1.p (2 of 2)
Progress Programming Handbook
208
The following code creates a dynamic frame, then populates it with an editor widget and a quit
button:
By default, Progress places the dynamic frame in the current window. To place the frame in a
different window, set the PARENT attribute to the widget handle of that window.
p-dynfrm.p
DEFINE VARIABLE ed AS WIDGET-HANDLE.
DEFINE VARIABLE frame1 AS WIDGET-HANDLE.
DEFINE VARIABLE button1 AS WIDGET-HANDLE.
CREATE FRAME frame1
ASSIGN
WIDTH-CHARS = 50
HEIGHT-CHARS = 28
SENSITIVE = YES.
VIEW frame1.
CREATE BUTTON button1
ASSIGN
X = 20
Y = 20
LABEL = "quit"
FRAME = frame1
SENSITIVE = YES
TRIGGERS:
ON CHOOSE STOP.
END TRIGGERS.
VIEW button1.
CREATE EDITOR ed
ASSIGN
WIDTH-CHARS = 20
HEIGHT-CHARS = 7
X = 70
Y = 0
FRAME = frame1
SENSITIVE = YES.
VIEW ed.
WAIT-FOR GO OF frame1.
Using Dynamic Widgets
209
This is the output of p-dynfrm.p:
20.2 Managing Dynamic Widget Pools
Every dynamic widget you create is assigned to a widget pool. A widget pool is a group of
widgets that are scoped together and can be deleted as a group.
Progress creates a single unnamed widget pool for each client session. The session pool is
initially the default pool for all dynamic widgets created during the session. It is automatically
deleted when the session ends.
You can also create your own named or unnamed widget pools with the
CREATE WIDGETPOOL statement:
You can delete a widget pool with the DELETE WIDGETPOOL statement:
SYNTAX
CREATE WIDGET-POOL [ pool-name [ PERSISTENT ] ]
[ NO-ERROR ]
SYNTAX
DELETE WIDGET-POOL [ pool-name ] [ NO-ERROR ]
Progress Programming Handbook
2010
In general, when you create a dynamic widget, Progress assigns it to the most recently created
unnamed widget pool by default. You can explicitly assign a dynamic widget only to a named
widget pool. When you delete a widget pool, all dynamic widgets assigned to that pool are
deleted as well.
For more information on these statements, see the Progress Language Reference.
20.2.1 Named Widget Pools
Progress supports two types of named widget pools: persistent and nonpersistent. A persistent
widget pool remains allocated until it is explicitly deleted or the session ends. A nonpersistent
widget pool remains allocated until it is explicitly deleted or the procedure block that creates it
goes out of scope. When execution of a procedure or trigger ends or goes out of scope, Progress
automatically deletes any nonpersistent widget pool defined in that routine.
NOTE: A persistent procedure goes out of scope only when it is explicitly deleted. Thus,
non-persistent widget pools created in it can persist as long as the procedure persists.
When you create a dynamic widget, you can assign it to a named pool by using the
IN WIDGETPOOL option.
Using Dynamic Widgets
2011
The following procedure creates a named widget pool and creates a series of buttons in it:
The widget pool in this example is nonpersistent. Therefore, if you omit the DELETE
WIDGETPOOL statement at the end of the procedure, the widget pool is automatically deleted
when the procedure ends.
p-dybut2.p
DEFINE VARIABLE num-buts AS INTEGER.
DEFINE VARIABLE temp-hand AS WIDGET-HANDLE.
DEFINE FRAME butt-frame
WITH WIDTH 60 CENTERED TITLE "Sales Representatives".
FORM
salesrep
WITH FRAME rep-frame.
CREATE WIDGET-POOL "rep-buttons".
num-buts = 0.
FOR EACH salesrep:
CREATE BUTTON temp-hand IN WIDGET-POOL "rep-buttons"
ASSIGN LABEL = salesrep.sales-rep
FRAME = FRAME butt-frame:HANDLE
ROW = TRUNC(num-buts / 3, 0) + 1
COLUMN = ((num-buts MOD 3) * 20) + 1
SENSITIVE = TRUE
TRIGGERS:
ON CHOOSE
DO:
FIND salesrep WHERE salesrep.sales-rep = SELF:LABEL.
DISPLAY salesrep WITH FRAME rep-frame.
END.
END TRIGGERS.
num-buts = num-buts + 1.
END.
FRAME butt-frame:HEIGHT-CHARS = (num-buts / 3) + 2.
VIEW FRAME butt-frame.
WAIT-FOR WINDOW-CLOSE OF CURRENT-WINDOW.
DELETE WIDGET-POOL "rep-buttons".
Progress Programming Handbook
2012
Because all the widgets in p-dybut2.p are created in the main procedure and are deleted at the
end of the procedure, the widget pool does not need to be persistent. However, the following
procedure requires a persistent widget pool. The following procedure, p-dybut3.p, accepts a
month and year and then creates a button for each order entered during that month:
p-dybut3.p (1 of 2)
DEFINE VARIABLE report-month AS INTEGER LABEL "Month" FORMAT ">9".
DEFINE VARIABLE report-year AS INTEGER LABEL "Year" FORMAT "9999".
DEFINE VARIABLE temp-hand AS WIDGET-HANDLE.
FORM
report-month report-year
WITH FRAME prompt-frame SIDE-LABELS.
DEFINE FRAME butt-frame WITH WIDTH 80.
ON GO OF FRAME prompt-frame
DO:
IF report-month:MODIFIED or report-year:MODIFIED
THEN DO:
ASSIGN report-month report-year.
DELETE WIDGET-POOL "order-pool".
RUN make-buttons.
END.
END.
ASSIGN report-month = MONTH(TODAY)
report-year = YEAR(TODAY).
DISPLAY report-month report-year WITH FRAME prompt-frame.
RUN make-buttons.
ENABLE report-month report-year WITH FRAME prompt-frame.
DO ON ERROR UNDO, LEAVE ON ENDKEY UNDO, LEAVE ON STOP UNDO, LEAVE:
WAIT-FOR WINDOW-CLOSE OF CURRENT-WINDOW.
END.
DELETE WIDGET-POOL "order-pool".
Using Dynamic Widgets
2013
PROCEDURE make-buttons:
DEFINE VARIABLE num-buts AS INTEGER.
CREATE WIDGET-POOL "order-pool" PERSISTENT.
num-buts = 0.
FRAME butt-frame:HIDDEN = TRUE.
FOR EACH order WHERE MONTH(order-date) = report-month AND
YEAR(order-date) = report-year:
FRAME butt-frame:HEIGHT-CHARS = TRUNC(num-buts / 5, 0) + 3.
CREATE BUTTON temp-hand IN WIDGET-POOL "order-pool"
ASSIGN LABEL = STRING(order-num) + "/" + STRING(cust-num)
FRAME = FRAME butt-frame:HANDLE
ROW = TRUNC(num-buts / 5, 0) + 1
COLUMN = ((num-buts MOD 5) * 15) + 1
SENSITIVE = TRUE
TRIGGERS:
ON CHOOSE
PERSISTENT RUN disp-order.
END TRIGGERS.
num-buts = num-buts + 1.
END.
FRAME butt-frame:HIDDEN = FALSE.
IF num-buts = 0
THEN MESSAGE "No orders found for" STRING(report-month) + "/" +
STRING(report-year) VIEW-AS ALERT-BOX INFORMATION BUTTONS OK.
END PROCEDURE.
PROCEDURE disp-order:
FORM
order
WITH FRAME order-frame SIDE-LABELS.
FIND order WHERE order-num =
INTEGER(SUBSTRING(SELF:LABEL, 1, INDEX(SELF:LABEL, "/") - 1)).
CREATE WINDOW temp-hand IN WIDGET-POOL "order-pool"
ASSIGN TITLE = "Order Detail"
VIRTUAL-HEIGHT-CHARS = FRAME order-frame:HEIGHT-CHARS.
CURRENT-WINDOW = temp-hand.
DISPLAY order WITH FRAME order-frame.
CURRENT-WINDOW = DEFAULT-WINDOW.
END PROCEDURE.
p-dybut3.p (2 of 2)
Progress Programming Handbook
2014
In this example, the widget pool is created within the internal procedure makebuttons. Because
the buttons must be available outside the internal procedure, the widget pool must be persistent;
otherwise the pool would be deleted at the end of the internal procedure. When you change the
month or year value, the GO trigger deletes the widget pool (and therefore deletes all the buttons
as well). It then runs makebuttons to create a new widget pool and a new set of buttons.
Note that the internal procedure, disp-order, creates a window in the persistent widget pool.
When you change the month or year, any windows so created are deleted along with the buttons.
When you use a persistent widget pool you must make sure the pool is deleted. In p-dybut3.p
the DO group surrounding the WAITFOR statement ensures that control will pass to the
DELETE WIDGETPOOL statement no matter how you exit the procedure.
20.2.2 Unnamed Widget Pools
Any unnamed widget pool you create becomes the default pool until it is deleted or until you
create another unnamed widget pool. Any unnamed pools you create are scoped to the routine
in which they are created. Also, any subprocedure or trigger inherits, as its default pool, the most
recent unnamed widget pool created in a calling procedure until (and unless) it creates an
unnamed widget pool of its own. When execution of a routine ends, or it goes out of scope, any
unnamed pools created in the routine (and therefore, all widgets in them) are automatically
deleted.
NOTE: A persistent procedure goes out of scope only when it is explicitly deleted. Thus,
unnamed widget pools created in it can persist as long as the procedure persists.
If you omit the IN WIDGETPOOL option in the statement that creates the widget, the widget
is automatically assigned to the most recently created unnamed widget pool that has not been
deleted.
You can delete the current default widget pool by using the DELETE WIDGETPOOL
statement without a pool name. You can delete the default widget pool only within the routine
that defined it. For example, if you define an unnamed widget pool in a procedure, you cannot
delete it in a subprocedure or trigger called by that procedure. If you attempt to do so, Progress
ignores the DELETE WIDGETPOOL statement.
You cannot specify the PERSISTENT option when you create an unnamed widget pool. All
unnamed pools, except the session pool and pools created in persistent procedures, are
nonpersistent.
Using Dynamic Widgets
2015
You might use an unnamed widget pool, for example, to ensure that all widgets created in the
default pool in a run procedure are deleted when that procedure returns or goes out of scope. For
example, the following procedure calls p-dybuts.p (see Chapter 16, Widgets and Handles):
In this example, the CREATE WIDGETPOOL statement creates a new default widget pool.
Any widgets created in the default pool within p-dybuts.p are placed in this pool. After
p-dybuts.p completes, the DELETE WIDGETPOOL statement deletes that widget pool.
On the other hand, in a persistent procedure, you can use an unnamed widget pool to ensure that
its dynamic widgets are not deleted after it returns from instantiating its persistent context.
Otherwise, if the calling procedure deletes the widget pool that was current when it created the
persistent procedure, any dynamic widgets created for the persistent context are deleted as well.
Thus, if you ran p-dybuts.p persistently in p-unname.p, creating an unnamed widget pool in
p-dybuts.p prevents p-unname.p from deleting the dynamic widgets created in the persistent
context of p-dybuts.p. For a working example, see the section on multiple-window
applications with persistent procedures in Chapter 21, Windows.
20.3 Creating and Using Dynamic Queries
The static query is one of the basic methods of fetching database records. For more information
on static queries, see Defining a Set of Records to Fetch in Chapter 9, Database Access.
This section describes programming and using the dynamic querya query whose elements are
resolved at runtime.
Progress supports creating and using dynamic queries by providing query, buffer, and
buffer-field objects.
p-unname.p
CREATE WIDGET-POOL.
RUN p-dybuts.p.
DELETE WIDGET-POOL.
Progress Programming Handbook
2016
20.3.1 Creating a Query Object
The query object corresponds to an underlying Progress query, which can be static or dynamic.
A static query is one you define at compile time using the DEFINE QUERY statement. A
dynamic query is one you create at run time using the QUERY option of the CREATE Widget
statement.
The query object, like other Progress objects, is represented by a variable of type HANDLE.
You can use a HANDLE variable to acquire a new query object for a dynamically-created
query, as the following code fragment demonstrates:
You can also use a HANDLE variable to acquire the query object of an existing
statically-created query, as the following code fragment demonstrates:
DEFINE VARIABLE my-query-handle AS HANDLE.
CREATE QUERY my-query-handle.
...
DEFINE VARIABLE my-query-handle AS HANDLE.
DEFINE QUERY q1 FOR customer.
my-query-handle = QUERY q1:HANDLE.
...
Using Dynamic Widgets
2017
20.3.2 Using a Query Object
Using a dynamic query requires using several methods in addition to the CREATE QUERY
statement. The CREATE QUERY statement does not define what database table or buffer you
wish to access or what subset of records you want to fetch. The SETBUFFERS( ) method and
the QUERYPREPARE( ) method specify this information and the QUERYOPEN( ) method
opens the query.
The following code fragment depicts one way of creating and using a dynamic query:
For a list of the attributes and methods of the query object, see the Query Object Handle
reference entry in the Progress Language Reference. For a complete description of the attributes
and methods, see the Attributes and Methods Reference chapter of the same book.
DEFINE VARIABLE qh8 AS WIDGET-HANDLE.
CREATE QUERY qh8.
qh8:SET-BUFFERS(BUFFER invoice:HANDLE).
qh8:QUERY-PREPARE("FOR EACH invoice BY invoice-date").
qh8:QUERY-OPEN().
REPEAT WITH FRAME fr1:
qh8:GET-NEXT.
IF qh8:QUERY-OFF-END THEN LEAVE.
. . .
END.
.
.
.
DELETE OBJECT qh8.
Progress Programming Handbook
2018
20.3.3 The Buffer Object
The buffer object corresponds to an underlying Progress buffer, which can be static or dynamic.
A static buffer is one you define at compile time by using the DEFINE BUFFER statement, or
by implicitly referencing a table in a 4GL construct such as customer.cust-num. A dynamic
buffer is one you create at run time using the BUFFER option of the CREATE Widget
statement.
The buffer object, like the query object and other Progress objects, is represented by a variable
of type HANDLE. You can use a HANDLE variable to acquire a new buffer object as the
following code fragment demonstrates:
You can also use a HANDLE variable to acquire a buffer object for an existing statically-created
buffer, as the following code fragment demonstrates:
With the buffer object you can:
Create a dynamic buffer for a dynamic query
Manipulate an existing static buffer
Write generic 4GL code without knowing at compile time the precise names of the
databases, buffers, and fields
Determine schema properties, such as the names and values of individual buffer-fields, by
using dynamic buffer-fields and their methods
DEFINE VARIABLE my-buffer-handle AS HANDLE.
CREATE BUFFER my-buffer-handle FOR TABLE "mycusttab".
...
DEFINE VARIABLE my-buffer-handle AS HANDLE.
my-buffer-handle = BUFFER customer:HANDLE.
...
Using Dynamic Widgets
2019
The following program example demonstrates the use of buffer objects in a dynamic query.
Note the use of the dynamic predicate (WHERE and BY clauses) in the query:
When the program pauses, press the GO key (usually F2) to continue.
p-qryob1.p
/* p-qryob1.p - demonstrates buffer and query objects */
DEFINE VARIABLE qry1 AS HANDLE.
DEFINE VARIABLE wherev AS CHARACTER INITIAL "WHERE cust-num < 10".
DEFINE VARIABLE sortv AS CHARACTER INITIAL "BY sales-rep".
DEFINE VARIABLE bval AS LOGICAL.
DEFINE VARIABLE i AS INTEGER.
DEFINE VARIABLE bh AS WIDGET-HANDLE EXTENT 4.
CREATE QUERY qry1.
REPEAT:
UPDATE wherev FORMAT "x(70)"
LABEL "Enter WHERE and BY information." SKIP
sortv FORMAT "x(70)" NO-LABEL.
qry1:SET-BUFFERS(BUFFER customer:HANDLE, BUFFER order:HANDLE).
bval = qry1:QUERY-PREPARE("FOR EACH customer " + wherev +
", EACH order OF customer " + sortv).
REPEAT i = 1 TO qry1:NUM-BUFFERS:
bh[i] = qry1:GET-BUFFER-HANDLE(i).
DISPLAY bh[i]:NAME. /* display the buffer names */
END.
IF (bval = FALSE) THEN NEXT.
qry1:QUERY-OPEN.
REPEAT WITH FRAME fr1 ROW 8:
qry1:GET-NEXT.
IF (qry1:QUERY-OFF-END) THEN LEAVE.
DISPLAY cust.cust-num cust.name cust.sales-rep
cust.state order.order-num.
END.
qry1:QUERY-CLOSE.
END.
DELETE OBJECT qry1.
Progress Programming Handbook
2020
Notes on Dynamic Buffers
Since the dynamic buffer lets you manipulate database records at run time without the
benefit of the compilers security checking, Progress ensures that when you use a dynamic
buffer, you have the necessary read, write, create, and delete permissions. In addition, if
the table corresponding to a dynamic buffer specifies delete validation, Progress does not
let you delete instances of the table dynamically.
When you have finished using the buffer object, it is important to apply the
BUFFERRELEASE method, since Progress does not automatically release the object
until the underlying buffer object is deleted.
A dynamic buffer has the same scope as the widgetpool in which it was created. This
means that Progress automatically deletes a dynamic buffer object only when it deletes its
widgetpool. To delete a dynamic buffer object manually, you must use the DELETE
OBJECT statement.
If you place the phrase BUFFER name anywhere in a procedure file, where name
represents the name of a table, not necessarily the name of a buffer you defined using the
DEFINE statement, Progress scopes name as it would a free reference.
For a list of the attributes and methods of the buffer object, see the Buffer Object Handle
reference entry in the Progress Language Reference. For a complete description of the
attributes and methods, see the Attributes and Methods Reference chapter of the same
book.
20.3.4 The Buffer-field Object
The buffer-field object represents a field of a buffer. You do not create buffer-field objects.
Progress creates them automatically when you reference any field of a buffer object.
Using buffer-field objects lets you examine and modify the fields of a buffer and examine the
schema properties of the fields.
When your code accesses buffer-fields, Progress checks security permissions at run time.
Using Dynamic Widgets
2021
You can use HANDLE variables to retrieve the handle of a buffer-field object, as the following
code fragments demonstrate:
The following program example demonstrates using bufferfield objects and their
BUFFERVALUE, NAME and EXTENT attributes. Note that bufferfield objects can be used
without using query or buffer objects:
DEFINE VARIABLE my-buffer-handle as HANDLE.
DEFINE VARIABLE my-buffer-field-handle as HANDLE.
...
my-buffer-handle = BUFFER custx:HANDLE.
my-buffer-field-handle = my-buffer-handle:BUFFER-FIELD(3).
DEFINE VARIABLE my-buffer-field-handle as HANDLE.
...
my-buffer-field-handle = BUFFER customer:BUFFER-FIELD("cust-num").
p-qryob2.p
/* p-qryob2.p - demonstrates buffer and buffer-field objects */
DEFINE VARIABLE i AS INTEGER.
DEFINE VARIABLE arrayi AS INTEGER.
DEFINE VARIABLE bh AS WIDGET-HANDLE.
DEFINE VARIABLE fh AS WIDGET-HANDLE EXTENT 100.
DEFINE VARIABLE fhc AS HANDLE.
DEFINE BUFFER custx FOR customer.
FIND FIRST custx.
bh = BUFFER custx:HANDLE.
MESSAGE "Value of City field is: " city:BUFFER-VALUE IN BUFFER custx.
fhc = bh:BUFFER-FIELD("city").
MESSAGE "Name of City field is: " fhc:NAME.
DISPLAY bh:NAME.
REPEAT i = 1 TO bh:NUM-FIELDS TRANSACTION:
fh[i] = bh:BUFFER-FIELD(i).
IF i = 5 THEN fh[i]:BUFFER-VALUE = "new addr2".
DISPLAY fh[i]:NAME fh[i]:EXTENT.
arrayi = 0.
IF fh[i]:EXTENT > 0 THEN arrayi = 2.
DISPLAY STRING(fh[i]:BUFFER-VALUE(arrayi)) FORMAT "x(20)".
END.
Progress Programming Handbook
2022
Notes on Dynamic BufferFields
If you assign the UNKNOWN value (?) to the BUFFERVALUE attribute of a
bufferfield object, the value of BUFFERVALUE becomes the UNKNOWN value.
Similarly, if you assign the empty string to the BUFFERVALUE attribute of a
buffer-field object, the value of BUFFERVALUE becomes the UNKNOWN
valueunless the buffer-field object is CHARACTER, in which case the value becomes
the empty string. Conversely, if you assign the UNKNOWN value to the
BUFFERVALUE attribute of a buffer-field object, the value of its STRINGVALUE
attribute becomes the empty string.
For a list of the attributes and methods of the buffer-field object, see the Buffer-field
Object Handle reference entry in the Progress Language Reference. For a complete
description of the attributes and methods, see the Attributes and Methods Reference
chapter of the same book.
20.3.5 Error Handling
Each method of the query object, buffer object, and buffer-field object that can have errors
returns a value of type LOGICAL that you can test. If an error occurs, the method returns
FALSE, but does not automatically raise the error condition. Progress Software Corporation
(PSC) recommends that you test the return value of methods like QUERYPREPARE, where
an error causes subsequent OPENs, GETNEXTs, etc. to fail.
The following code fragments demonstrates checking the return value after the
QUERYPREPARE method:
The following code fragment demonstrates checking the return value after the QUERYOPEN
method:
DEFINE VARIABLE retval AS LOGICAL.
retval = q:QUERY-PREPARE("FOR EACH customer...").
IF retval = FALSE THEN.../* error exit */
DEFINE VARIABLE retval AS LOGICAL.
retval = q:QUERY-OPEN().
IF retval = FALSE, THEN.../* error exit */
Using Dynamic Widgets
2023
20.3.6 Dynamic Query Code Example
The following program example demonstrates how you can change the predicate of a dynamic
query using radio-set input and populate a dynamic browse (covered in the next section) with
varying results-lists:
p-fnlqry.p (1 of 3)
CURRENT-WINDOW:ROW = 1.
CURRENT-WINDOW:HEIGHT-CHARS = 18.
CURRENT-WINDOW:WIDTH-CHARS = 132.
CURRENT-WINDOW:TITLE = "Dynamic Browse With Dynamic Query".
CURRENT-WINDOW:KEEP-FRAME-Z-ORDER = TRUE.
/* Test controls */
DEFINE BUTTON Make LABEL "CreateDynBrowse".
DEFINE BUTTON AddCols LABEL "Add Columns".
DEFINE BUTTON btn-delete LABEL "DeleteDynBrowse".
DEFINE BUTTON btn-quit LABEL "&Quit" AUTO-ENDKEY.
DEFINE VARIABLE query-criteria AS CHARACTER
VIEW-AS RADIO-SET VERTICAL
RADIO-BUTTONS
"All customers", "FOR EACH customer NO-LOCK", "USA customers",
"FOR EACH customer WHERE customer.country EQ USA NO-LOCK BY
customer.state ",
"Non-USA customers",
"FOR EACH customer WHERE customer.country NE USA NO-LOCK BY
customer.country "
SIZE 25 BY 3 TOOLTIP "Choose the customers you want to see"
INITIAL "FOR EACH customer NO-LOCK" NO-UNDO.
/* Widget and other Handles */
DEFINE VARIABLE Browse-Hndl AS WIDGET-HANDLE.
DEFINE VARIABLE bufFieldHandle AS WIDGET-HANDLE.
DEFINE VARIABLE qh AS WIDGET-HANDLE.
DEFINE VARIABLE bh AS WIDGET-HANDLE.
bh = BUFFER customer:HANDLE.
/* Create Query */
CREATE QUERY qh.
qh:SET-BUFFERS(bh).
DEFINE FRAME F1
skip(13)
make AddCols btn-delete btn-quit query-criteria
WITH SIZE 132 BY 18 THREE-D NO-LABELS.
Progress Programming Handbook
2024
ON CHOOSE OF make DO: /* LABEL "CreateDynBrowse". */
CREATE BROWSE Browse-Hndl
ASSIGN
FRAME = FRAME F1:HANDLE
QUERY = qh
TITLE = " "
X = 2
Y = 2
WIDTH = 130
DOWN = 12
VISIBLE = TRUE
SENSITIVE = TRUE
READ-ONLY = NO
COLUMN-SCROLLING = TRUE
SEPARATORS = YES.
END.
ON CHOOSE OF AddCols DO: /* LABEL "Add Columns" */
ASSIGN query-criteria.
qh:QUERY-PREPARE(query-criteria).
qh:QUERY-OPEN.
Browse-Hndl:ADD-LIKE-COLUMN(bh:BUFFER-FIELD(1),1).
Browse-Hndl:ADD-LIKE-COLUMN(bh:BUFFER-FIELD(3),2).
Browse-Hndl:ADD-LIKE-COLUMN(bh:BUFFER-FIELD("city"),3).
CASE query-criteria:
WHEN "for each customer NO-LOCK" THEN
DO:
Browse-Hndl:ADD-LIKE-COLUMN(bh:BUFFER-FIELD("state")).
Browse-Hndl:ADD-LIKE-COLUMN(bh:BUFFER-FIELD("country")).
Browse-Hndl:ADD-LIKE-COLUMN(bh:BUFFER-FIELD("postal-code")).
Browse-Hndl:TITLE = "All Customers".
END.
WHEN "FOR EACH customer WHERE customer.country EQ USA NO-LOCK BY
customer.state " THEN
DO:
Browse-Hndl:ADD-LIKE-COLUMN(bh:BUFFER-FIELD("state")).
Browse-Hndl:ADD-LIKE-COLUMN(bh:BUFFER-FIELD("postal-code")).
Browse-Hndl:TITLE = "All US Customers".
END.
WHEN "FOR EACH customer WHERE customer.country NE USA NO-LOCK BY
customer.country " THEN
DO:
Browse-Hndl:ADD-LIKE-COLUMN(bh:BUFFER-FIELD("country")).
Browse-Hndl:ADD-LIKE-COLUMN(bh:BUFFER-FIELD("postal-code")).
Browse-Hndl:TITLE = "All non-US Customers".
END.
END CASE.
p-fnlqry.p (2 of 3)
Using Dynamic Widgets
2025
20.4 Creating and Using a Dynamic Browse
The design and programming of the static browse was described in Chapter 10, Using the
Browse Widget. This section describes programming and using a dynamic browse, that is, a
browse whose elements are resolved at runtime.
The dynamic browse can be either a read-only or an updateable browse. If the dynamic browse
is updateable, it is a NOASSIGN browse and you must do all database updating manually. The
dynamic browse can be used with either a static or a dynamic query.
bufFieldHandle = bh:BUFFER-FIELD("balance").
Browse-Hndl:ADD-LIKE-COLUMN(bufFieldHandle).
AddCols:SENSITIVE = FALSE.
query-criteria:SENSITIVE = FALSE.
make:SENSITIVE = FALSE.
END.
ON CHOOSE OF btn-delete /* LABEL "DeleteDynBrowse". */
DO:
DELETE WIDGET Browse-Hndl.
AddCols:SENSITIVE = TRUE.
query-criteria:SENSITIVE = TRUE.
make:SENSITIVE = TRUE.
END.
ON CHOOSE OF btn-quit
DO:
APPLY "window-close" TO CURRENT-WINDOW.
END.
ENABLE ALL WITH FRAME F1.
WAIT-FOR CLOSE OF CURRENT-WINDOW.
p-fnlqry.p (3 of 3)
Progress Programming Handbook
2026
20.4.1 Creating the Dynamic Browse
A dynamic browse is one you create at runtime using the BROWSE option of the CREATE
Widget statement. Within the CREATE BROWSE statement, you can assign attributes to the
browse and specify its associated query.
The following code fragment depicts creating a dynamic browse:
DEFINE VARIABLE br1 AS HANDLE.
. . .
/* define query and define frame here */
CREATE BROWSE br1
ASSIGN FRAME = FRAME f1:HANDLE
X = 2
Y = 2
WIDTH = 80
DOWN = 10
QUERY = QUERY q1:HANDLE
TITLE = "Dynamic Browse"
SENSITIVE = TRUE
VISIBLE = TRUE
READ-ONLY = FALSE
SEPARATORS = TRUE.
. . .
Using Dynamic Widgets
2027
20.4.2 Creating Dynamic Browse Columns
The dynamic browse that you create with the CREATE BROWSE statement is an empty
browse. You must add the browse columns using the following methods:
ADDCOLUMNSFROM( )
ADDLIKECOLUMN( )
ADDCALCCOLUMN( )
ADDCOLUMNSFROM( ) Method
The ADDCOLUMNSFROM( ) method creates a browse column for every field in the
specified table or buffer except for any fields specified in an except-list. The specified table or
buffer must be in the associated query. Use this method when you want the browse to contain
all or most of the fields in a table or buffer.
The following program creates a browse which displays all the fields in the customer table
except for terms and comments:
p-dybrw1.p
DEFINE VARIABLE b1 AS HANDLE.
DEFINE QUERY q1 FOR customer EXCEPT (terms comments) SCROLLING.
OPEN QUERY q1 FOR EACH customer WHERE customer.cust-num < 10.
DEFINE FRAME f1
WITH SIZE 82 BY 29.
CREATE BROWSE b1
ASSIGN
FRAME = FRAME f1:HANDLE
X = 2
Y = 2
WIDTH = 80
DOWN = 10
QUERY = QUERY q1:HANDLE
TITLE = "Dynamic Browse with static query"
SENSITIVE = TRUE
VISIBLE = TRUE
READ-ONLY = FALSE
SEPARATORS = TRUE.
b1:ADD-COLUMNS-FROM("customer","terms,comments").
ENABLE ALL WITH FRAME f1.
WAIT-FOR CLOSE OF CURRENT-WINDOW.
Progress Programming Handbook
2028
ADDLIKECOLUMN( ) Method
The ADDLIKECOLUMN method creates a single browse column from the specified field
name or buffer field handle. You can also define the position of this column within the browse.
The specified field must be a field in one of the buffers of the associated query. Use this method
when you want the browse to contain only a few fields in a table or buffer.
The following program creates a browse containing only four fields from the customer table:
For other examples of the ADDLIKECOLUMN( ) method, see the program, p-fnlqry.p, in
section Dynamic Query Code Example.
p-dybrw2.p
DEFINE VARIABLE b1 AS HANDLE.
DEFINE QUERY q1 FOR customer
FIELDS (cust-num name phone contact) SCROLLING.
OPEN QUERY q1 FOR EACH customer WHERE customer.cust-num < 10.
DEFINE FRAME f1
WITH SIZE 82 BY 29.
CREATE BROWSE b1
ASSIGN
FRAME = FRAME f1:HANDLE
X = 2
Y = 2
WIDTH = 80
DOWN = 10
QUERY = QUERY q1:HANDLE
TITLE = "Dynamic Browse with static query"
SENSITIVE = TRUE
VISIBLE = TRUE
READ-ONLY = FALSE
SEPARATORS = TRUE.
b1:ADD-LIKE-COLUMN("customer.cust-num").
b1:ADD-LIKE-COLUMN("customer.name").
b1:ADD-LIKE-COLUMN("customer.phone").
b1:ADD-LIKE-COLUMN("customer.contact").
ENABLE ALL WITH FRAME f1.
WAIT-FOR CLOSE OF CURRENT-WINDOW.
Using Dynamic Widgets
2029
ADDCALCCOLUMN( ) Method
The ADDCALCCOLUMN( ) method creates a single browse column with the specified
properties rather than from a table or buffer field. This is typically used as a placeholder column
for a calculated value.
The following program creates a browse containing four fields from the customer table plus a
fifth field containing the calculated current credit limit:
p-dybrw3.p
DEFINE VARIABLE b1 AS HANDLE.
DEFINE VARIABLE calch AS HANDLE.
DEFINE QUERY q1 FOR customer
FIELDS (cust-num name phone contact credit-limit balance) SCROLLING.
OPEN QUERY q1 FOR EACH customer WHERE customer.cust-num < 30.
DEFINE FRAME f1
WITH SIZE 78 BY 18.
CREATE BROWSE b1
ASSIGN
FRAME = FRAME f1:HANDLE
X = 2
Y = 2
WIDTH = 76
DOWN = 15
QUERY = QUERY q1:HANDLE
TITLE = "Dynamic Browse with static query"
SENSITIVE = TRUE
VISIBLE = FALSE
READ-ONLY = FALSE
SEPARATORS = TRUE.
ON ROW-DISPLAY OF b1 DO:
IF VALID-HANDLE(calch) THEN
calch:SCREEN-VALUE=STRING(customer.credit-limit - customer.balance).
END.
b1:ADD-LIKE-COLUMN("customer.cust-num").
b1:ADD-LIKE-COLUMN("customer.name").
b1:ADD-LIKE-COLUMN("customer.phone").
b1:ADD-LIKE-COLUMN("customer.contact").
calch=b1:ADD-CALC-COLUMN("DECIMAL","->,>>>,>>9.99","0","CurrentLimit").
b1:VISIBLE = TRUE.
ENABLE ALL WITH FRAME f1.
WAIT-FOR CLOSE OF CURRENT-WINDOW.
Progress Programming Handbook
2030
Notes on Dynamic Browse Columns
You cannot specify the CANFIND function in the validation expression for a dynamic
browse-column. Since Progress compiles validation expressions at runtime for dynamic
browse-columns, CANFIND produces an error and the validation will not run for that
column. You can still use the CANFIND function for a static-browse column.
You can set the VISIBLE attribute for a browse-column as well as a browse.
You can change the SCREENVALUE attribute for a browse-column within the browses
ROWDISPLAY trigger as well as outside the trigger.
You must define the ROWDISPLAY trigger where the value of the calculated column is
set prior to using the ADDCALCCOLUMN( ) method so that the trigger is already set
before the column is added. This is to ensure that the initial viewport of the calculated
column is populated.
20.4.3 Dynamic Enhancements to the Static Browse
You can now dynamically create some or all of the columns for a static browse by using the
ADDCOLUMNSFROM( ), ADDLIKECOLUMN( ) and ADDCALCCOLUMN( )
methods. If you use the ADDCOLUMNSFROM( ) or ADDLIKECOLUMNS( ) methods
on a static browse, the browse becomes a NOASSIGN browse and you will have to manage
the database updates manually. See Chapter 10, Using the Browse Widget, for more
information on the static browse.
In addition, you can now change the query of a static browse even if the underlying fields are
not the same as those of the original query. However, if the new underlying fields are not the
same as the original, all browse columns will be removed and you will have to specify new
columns using the new ADDCOLUMNSFROM( ), ADDLIKECOLUMN( ) and
ADDCALCCOLUMN( ) methods. If the QUERY attribute is set to the UNKNOWN value
(?), all browsecolumns are removed.
Using Dynamic Widgets
2031
The following code example depicts an updateable static browse with all dynamic columns:
p-stbrw1.p
DEFINE BUTTON btn-quit LABEL "&Quit" AUTO-ENDKEY.
DEFINE VARIABLE i AS INTEGER.
DEFINE VARIABLE ColHandle AS HANDLE.
DEFINE QUERY q1 FOR customer SCROLLING.
OPEN QUERY q1 FOR EACH customer NO-LOCK.
DEFINE BROWSE StaticBrowse
QUERY q1
WITH TITLE "A Static Browse with Dynamic Columns"
MULTIPLE
SEPARATORS
SIZE 104 BY 10.
DEFINE FRAME f1
StaticBrowse SKIP(2)
btn-quit AT ROW 13 COLUMN 3
WITH THREE-D SIZE 105 BY 15 .
CURRENT-WINDOW:ROW = 1.
CURRENT-WINDOW:HEIGHT-CHARS = 16.
CURRENT-WINDOW:WIDTH-CHARS = 107.
CURRENT-WINDOW:TITLE = "A Static Browse with All Dynamic Columns".
CURRENT-WINDOW:KEEP-FRAME-Z-ORDER = TRUE.
StaticBrowse:READ-ONLY = FALSE.
StaticBrowse:ROW-MARKERS = TRUE.
StaticBrowse:ADD-LIKE-COLUMN("customer.state").
StaticBrowse:ADD-LIKE-COLUMN("customer.postal-code").
StaticBrowse:ADD-LIKE-COLUMN("customer.address").
StaticBrowse:ADD-LIKE-COLUMN("customer.address2").
StaticBrowse:ADD-LIKE-COLUMN("customer.terms").
REPEAT i=2 TO StaticBrowse:NUM-COLUMNS:
ColHandle = StaticBrowse:GET-BROWSE-COLUMN(i).
ColHandle:READ-ONLY = FALSE.
END.
ON CHOOSE OF btn-quit DO:
APPLY "window-close" TO CURRENT-WINDOW.
END.
ENABLE ALL WITH FRAME F1.
WAIT-FOR CLOSE OF CURRENT-WINDOW.
Progress Programming Handbook
2032
21
Windows
When you start a session, Progress automatically creates a static window for the session. You
can access this static window by using the DEFAULTWINDOW system handle. This static
window is also your initial current window, which you can access and reset using the
CURRENTWINDOW system handle.
The current window is the default session window for parenting frames, dialog boxes, and
messages. Depending on your user interface, you can create one or more dynamic windows,
using each one in turn as the current window. You can also specify a default window for the
current external procedure by assigning the widget handle of any window to the
CURRENTWINDOW attribute of the THISPROCEDURE handle. The setting of this
attribute overrides the setting of the CURRENTWINDOW handle for the context of the
current external procedure only. It has no effect on other external procedures in the session.
In a character interface, you can use only the static window for the entire session. In a graphical
interface, you can create multiple windows dynamically within an application and create parent
and child relationships between them. You can also change the appearance of any window,
including (with some restrictions) the static window.
This chapter describes:
Multiple window pros and cons
Window attributes and methods
Window events
Creating and managing windows
Window-based applications
Progress Programming Handbook
212
21.1 Multiple-window Pros and Cons
There are several reasons why you might want to use more than one window in an application:
Maximizing use of screen space The simplest reason for using an additional window
is to get more screen space. In some cases, you can avoid using a new window by enlarging
the existing window or overlaying frames.
Grouping functionality You can use multiple windows to group widgets that are
logically related. Each window can have its own menu bar with submenus related to those
specific widgets. You can also group related windows into window families, hierarchies
of parent and child windows.
Providing greater user control By placing information in multiple windows, you can
provide greater access to application functions. This also gives the user the ability to move
the windows (functionality) around the screen. The user can choose to put one window in
front of another or to minimize a window that is not currently needed.
Displaying multiple copies of the same frame You can only display a specific frame
once in a window. This can be a limitation, for example, if you want to show all the order
items associated with an order. You can define the frame once (in a subprocedure or
trigger) and view it in several windows simultaneously, populating each copy with
different data.
NOTE: You cannot enable fields for update in more than one copy of the same
frameeven if the copies are in different windows. If you want to be able to
update multiple copies of the frame, you must create separate frames for each
window.
There are several disadvantages to using multiple windows in an application:
Window management You must manage the windows yourself. Specifically, you
must ensure that windows are deleted when appropriate, usually when the controlling
procedure goes out of scope.
Widget access In a multi-window interface, one window may become concealed
behind another or inadvertently minimized, losing immediate access to application
widgets. This may cause confusion for the user.
Windows
213
Lack of interface portability Since Progress supports only a single window in
character interfaces, you cannot directly port a multi-window application to a character
interface.
Transaction management Even though you may have several windows on the screen,
you can only have one transaction active at a time. Undoing work in one window might
cause work in another window to be undone also.
21.1.1 Windows Versus Dialog Boxes
A dialog box is a type of frame that shares some of the visual characteristics of a separate
window. For example, the user can move a dialog box around the display outside its parent
window. However, a dialog box is always modal. That means that when the dialog box is
displayed, the user must react to that dialog box and cannot work in any other window until the
dialog box has been dismissed.
Windows are non-modal by default, and are not easily made modal. Thus, the user can move
freely from one window to another to enter input. If you want to enforce modality, use a dialog
box. For more information on dialog boxes, see Chapter 25, Interface Design.
21.1.2 Multiple Windows and Transactions
When an application uses multiple windows, the user may tend to assume that actions in one
window are totally independent of actions in another window. However, regardless of how
many windows are on the screen, only one transaction is active at any time. This means that you
cannot undo or commit work in one window without undoing or committing the work pending
in all other windows. You must design your application so that transaction scoping is clear and
intuitive to the user.
In multi-window applications, it simplifies transaction management if you can ensure that a
transaction started in one window is committed before the user gives focus to another window.
Thus, multi-window applications work more naturally with atomic transactions (those opened
and committed by a single user action or dialog box). However, your application must
ultimately determine your transaction management requirements.
Progress Programming Handbook
214
21.2 Window Attributes and Methods
You can set attributes for both the static window created by Progress and windows that you
create. You can, for example:
Specify the window title (TITLE attribute).
Set the normal, minimum, and maximum sizes of the window. You can do this in either of
two units:
In pixels (HEIGHTPIXELS, WIDTHPIXELS, MINHEIGHTPIXELS,
MINWIDTHPIXELS, MAXHEIGHTPIXELS, MAXWIDTHPIXELS
attributes).
In character units (HEIGHTROWSCHARS, WIDTHCOLUMNSCHARS,
MINHEIGHTCHARS, MINWIDTHCHARS, MAXHEIGHTCHARS,
MAXWIDTHCHARS attributes).
Specify the maximum display area within the window (VIRTUALHEIGHTCHARS,
VIRTUALWIDTHCHARS, VIRTUALHEIGHTPIXELS,
VIRTUALWIDTHPIXELS).
Allow or prevent the user resizing the window by setting the RESIZE attribute.
Specify the presence or absence of a window component or its appearance, such as
message area (MESSAGEAREA, MESSAGEAREAFONT), status area
(STATUSAREA, STATUSAREAFONT), or scroll bars (SCROLLBARS).
Change the state (minimized, maximized, restored) of a window within a program
(WINDOWSTATE attribute).
Associate a menu bar or pop-up menu with the window (MENUBAR or POPUPMENU
attribute).
Find all frames within the window (FIRSTCHILD or LASTCHILD attribute of the
window; NEXTSIBLING or PREVSIBLING attribute of frames).
Set up parent and child relationships between windows (PARENT attribute).
Windows
215
There are also several methods available on a window widget. The most common of these
methods that apply to windows are the LOADICON() and LOADSMALLICON() methods.
These methods allow you to associate icons with windows. The icon displays to reference a
window in one of its states such as minimized or maximized. For example, using the
LOADICON() method allows you to specify an icon to display in the title bar of a window
(maximized), in the task bar (minimized), and when selecting a program using ALTTAB. The
LOAD-SMALLICON() method allows you to specify an icon to display in the title bar of a
window and in the task bar only. The value you assign with either the LOADICON() or the
LOADSMALLICON() methods must be the name of an icon (.ico) file. Both of these
methods accommodate icons formatted as small size (16x16), regular size (32x32), or both.
For more information on these and other window attributes and methods, see the Progress
Language Reference.
Progress Programming Handbook
216
The following procedure uses attributes of the static window to make the window wide and
short and to change its title:
p-wina.p
OPEN QUERY custq FOR EACH customer.
DEFINE BROWSE custb QUERY custq DISPLAY cust-num name WITH 15 DOWN.
FORM
custb
WITH FRAME x.
FORM
customer
WITH FRAME y SIDE-LABELS COLUMN 40 ROW 3 WIDTH 75.
ASSIGN DEFAULT-WINDOW:VIRTUAL-WIDTH-CHARS = 120
DEFAULT-WINDOW:VIRTUAL-HEIGHT-CHARS = 15
DEFAULT-WINDOW:WIDTH-CHARS = 120
DEFAULT-WINDOW:HEIGHT-CHARS = 15
DEFAULT-WINDOW:TITLE = "Customer Browser".
ON ITERATION-CHANGED OF BROWSE custb
DO:
DISPLAY customer WITH FRAME y.
END.
ON WINDOW-CLOSE OF CURRENT-WINDOW
QUIT.
ENABLE custb WITH FRAME x.
APPLY "ITERATION-CHANGED" TO BROWSE custb.
ENABLE ALL WITH FRAME y.
WAIT-FOR WINDOW-CLOSE OF CURRENT-WINDOW.
Windows
217
When you run this code, the following window appears:
Progress Programming Handbook
218
21.3 Window Events
In addition to several standard events, windows respond to unique events of their own.
Several events indicate window state changes, including WINDOWMINIMIZED,
WINDOWMAXIMIZED, and WINDOWRESTORED.
The WINDOWRESIZED event occurs any time a keyboard or mouse action starts to change
the windows vertical or horizontal dimensions. Note that the window RESIZE attribute must
be set to TRUE for this event to occur.
The WINDOWCLOSE event occurs when the user tries to close a window. By default,
Progress takes no action when the WINDOWCLOSE event occursit does not close the
window. If you want your application to react to the WINDOWCLOSE event, you must code
a trigger for it or reference it in a WAITFOR statement. In fact, this is a typical termination
condition.
For more information on window events, see the Progress Language Reference.
21.4 Creating and Managing Windows
You can create a new window dynamically with the CREATE Widget statement.
This is the syntax for creating a window:
NOTE: Character interfaces support only a single window. If you attempt to create additional
windows, Progress raises the ERROR condition.
You must define handle as a WIDGETHANDLE variable, field, or parameter before creating
the window. Likewise, you must create the widget pool specified by poolname using a
CREATE WIDGETPOOL statement before you reference the widget pool in the CREATE
WINDOW statement. In some applications, you might prefer to use unnamed widget pools to
contain your windows. This is especially useful with windows created by persistent procedures,
where a single unnamed widget pool can encompass the entire dynamic context of the
procedure.
WAIT-FOR WINDOW-CLOSE OF CURRENT-WINDOW.
SYNTAX
CREATE WINDOW handle
[ IN WIDGET-POOL pool-name ]
[ ASSIGN attribute = value [ attribute = value ] ... ]
{ [ trigger-phrase ] }
Windows
219
One of the advantages of creating your own window is the ability to specify all of its attributes.
The ASSIGN option allows you to specify any window attribute you want, including those that
must be specified before realization. These include sizing and component attributes, such as
RESIZE and MESSAGEAREA.
You can specify user interface triggers for a window using either the triggerphrase or the ON
statement. The triggerphrase offers the convenience of a single window context, while the ON
statement offers the flexibility to define your triggers conditionally, after the window is created.
For more information on handles, attributes, and user interface triggers, see Chapter 16,
Widgets and Handles. For more information on widget pools, see Chapter 20, Using
Dynamic Widgets.
21.4.1 Creating Window Families
By default, when you create a window, Progress parents that window transparently to the
window system. In this way, windows are siblings of each other. You must manage these
windows individually.
You can also parent a window (child window) to another window (parent window) by setting
the child windows PARENT attribute to the widget handle of the parent window. Windows that
are parented by a window, which in turn is parented by the window system, form a window
family. The window parented by the window system is the root window of the window family.
Windows parented by any child window, in turn, form a child window family. A child window
can only be parented by one window at a time.
Progress Programming Handbook
2110
Window Families vs. Individual Windows
Each window in a window family, by itself, functions much the same as an individual window.
That is, you can individually move, resize, and interact with a member of a window family like
an individual window.
However, window families share a number of additional properties that make them convenient
for both applications and users to manage:
Coordinated viewing and hiding When you view any member of a window family,
the whole window family is viewed, unless the HIDDEN attribute is TRUE for at least one
other member. If HIDDEN is TRUE for a parent or ancestor window, no windows in the
window family are viewed, however any HIDDEN attribute setting for the explicitly
viewed window is set to FALSE. If HIDDEN is TRUE for a descendant window, all
windows in the family are viewed except the hidden descendant window and all of its
descendants. When you hide a member of a window family (set VISIBLE to FALSE), that
window and all of its descendant windows are hidden (VISIBLE set to FALSE), but none
of their HIDDEN attributes are affected.
Coordinated minimizing and restoring When you minimize (iconify) a window, all
of its descendants disappear from view, unless they are already minimized. Any
minimized descendants remain minimized and can be restored individually. When you
restore a parent window, any of its hidden descendants are redisplayed. Note that when
you hide a window (set VISIBLE to FALSE), any minimized descendants are hidden also,
and when you redisplay that window, its minimized descendants reappear minimized.
Coordinated close events If a parent window receives a WINDOWCLOSE event, it
propagates a PARENTWINDOWCLOSE event to all of its descendant windows.
However, any action on these events is trigger-dependant. The WINDOWCLOSE does
not propagate any events upward to ancestor windows.
For more information on window family properties, see the WINDOW Widget, HIDDEN
Attribute, and VISIBLE Attribute reference entries in the Progress Language Reference.
Windows
2111
Window Family Example
The following procedure creates a window family with three windowsa parent, child, and
grandchild window. By choosing the buttons in the default window (Control Panel), you can
visualize most of the properties described in the previous list.
p-wow1.p (1 of 2)
DEFINE VARIABLE whandle1 AS HANDLE.
DEFINE VARIABLE whandle2 AS HANDLE.
DEFINE VARIABLE whandle3 AS HANDLE.
DEFINE BUTTON bviewp LABEL "VIEW".
DEFINE BUTTON bviewc LABEL "VIEW".
DEFINE BUTTON bviewgc LABEL "VIEW".
DEFINE BUTTON bhidep LABEL "HIDE".
DEFINE BUTTON bhidec LABEL "HIDE".
DEFINE BUTTON bhidegc LABEL "HIDE".
DEFINE BUTTON bhiddp LABEL "HIDDEN".
DEFINE BUTTON bhiddc LABEL "HIDDEN".
DEFINE BUTTON bhiddgc LABEL "HIDDEN".
DEFINE FRAME alpha SKIP
"Parent" AT 11 "Child" AT 37 "Grand Child" AT 64 SKIP
bviewp AT 11 bviewc AT 37 bviewgc AT 64 SKIP(.5)
bhidep AT 11 bhidec AT 37 bhidegc AT 64 SKIP(.5)
bhiddp AT 11 bhiddc AT 37 bhiddgc AT 64
WITH SIZE 80 BY 6.
CREATE WINDOW whandle1
ASSIGN TITLE = "Parent Window"
HEIGHT-CHARS = 5
WIDTH-CHARS = 27
PARENT = CURRENT-WINDOW.
CREATE WINDOW whandle2
ASSIGN TITLE = "Child Window"
HEIGHT-CHARS = 5
WIDTH-CHARS = 27
PARENT = whandle1.
CREATE WINDOW whandle3
ASSIGN TITLE = "Grand Child Window"
HEIGHT-CHARS = 5
WIDTH-CHARS = 27
PARENT = whandle2.
ON CHOOSE OF bviewp DO: /* View Parent */
VIEW whandle1.
RUN win-status IN THIS-PROCEDURE.
END.
Progress Programming Handbook
2112
ON CHOOSE OF bhidep DO: /* Hide Parent */
HIDE whandle1.
RUN win-status IN THIS-PROCEDURE.
END.
ON CHOOSE OF bhiddp DO: /* Hidden Parent */
whandle1:HIDDEN = TRUE.
RUN win-status IN THIS-PROCEDURE.
END.
ON CHOOSE OF bviewc DO: /* View Child */
VIEW whandle2.
RUN win-status IN THIS-PROCEDURE.
END.
ON CHOOSE OF bhidec DO: /* Hide Child */
HIDE whandle2.
RUN win-status IN THIS-PROCEDURE.
END.
ON CHOOSE OF bhiddc DO: /* Hidden Child */
whandle2:HIDDEN = TRUE.
RUN win-status IN THIS-PROCEDURE.
END.
ON CHOOSE OF bviewgc DO: /* View Grand Child */
VIEW whandle3.
RUN win-status IN THIS-PROCEDURE.
END.
ON CHOOSE OF bhidegc DO: /* Hide Grand Child */
HIDE whandle3.
RUN win-status IN THIS-PROCEDURE.
END.
ON CHOOSE OF bhiddgc DO: /* Hidden Grand Child */
whandle3:HIDDEN = TRUE.
RUN win-status IN THIS-PROCEDURE.
END.
CURRENT-WINDOW:TITLE = "Control Panel".
CURRENT-WINDOW:HEIGHT-CHARS = 6.
ENABLE ALL IN WINDOW CURRENT-WINDOW WITH FRAME alpha.
WAIT-FOR WINDOW-CLOSE OF CURRENT-WINDOW.
PROCEDURE win-status:
MESSAGE "Parent HIDDEN:" whandle1:HIDDEN
"/ Parent VISIBLE:" whandle1:VISIBLE
"/ Child HIDDEN:" whandle2:HIDDEN
"/ Child VISIBLE:" whandle2:VISIBLE.
MESSAGE "Grand Child HIDDEN:" whandle3:HIDDEN
"/ Grand Child VISIBLE:" whandle3:VISIBLE.
END.
p-wow1.p (2 of 2)
Windows
2113
Figure 211 shows the Control Panel window and the example window family.
Figure 211: Window Family
Combine the VIEW, HIDE, and HIDDEN button events with the WINDOWMINIMIZE and
WINDOWRESTORE events using the window controls to see how window hiding and
viewing work together with window minimizing and restoring.
For an example that uses a window family in a database application, see the Persistent
Multi-window Management section.
Progress Programming Handbook
2114
21.5 Window-based Applications
Progress supports three types of interactive application:
Single-window Every interactive Progress application uses at least a single window.
Typically, you use the static window provided by Progress, although you can create your
own, instead, if you want to change any attributes set prior to realization. You can change
any attributes of the static window that can be set after realization.
Multi-window with non-persistent management You can create additional windows
for viewing or updating specific information using non-persistent procedures. In
non-persistent procedures, you can use multiple windows to group related information or
just to provide greater flexibility and more screen space. In a non-persistent environment,
you must manage each window explicitly throughout the application. Also, you cannot
easily manage multiple windows as copies or iterations of a single window. No matter how
similar the definition of each window is to another, each window requires separate
management code that must be integrated with the rest at every point in the application.
Multi-window with persistent management You can also create additional windows
using persistent procedures. This technique allows you to have more than one copy of a
like-defined window on the screen simultaneously, managed by the same application
code. Managing multiple windows with persistent procedures affords far greater
flexibility than using only non-persistent procedures.
Typically, a persistent procedure creates the window it controls when it executes. This
window then remains on screen and available for input after the procedure returns from
execution. Unlike windows created by non-persistent procedures, windows created in
persistent procedures require little or no management by the main line of your application.
The essential management code for each window is contained in the persistent procedure
that created it. Also, each time you execute a persistent procedure, it can create a
completely new window that is a copy of others created by the same procedure. Each copy
is managed by the same application code in its own procedure context. If you so design,
your main-line application can interact with the windows of these persistent procedures
through standard procedure triggers or internal procedures provided to the application by
the persistent procedures.
NOTE: You can also create multiple windows using persistent triggers in a
non-persistent procedure, but this is a less robust technique with more
complicated window management requirements.
As the single-window application is the default application style used throughout this manual,
the following sections describe the two basic techniques of managing multi-window
applications: non-persistent and persistent.
Windows
2115
21.5.1 Non-persistent Multi-window Management
The following procedure creates a concurrent window and stores its handle in the newwin
variable. It then preforms I/O to both the new window and the static window:
p-win1.p
DEFINE VARIABLE new-win AS WIDGET-HANDLE.
FORM
customer
WITH FRAME cust-frame.
FORM
salesrep
WITH FRAME rep-frame.
CREATE WINDOW new-win
ASSIGN TITLE = "Sales Representative".
ASSIGN DEFAULT-WINDOW:TITLE = "Customer".
VIEW new-win.
MESSAGE "This is the default window.".
MESSAGE "This is the newly created window." IN WINDOW new-win.
PAUSE 0 BEFORE-HIDE.
FOR EACH customer, salesrep OF customer ON ERROR UNDO, LEAVE
ON ENDKEY UNDO, LEAVE ON STOP UNDO, LEAVE:
DISPLAY customer WITH FRAME cust-frame.
ENABLE ALL WITH FRAME cust-frame.
CURRENT-WINDOW = new-win.
DISPLAY salesrep WITH FRAME rep-frame.
ENABLE ALL WITH FRAME rep-frame.
CURRENT-WINDOW = DEFAULT-WINDOW.
WAIT-FOR GO OF FRAME cust-frame, FRAME rep-frame.
ASSIGN customer.
ASSIGN salesrep.
END.
DELETE WIDGET new-win.
Progress Programming Handbook
2116
The p-win1.p procedure demonstrates two ways to direct I/O to a specific window: the IN
WINDOW option and the CURRENTWINDOW handle. Because the first MESSAGE
statement does not reference a window, it writes to the current window, which is initially the
static window for the session. The second message uses the IN WINDOW option to direct the
output to the newly created window. Within the FOR EACH group, the first DISPLAY
statement writes to the static window. Before the second DISPLAY statement, the
CURRENTWINDOW value is reset to the new window. Therefore, that DISPLAY statement
writes to the new window. The value of CURRENTWINDOW is then reset to the static
window. Note that the DISPLAY statements must use separate frames, because a frame can
only appear in one window at a time.
21.5.2 Persistent Multi-window Management
An application that uses persistent multi-window management generally provides its basic
application options in the default application window. These options then open one or more
windows under persistent procedure control to provide functionality in addition to the default
application window. All of these windows, while visible, remain available to the user in
non-modal fashion. You can also provide modal capabilities anywhere in the non-modal
interface using alert boxes and dialog boxes.
Creating Windows in Persistent Procedures
In general, you create and manage each persistent window using a persistent procedure. Each
time you run a procedure persistently, it instantiates (creates) a context for itself. This context
can include any windows that you create during instantiation. However, each persistent
procedure can maintain its own current window by setting the CURRENTWINDOW attribute
of the THISPROCEDURE handle. This attribute overrides (but does not change) the setting of
the CURRENTWINDOW handle. If set to the widget handle of a valid window, all frames and
dialog boxes defined in the procedure parent, by default, to the window specified by this
attribute. Thus, a persistent procedure instance usually manages a single window in the
multi-window application. For more information on procedure handles and persistent
procedures, see Chapter 3, Block Properties.
Managing Windows in Persistent Procedures
It is also often helpful to create a separate dynamic widget pool for the window you create in
the persistent procedure. Usually, you want the window to exist for the life of the persistent
procedure that manages it. The simplest way to guarantee this is by creating a single unnamed
widget pool for the procedure. When you delete a persistent procedure that maintains its own
widget pool, its persistent window is automatically deleted also. For more information on
dynamic widget pools, see Chapter 20, Using Dynamic Widgets.
Windows
2117
Example Persistent Multi-window Management
The following three procedures, p-perwn1.p, p-perwn2.p, and p-perwn3.p implement a small
application for updating customer orders in the sports database using multiple windows. The
main procedure (p-perwn1.p) displays a browse of the customer table in the default application
window. You can browse the order table for a selected customer by choosing the bOpenorders
button. This creates a persistent procedure (p-perwn2.p) that opens a child window for the order
browse (p-perwn1.p (1)):
p-perwn1.p (1 of 2)
DEFINE QUERY custq FOR customer.
DEFINE BROWSE custb QUERY custq
DISPLAY name cust-num balance credit-limit phone sales-rep
WITH 10 DOWN.
DEFINE BUTTON bExit LABEL "Exit".
DEFINE BUTTON bOpenorders LABEL "Open Orders".
DEFINE VARIABLE whand AS WIDGET-HANDLE.
DEFINE VARIABLE lcust-redundant AS LOGICAL.
DEFINE FRAME CustFrame
custb SKIP bExit bOpenorders
WITH SIZE-CHARS 96.5 by 11.
ON CHOOSE OF bExit IN FRAME CustFrame DO:
RUN exit-proc.
END.
ON CHOOSE OF bOpenorders IN FRAME CustFrame DO:
IF custb:NUM-SELECTED-ROWS >= 1 THEN DO:
RUN check-redundant(OUTPUT lcust-redundant).
IF NOT lcust-redundant THEN DO:
MESSAGE "Opening orders for" customer.name + ".".
(1) RUN p-perwn2.p PERSISTENT
(BUFFER customer, INPUT whand:TITLE,
INPUT whand) NO-ERROR.
END.
ELSE DO:
BELL.
MESSAGE "Orders already open for" customer.name + ".".
END.
END.
ELSE DO:
BELL.
MESSAGE "Select a customer to open orders ...".
END.
END.
Progress Programming Handbook
2118
(2) CREATE WINDOW whand
ASSIGN
TITLE = "Customer Order Maintenance"
SCROLL-BARS = FALSE
RESIZE = FALSE
HEIGHT-CHARS = FRAME CustFrame:HEIGHT-CHARS
WIDTH-CHARS = FRAME CustFrame:WIDTH-CHARS.
CURRENT-WINDOW = whand.
OPEN QUERY custq PRESELECT EACH customer BY name.
PAUSE 0 BEFORE-HIDE.
ENABLE ALL WITH FRAME CustFrame.
WAIT-FOR CHOOSE OF bExit IN FRAME CustFrame.
PROCEDURE exit-proc:
DEFINE VARIABLE phand AS HANDLE.
DEFINE VARIABLE nhand AS HANDLE.
MESSAGE "Exiting application ...".
(3) phand = SESSION:FIRST-PROCEDURE.
DO WHILE VALID-HANDLE(phand):
nhand = phand:NEXT-SIBLING.
IF LOOKUP(whand:TITLE, phand:PRIVATE-DATA) > 0 THEN
RUN destroy-query IN phand NO-ERROR.
phand = nhand.
END.
DELETE WIDGET whand.
END PROCEDURE.
PROCEDURE check-redundant:
DEFINE OUTPUT PARAMETER lcust-redundant AS LOGICAL INITIAL FALSE.
DEFINE VARIABLE phand AS HANDLE.
DEFINE VARIABLE nhand AS HANDLE.
(4) phand = SESSION:FIRST-PROCEDURE.
DO WHILE VALID-HANDLE(phand):
nhand = phand:NEXT-SIBLING.
IF LOOKUP(whand:TITLE, phand:PRIVATE-DATA) > 0 AND
LOOKUP(customer.name, phand:PRIVATE-DATA) > 0 THEN DO:
lcust-redundant = TRUE.
RETURN.
END.
phand = nhand.
END.
END PROCEDURE.
p-perwn1.p (2 of 2)
Windows
2119
You can repeatedly select another customer and choose the bOpenorders button to browse
orders for as many customers as you want. The customer and order browses displayed for every
customer all remain available to the user for input simultaneously.
This application makes ample use of procedure and SESSION handle attributes to help manage
the persistent windows of the application. The main procedure uses them in two ways:
To delete all persistent procedures (and their windows) when you terminate the application
To identify whether an order window has already been created for a selected customer so
that only one order browse is enabled for a customer at a time
When you terminate the application by choosing the bExit button, p-perwn1.p calls a local
internal procedure, exitproc. After locating the first persistent procedure instance, exitproc
loops through all persistent procedures in the session and deletes each one created by the
application as specified by the PRIVATEDATA procedure handle attribute (p-perwn1.p (3)).
Note that exitproc deletes each persistent procedure by calling the destroyquery procedure
owned by the persistent procedure. This ensures that each persistent procedure manages its own
resource clean-up and deletion.
When you attempt to open the order browse for a customer, p-perwn1.p also checks to see if
there is already an order browse open for that customer. It does this by looking for a persistent
procedure with the customer name as part of its private data (p-perwn1.p (4)).
Note also that p-perwn1.p creates its own default application window instead of using the static
window (p-perwn1.p (2)). This allows the setting of the SCROLLBARS and RESIZE window
attributes.
The persistent procedure, p-perwn2.p, displays a browse of all orders for the selected customer.
Like the customer browse, you can repeatedly select orders and choose the bOpenlines button
(which runs p-perwn3.p) to browse order lines in a child window of a selected order for as many
orders as you want (p-perwn2.p (1)). In p-perwn2.p, you can also update each order by
choosing the bUpdate button and close all orders for a customer by choosing the bClose button.
Note that as persistent procedures, both p-perwn2.p and p-perwn3.p are written to be run
non-persistently for testing or other application purposes. This is accomplished by making some
of the code conditionally executable based on the value of the PERSISTENT attribute of the
THISPROCEDURE system handle. For example, the bClose button in p-perwn2.p runs the
destroyquery procedure or just exits p-perwn2.p, depending on how it is run (p-perwn2.p (4)).
You can devise many variations on this approach. One possible variation includes using the
preprocessor to conditionally compile the PERSISTENT attribute tests based on whether you
are compiling for a development or production environment.
Progress Programming Handbook
2120
Each persistent procedure easily maintains the current window that it creates by setting the
CURRENTWINDOW attribute of the THISPROCEDURE handle (p-perwn2.p (3) and
p-perwn3.p (2)). Thus, there is no need to set and reset the CURRENTWINDOW handle:
p-perwn2.p (1 of 5)
DEFINE PARAMETER BUFFER custbuf FOR customer.
DEFINE INPUT PARAMETER appdata AS CHARACTER.
DEFINE INPUT PARAMETER wparent AS WIDGET-HANDLE.
DEFINE QUERY orderq FOR order.
DEFINE BROWSE orderb QUERY orderq
DISPLAY
order.order-num order.order-date
order.ship-date order.promise-date order.carrier
WITH 5 DOWN.
DEFINE BUTTON bClose LABEL "Close Orders".
DEFINE BUTTON bOpenlines LABEL "Open Order Lines".
DEFINE BUTTON bUpdate LABEL "Update Order".
DEFINE VARIABLE whand AS WIDGET-HANDLE.
DEFINE VARIABLE lorder-redundant AS LOGICAL.
DEFINE FRAME OrderFrame SKIP(.5)
custbuf.name COLON 11 VIEW-AS TEXT SKIP
custbuf.cust-num COLON 11 VIEW-AS TEXT SKIP(.5)
orderb SKIP
bClose bOpenlines bUpdate
WITH SIDE-LABELS SIZE-CHARS 65.8 by 9.
ON CHOOSE OF bOpenlines IN FRAME OrderFrame DO:
IF orderb:NUM-SELECTED-ROWS >= 1 THEN DO:
RUN check-redundant(OUTPUT lorder-redundant).
IF NOT lorder-redundant THEN DO:
MESSAGE "Opening order lines for order"
STRING(order.order-num) + ".".
(1) RUN p-perwn3.p PERSISTENT
(BUFFER order, INPUT THIS-PROCEDURE:PRIVATE-DATA,
INPUT whand) NO-ERROR.
END.
ELSE DO:
BELL.
MESSAGE "Order lines already open for order"
STRING(order.order-num) + ".".
END.
END.
ELSE DO:
BELL.
MESSAGE "Select an order to open order lines ...".
END.
END.
Windows
2121
ON CHOOSE OF bUpdate IN FRAME OrderFrame DO:
IF orderb:NUM-SELECTED-ROWS >= 1 THEN DO:
RUN update-order.
END.
ELSE DO:
BELL.
MESSAGE "Select an order to update ...".
END.
END.
(2) IF THIS-PROCEDURE:PERSISTENT THEN DO:
THIS-PROCEDURE:PRIVATE-DATA = appdata + "," + custbuf.name.
CREATE WIDGET-POOL.
END.
(3) CREATE WINDOW whand
ASSIGN
TITLE = "Orders for Customer ..."
PARENT = wparent
RESIZE = FALSE
SCROLL-BARS = FALSE
HEIGHT-CHARS = FRAME OrderFrame:HEIGHT-CHARS
WIDTH-CHARS = FRAME OrderFrame:WIDTH-CHARS.
THIS-PROCEDURE:CURRENT-WINDOW = whand.
OPEN QUERY orderq PRESELECT EACH order
WHERE order.cust-num = custbuf.cust-num BY order-num.
DISPLAY custbuf.cust-num custbuf.name WITH FRAME OrderFrame.
ENABLE ALL WITH FRAME OrderFrame.
(4) IF THIS-PROCEDURE:PERSISTENT THEN DO:
ON CHOOSE OF bClose IN FRAME OrderFrame DO:
RUN destroy-query.
END.
END.
ELSE DO:
WAIT-FOR CHOOSE OF bClose IN FRAME OrderFrame.
END.
p-perwn2.p (2 of 5)
Progress Programming Handbook
2122
PROCEDURE destroy-query:
DEFINE VARIABLE phand AS HANDLE.
DEFINE VARIABLE nhand AS HANDLE.
(5) MESSAGE "Exiting orders for" custbuf.name "...".
phand = SESSION:FIRST-PROCEDURE.
DO WHILE VALID-HANDLE(phand):
nhand = phand:NEXT-SIBLING.
IF LOOKUP(custbuf.name, phand:PRIVATE-DATA) > 0 AND
phand <> THIS-PROCEDURE THEN
RUN destroy-query IN phand NO-ERROR.
phand = nhand.
END.
DELETE PROCEDURE THIS-PROCEDURE NO-ERROR.
DELETE WIDGET-POOL.
END.
PROCEDURE check-redundant:
DEFINE OUTPUT PARAMETER lorder-redundant AS LOGICAL INITIAL FALSE.
DEFINE VARIABLE phand AS HANDLE.
DEFINE VARIABLE nhand AS HANDLE.
(6) phand = SESSION:FIRST-PROCEDURE.
DO WHILE VALID-HANDLE(phand):
nhand = phand:NEXT-SIBLING.
IF LOOKUP(appdata, phand:PRIVATE-DATA) > 0 AND
LOOKUP(STRING(order.order-num),
phand:PRIVATE-DATA) > 0 THEN DO:
lorder-redundant = TRUE.
RETURN.
END.
phand = nhand.
END.
END PROCEDURE.
PROCEDURE update-order:
DEFINE VARIABLE rid AS ROWID.
DEFINE VARIABLE choice AS LOGICAL.
DEFINE BUTTON bSave LABEL "Save Changes".
DEFINE BUTTON bCancel LABEL "Cancel".
p-perwn2.p (3 of 5)
Windows
2123
(7) DEFINE FRAME UpdateFrame SKIP(.5)
order.order-num COLON 12 VIEW-AS TEXT
order.sales-rep VIEW-AS TEXT "For..." VIEW-AS TEXT
custbuf.name VIEW-AS TEXT SKIP
custbuf.cust-num COLON 48 VIEW-AS TEXT SKIP(1) SPACE(1)
order.order-date order.ship-date order.promise-date
SKIP SPACE(1)
order.carrier order.instructions SKIP SPACE(1)
order.PO order.terms SKIP(1)
bSave bCancel
WITH TITLE "Update Order" SIDE-LABELS VIEW-AS DIALOG-BOX.
ON CHOOSE OF bSave IN FRAME UpdateFrame
OR GO OF FRAME UpdateFrame DO:
MESSAGE "Are you sure you want to save your changes?"
VIEW-AS ALERT-BOX QUESTION BUTTONS YES-NO
UPDATE choice AS LOGICAL.
CASE choice:
(8) WHEN TRUE THEN DO TRANSACTION ON ERROR UNDO, RETRY:
rid = ROWID(order).
FIND order WHERE ROWID(order) = rid EXCLUSIVE-LOCK.
ASSIGN
order.order-date
order.ship-date
order.promise-date
order.carrier
order.instructions
order.PO order.terms.
DISPLAY
order.order-num order.order-date
order.ship-date order.promise-date order.carrier
WITH BROWSE orderb.
APPLY "WINDOW-CLOSE" TO FRAME UpdateFrame.
END.
WHEN FALSE THEN DO:
MESSAGE "Changes not saved."
VIEW-AS ALERT-BOX INFORMATION BUTTONS OK.
RETURN NO-APPLY.
END.
END CASE.
END.
p-perwn2.p (4 of 5)
Progress Programming Handbook
2124
Two other important features of persistent management include setting the PRIVATEDATA
attribute and creating a widget pool so that the context of each persistent procedure can be more
easily managed as a unit (p-perwn2.p (2) and p-perwn3.p (1)).
Like p-perwn1.p, p-perwn2.p uses procedure and SESSION handle attributes:
To delete all persistent procedures (and their windows) created for customer orders when
you exit a customer order browse (p-perwn2.p (5))
To identify whether an orderline window has already been created for a selected order so
that only one orderline browse is enabled for an order at a time (p-perwn2.p (6))
ON CHOOSE OF bCancel DO:
MESSAGE
"Are you sure you want to cancel your current updates?"
VIEW-AS ALERT-BOX QUESTION BUTTONS YES-NO
UPDATE choice AS LOGICAL.
CASE choice:
WHEN TRUE THEN DO:
MESSAGE "Current updates cancelled."
VIEW-AS ALERT-BOX INFORMATION BUTTONS OK.
APPLY "WINDOW-CLOSE" TO FRAME UpdateFrame.
END.
WHEN FALSE THEN DO:
MESSAGE "Current update is continuing ..."
VIEW-AS ALERT-BOX INFORMATION BUTTONS OK.
END.
END CASE.
END.
DISPLAY
order.order-num order.sales-rep custbuf.name custbuf.cust-num
order.order-date order.ship-date order.promise-date
order.carrier order.instructions order.PO order.terms
WITH FRAME UpdateFrame IN WINDOW ACTIVE-WINDOW.
ENABLE
order.order-date order.ship-date order.promise-date
order.carrier order.instructions order.PO order.terms
bSave bCancel
WITH FRAME UpdateFrame IN WINDOW ACTIVE-WINDOW.
WAIT-FOR WINDOW-CLOSE OF FRAME UpdateFrame.
END PROCEDURE.
p-perwn2.p (5 of 5)
Windows
2125
Note that each persistent procedure has a unique destroyquery procedure. This allows any
other procedure to delete the persistent procedures under its control while allowing each
persistent procedure to manage its own area of control appropriately.
The persistent procedure, p-perwn3.p, browses and updates order lines very much like
p-perwn2.p browses and updates orders, except that it is at the bottom of the hierarchy of
persistent management. When you choose the bClose button in p-perwn3.p, destroyquery only
has to delete the current instance of p-perwn3.p (p-perwn3.p (3)). Depending on your
application, you can also use a management model that is more or less hierarchical. Because
Progress procedures are recursive, you can even create persistent procedures and windows
recursively. However, there is no necessary connection between a persistent context and its
recursive instance unless you establish it, for example, using the PRIVATEDATA attribute or
a shared temporary table.
Progress Programming Handbook
2126
p-perwn3.p (1 of 4)
DEFINE PARAMETER BUFFER orderbuf FOR order.
DEFINE INPUT PARAMETER appdata AS CHARACTER.
DEFINE INPUT PARAMETER wparent AS WIDGET-HANDLE.
DEFINE QUERY ordlineq FOR order-line, item.
DEFINE BROWSE ordlineb QUERY ordlineq
DISPLAY
order-line.line-num order-line.item-num item.item-name
order-line.price order-line.qty order-line.extended-price
order-line.discount order-line.backorder
WITH 5 DOWN.
DEFINE BUTTON bClose LABEL "Close Order Lines".
DEFINE BUTTON bUpdate LABEL "Update Order Line".
DEFINE VARIABLE whand AS WIDGET-HANDLE.
DEFINE FRAME OrdlineFrame SKIP(.5)
orderbuf.order-num COLON 12 VIEW-AS TEXT SKIP(.5)
ordlineb SKIP
bClose bUpdate
WITH SIDE-LABELS SIZE-CHARS 98.8 by 8.5.
ON CHOOSE OF bUpdate IN FRAME OrdlineFrame DO:
IF ordlineb:NUM-SELECTED-ROWS >= 1 THEN DO:
RUN update-order-line.
END.
ELSE DO:
BELL.
MESSAGE "Select an order line to update ...".
END.
END.
(1) IF THIS-PROCEDURE:PERSISTENT THEN DO:
THIS-PROCEDURE:PRIVATE-DATA = appdata + ","
+ STRING(orderbuf.order-num).
CREATE WIDGET-POOL.
END.
Windows
2127
(2) CREATE WINDOW whand
ASSIGN
TITLE = "Order Lines for Order ..."
PARENT = wparent
RESIZE = FALSE
SCROLL-BARS = FALSE
HEIGHT-CHARS = FRAME OrdlineFrame:HEIGHT-CHARS
WIDTH-CHARS = FRAME OrdlineFrame:WIDTH-CHARS.
THIS-PROCEDURE:CURRENT-WINDOW = whand.
OPEN QUERY ordlineq PRESELECT EACH order-line
WHERE order-line.order-num = orderbuf.order-num, EACH item
WHERE item.item-num = order-line.item-num
BY order-line.line-num.
DISPLAY orderbuf.order-num WITH FRAME OrdlineFrame.
ENABLE ALL WITH FRAME OrdlineFrame.
IF THIS-PROCEDURE:PERSISTENT THEN DO:
ON CHOOSE OF bClose IN FRAME OrdlineFrame DO:
RUN destroy-query.
END.
END.
ELSE DO:
WAIT-FOR CHOOSE OF bClose IN FRAME OrdlineFrame.
END.
PROCEDURE destroy-query:
MESSAGE "Exiting order lines for order"
STRING(orderbuf.order-num) "...".
(3) DELETE PROCEDURE THIS-PROCEDURE NO-ERROR.
DELETE WIDGET-POOL.
END.
PROCEDURE update-order-line:
DEFINE VARIABLE rid AS ROWID.
DEFINE VARIABLE choice AS LOGICAL.
DEFINE BUTTON bSave LABEL "Save Changes".
DEFINE BUTTON bCancel LABEL "Cancel".
(4) DEFINE FRAME UpdateFrame SKIP(.5)
orderbuf.order-num COLON 12 VIEW-AS TEXT
order-line.line-num VIEW-AS TEXT SKIP(1) SPACE(1)
order-line.qty order-line.discount order-line.backorder
SKIP(1)
bSave bCancel
WITH TITLE "Update Order Line" SIDE-LABELS VIEW-AS DIALOG-BOX.
p-perwn3.p (2 of 4)
Progress Programming Handbook
2128
ON CHOOSE OF bSave IN FRAME UpdateFrame
OR GO OF FRAME UpdateFrame DO:
MESSAGE "Are you sure you want to save your changes?"
VIEW-AS ALERT-BOX QUESTION BUTTONS YES-NO
UPDATE choice AS LOGICAL.
CASE choice:
(5) WHEN TRUE THEN DO TRANSACTION ON ERROR UNDO, RETRY:
rid = ROWID(order-line).
FIND order-line WHERE ROWID(order-line)
= rid EXCLUSIVE-LOCK.
ASSIGN
order-line.qty
order-line.discount
order-line.backorder
order-line.extended-price = INPUT order-line.qty
* order-line.price
* (1 - (INPUT order-line.discount * 0.01))
.
DISPLAY
order-line.qty order-line.discount
order-line.backorder order-line.extended-price
WITH BROWSE ordlineb.
APPLY "WINDOW-CLOSE" TO FRAME UpdateFrame.
END.
WHEN FALSE THEN DO:
MESSAGE "Changes not saved."
VIEW-AS ALERT-BOX INFORMATION BUTTONS OK.
RETURN NO-APPLY.
END.
END CASE.
END.
p-perwn3.p (3 of 4)
Windows
2129
To help the user manage their screen, the application organizes the windows for orders and
order lines into a window family, with the customer browse window as the root
(p-perwn1.p (1), p-perwn2.p (3), p-perwn2.p (1), and p-perwn3.p (2)). Thus, the user can
minimize the entire application or any order along with all of its child windows for order lines.
Another feature of this application is the use of dialog boxes to provide modal functionality in
an otherwise non-modal, multi-window application (p-perwn2.p (7) and p-perwn3.p (4)). The
p-perwn2.p and p-perwn3.p procedures each use a dialog box to update the order and
orderlines table, respectively. This can help to control transaction size during an update. In
these examples, the transaction size is limited to a single trigger block (p-perwn2.p (8) or
p-perwn3.p (5)). So, the update windows in this case could also be persistently managed
non-modal windows without affecting the scope of transactions.
ON CHOOSE OF bCancel DO:
MESSAGE
"Are you sure you want to cancel your current updates?"
VIEW-AS ALERT-BOX QUESTION BUTTONS YES-NO
UPDATE choice AS LOGICAL.
CASE choice:
WHEN TRUE THEN DO:
MESSAGE "Current updates cancelled."
VIEW-AS ALERT-BOX INFORMATION BUTTONS OK.
APPLY "WINDOW-CLOSE" TO FRAME UpdateFrame.
END.
WHEN FALSE THEN DO:
MESSAGE "Current update is continuing ..."
VIEW-AS ALERT-BOX INFORMATION BUTTONS OK.
END.
END CASE.
END.
(6) DISPLAY
orderbuf.order-num order-line.line-num order-line.qty
order-line.discount order-line.backorder
WITH FRAME UpdateFrame IN WINDOW ACTIVE-WINDOW.
ENABLE
order-line.qty order-line.discount order-line.backorder
bSave bCancel
WITH FRAME UpdateFrame IN WINDOW ACTIVE-WINDOW.
WAIT-FOR WINDOW-CLOSE OF FRAME UpdateFrame.
END PROCEDURE.
p-perwn3.p (4 of 4)
Progress Programming Handbook
2130
Note also that these dialog boxes are parented to the window specified by the
ACTIVEWINDOW system handle (for example, p-perwn3.p (6)). This handle specifies the
window that has received the most recent input focus in the application. Using this handle
guarantees that the dialog box appears in the Progress window where the user is working, even
if it is not the current window of the procedure that displays the dialog box. For more
information on the ACTIVEWINDOW handle, see Chapter 25, Interface Design.
NOTE: This example updates the qty and extendedprice field in the orderline table.
However, it does not update the corresponding balance field in the customer table,
or the onhand and allocated fields in the item table. You might want to add the
necessary update code to maintain your working sports database. For example, you
could update the customer balance field by passing the procedure handle of
p-perwn1.p down to p-perwn3.p and calling an internal procedure in p-perwn1.p
that updates the balance field in the customer record specified by orderbuf.custnum
(in p-perwn3.p). Calling an internal procedure in p-perwn1.p keeps the customer
table and browse management all together in p-perwn1.p.
22
Menus
Progress supports two kinds of menusmenu bars and pop-up menus. You can define either
type of menu statically or create them dynamically.
A menu bar is a horizontal bar displayed at the top of a window. The menu bar contains menu
titles (submenus) arranged horizontally. When you select a menu title, a pull-down menu
containing a vertically arranged list of items is displayed.
A pop-up menu is a menu that contains items arranged vertically and it is associated with a
widget. The pop-up menu is context sensitive and appears only when the user performs a
particular mouse or keyboard action while the widget has focus.
Progress Programming Handbook
222
22.1 Menu Types
A menu is a widget that contains a list of commands or functions that users frequently use. The
two kinds of static and dynamic menu widgets available are menu bars and pop-up menus.
22.1.1 Menu Bar
A menu bar is associated with a window. Each Progress window can have only one menu bar,
and that menu bar contains menu items know as menu titles. When the user selects a menu title,
a pull-down menu with one or more items may appear directly below the menu bar. Depending
on the application, nested pull-down menus may appear to the sides of menu items when you
select them.
Figure 221 shows a menu bar with two menu titles. When you select Edit, a pull-down menu
with three menu items appears below Edit. When you further choose the menu item Add, a
nested submenu with four menu items appears to the side of Add. The Static Menu section
describes how to select menu items using the mouse and the keyboard.
Figure 221: Window with a Menu Bar
Pull-down
Menu with
Menu Items
Nested
Submenu
Menu Bar
Menu Titles
Menus
223
In a graphical interface, you can use the mouse to pull down menus from the menu bar and
choose menu items. In a character interface, use the ENTERMENUBAR key function (usually
mapped to F3) to give focus to the menu bar. You can then use the left and right arrow keys to
position to a menu title. Press RETURN to pull down the menu and the up and down arrow keys
to position to a menu item. Press RETURN again to choose the menu item.
22.1.2 Pop-up Menu
A pop-up menu is associated with a widget. The pop-up menu can contain one or more items as
well as nested submenus. Figure 222 shows two button widgets. When you choose the button
Hi, a pop-up menu that contains five choosable menu items appears.
Figure 222: Button with a Pop-up Menu
In a graphical interface, you can press and hold the mouse MENU button to pop up a menu by
default or use SHIFTF10. To choose an item, move the mouse pointer to the item and click the
SELECT button. You can define a different mouse button to use by setting the MENUMOUSE
attribute of the menu.
In a character interface, use the key associated with the DEFAULTPOPUP key function
(usually ESCU; F4 on Windows). Use the up and down arrow keys to move within the menu,
and press RETURN to choose a menu item. You can use this keyboard method in both character
and graphical interfaces.
Pop-up Menu
with Items
Progress Programming Handbook
224
22.2 Menu Hierarchy
Progress allows you to generate static and dynamic menus according to the requirements of your
application. If you know up-front the kinds of menus you need and the exact number of menu
items and submenus in your menus, you can generate static menus. Conversely, if you dont
know how many menus you need or the number of items your menu will contain, or even what
is in your menus, you can create dynamic menus.
Whether you generate static or dynamic menus, the relationships remain the same between
menus, their owners, and their child widgets. Figure 223 shows the menu hierarchy.
Figure 223: Menu Hierarchy
At the top of the menu hierarchy is the menu bar or pop-up menu. Menus are the parents of
menu items and/or submenus. Each child submenu can, in turn, be the parent of menu items and
submenus. Your application can have multiple levels of nested submenus.
Note that menus themselves do not have parents; instead, they have owners. All widgets, except
images, rectangles, text, labels, menus, menu-items, and submenus, can be menu owners.
Windows are the only widgets that can own a menu bar. All the other widgets can own only a
pop-up menu.
Menu
Item
Menu
Item
Menu
Item
Menu
Item
Menu
Item
Menu
Item
Menu
Item
Menu
Item
Submenu
Menu
Item
Submenu Submenu Submenu
Menu
Item
Menu
Item
Menu
Item
Menu
Item
Menu
Item
Submenu
Menu
Item
Submenu Submenu Submenu
Owner Widget
(e.g., window)
Menu Bar
Pop-up Menu
Menus
225
22.3 Features for Menu Widgets
You can add the following features to your static and dynamic menus:
Nested submenus
Duplicate menus
Accelerators
Menu mnemonics
Enabling and disabling characteristics
Toggle-box items
Detailed descriptions of these features appear later in this chapter.
22.4 Defining Menus
This section describes static menus, how to define them, and how to add features (such as
accelerators, mnemonics, etc.) to them.
22.4.1 Static Menu Bars
The following sections describe how to set up a menu bar. They contain sample code that
generates the following menu bar.
Progress Programming Handbook
226
Setting Up a Static Menu Bar
Setting up a static menu bar is a three-part process:
1. Define the pull-down submenus.
2. Define the menu bar itself.
3. Assign the menu bar to a window.
The following sections describe this process in greater detail.
Defining Pull-down Submenus
To generate a submenu, you use the DEFINE SUBMENU statement. Each submenu must have
a unique name. Use the MENUITEM phrase to specify menu items, and use the LABEL option
to define the text for the menu item. If you omit LABEL, Progress displays the item handle
name by default. Progress lays out the menu items consecutively in a top-to-bottom order. The
following code fragment defines three submenus and their menu items.
The ACCELERATOR option specifies a key or key combination that the user can use to select
a menu item without having to pull down the menu. For more information on accelerators, see
the Menu Item Accelerators section.
The ampersand (&) before the letter x in "E&xit" causes x to be underlined when the menu is
displayed. The letter x is called a mnemonic. A mnemonic provides a way to access menu items
from the keyboard. For more information on mnemonics, see the Menu Mnemonics section.
DEFINE SUB-MENU topic
MENU-ITEM numbr LABEL "Cust-num"
MENU-ITEM addr LABEL "Address"
MENU-ITEM othrinfo LABEL "Other".
DEFINE SUB-MENU move
MENU-ITEM forward LABEL "Next" ACCELERATOR "PAGE-DOWN"
MENU-ITEM backward LABEL "Prev" ACCELERATOR "UP".
DEFINE SUB-MENU quitit
MENU-ITEM quit LABEL "E&xit".
Menus
227
To generate a help submenu, add the SUBMENUHELP phrase to the DEFINE SUBMENU
statement, as shown below.
For more information on the DEFINE SUBMENU statement, see the Progress Language
Reference.
Defining a Menu Bar
To generate a menu bar, you use the DEFINE MENU statement. You must explicitly specify
the MENUBAR phrase. You can add menu items and submenus to the menu bar.
In the following code, you assign the three previously defined submenus to the menu bar with
the SUBMENU phrase. You use the LABEL option to define the text for the submenu.
Progress lays out the submenus consecutively in left-to-right order on the menu bar.
NOTE: You can add submenus or menu items to existing dynamic menu structures at any
time. Progress appends the newly added widgets to the end of the list. See the
Dynamic Menus section.
For more information on the DEFINE MENU statement, see the Progress Language Reference.
DEFINE SUB-MENU userhelp SUB-MENU-HELP
MENU-ITEM message LABEL "Recent Messages"
MENU-ITEM keybd LABEL "Keyboard"
MENU-ITEM command LABEL "Menu Commands".
DEFINE MENU mbar MENUBAR
SUB-MENU topic LABEL "Topic"
SUB-MENU move LABEL "Move"
SUB-MENU quitit LABEL "E&xit"
SUB-MENU userhelp LABEL "Help".
Progress Programming Handbook
228
Assigning a Menu Bar to a Window
Now that you have set up a menu bar, you need to assign it to a window. You do so by setting
the windows MENUBAR attribute equal to the handle of the menu. For example, if you are
using the current window or default window, you can refer to it by using the
CURRENTWINDOW or DEFAULTWINDOW system handle.
If you are using a window that your application has created, you can refer to the windows
widget handle. See the following code fragment. At the end of the code, delete the window with
the DELETE WIDGET statement.
ASSIGN DEFAULT-WINDOW:MENUBAR = MENU mbar:HANDLE.
DEFINE VARIABLE mwin AS WIDGET-HANDLE.
.
.
.
CREATE WINDOW mwin
ASSIGN MENUBAR = MENUmbar:HANDLE.
.
.
.
DELETE WIDGET mwin.
Menus
229
Example of a Menu Bar with Pull-down Submenus
The p-bar.p procedure that follows contains most of the sample definition codes used in the
previous sections. The procedure defines a menu bar, mbar, which contains three pull-down
submenus. The handle of mbar is assigned to the current window. The ON statements define
triggers that execute when you select the corresponding menu items.
p-bar.p (1 of 2)
DEFINE SUB-MENU topic
MENU-ITEM numbr LABEL "Cust. Number"
MENU-ITEM addr LABEL "Address"
MENU-ITEM othrinfo LABEL "Other".
DEFINE SUB-MENU move
MENU-ITEM forward LABEL "NextRec" ACCELERATOR "PAGE-DOWN"
MENU-ITEM backward LABEL "PrevRec" ACCELERATOR "PAGE-UP".
DEFINE SUB-MENU quitit
MENU-ITEM quititem LABEL "E&xit".
DEFINE MENU mbar MENUBAR
SUB-MENU topic LABEL "Topic"
SUB-MENU move LABEL "Move"
SUB-MENU quitit LABEL "E&xit".
FIND FIRST customer.
DISPLAY customer.name LABEL "Customer Name" WITH FRAME name-frame.
ON CHOOSE OF MENU-ITEM numbr
DISPLAY customer.cust-num WITH FRAME num-frame ROW 6.
ON CHOOSE OF MENU-ITEM addr
DISPLAY customer.address customer.address2 customer.city
customer.state customer.country customer.postal-code
WITH FRAME addr-frame NO-LABELS COLUMN 25 ROW 6.
ON CHOOSE OF MENU-ITEM othrinfo
DISPLAY customer EXCEPT name cust-num address
address2 city state country postal-code
WITH FRAME oth-frame SIDE-LABELS ROW 11.
Progress Programming Handbook
2210
When you run this code, you see a menu bar at the top of the window and a frame that displays
the name of the first customer. The menu bar contains three titles: Topic, Move, and Exit. You
can select each title, one at a time, and pull down its menu to select a menu item.
The Exit menu has only one item. When you choose that item, the procedure ends.
ON CHOOSE OF MENU-ITEM forward
DO:
HIDE ALL NO-PAUSE.
CLEAR FRAME name-frame.
FIND NEXT customer NO-ERROR.
IF AVAILABLE(customer)
THEN DISPLAY customer.name WITH FRAME name-frame.
END.
ON CHOOSE OF MENU-ITEM backward
DO:
HIDE ALL NO-PAUSE.
CLEAR FRAME name-frame.
FIND PREV customer NO-ERROR.
IF AVAILABLE(customer)
THEN DISPLAY customer.name WITH FRAME name-frame.
END.
ASSIGN CURRENT-WINDOW:MENUBAR = MENU mbar:HANDLE.
WAIT-FOR CHOOSE OF MENU-ITEM quititem.
p-bar.p (2 of 2)
Menus
2211
22.4.2 Static Pop-up Menus
You set up a static pop-up menu by using the DEFINE MENU statement. By default, if you
dont specify the MENUBAR phrase, Progress treats this widget as a pop-up menu. To associate
a pop-up menu with a widget, you assign the handle of the menu to the widgets
POPUPMENU attribute.
The p-popup.p procedure displays a button that has an associated pop-up menu:
p-popup.p
DEFINE BUTTON hi LABEL "Hello".
DEFINE MENU popmenu TITLE "Button State"
MENU-ITEM ve LABEL "Hello"
MENU-ITEM vd LABEL "Howdy"
MENU-ITEM iv LABEL "Hey"
RULE
MENU-ITEM ep LABEL "Exclamation point" TOGGLE-BOX
RULE
MENU-ITEM ex LABEL "Exit".
FORM
hi AT ROW 4 COLUMN 5
WITH FRAME button-frame.
/* Set popmenu to be the pop-up menu for hi. */
ASSIGN hi:POPUP-MENU = MENU popmenu:HANDLE.
/* Define action for menu selections. */
ON CHOOSE OF MENU-ITEM ve, MENU-ITEM vd, MENU-ITEM iv
ASSIGN hi:LABEL IN FRAME button-frame = SELF:LABEL.
/* Define action for button selection. When the button is
selected, display the current button label as a message.
If Exclamation Point is checked, add an exclamation point
to the message; otherwise, add a period. */
ON CHOOSE OF hi
MESSAGE hi:LABEL IN FRAME button-frame +
(IF MENU-ITEM ep:CHECKED IN MENU popmenu THEN "!" ELSE ".").
/* Enable input on the button and wait for the
user to select Exit from menu. */
ENABLE hi WITH FRAME button-frame.
WAIT-FOR CHOOSE OF MENU-ITEM ex.
Progress Programming Handbook
2212
When you run this code, a button with a label Hello appears on the screen. When you pop up
the associated menu and choose any of the first three menu itemsHello, Howdy, or Heythe
label of the button changes accordingly to the value of the chosen item. Subsequently, when you
choose the button itself, the current value of the buttons label displays as a message at the
bottom of the window. The message ends in either a period or an exclamation point, depending
on the current state of the ep toggle-box menu item. See the Menu Toggle Boxes section for
more information.
22.5 Nested Submenus
When the user selects a menu item assigned with nested submenus, a pull-down submenu
appears next to it. Although your pull-down and pop-up menus can contain many levels of
nested submenus, you should limit them to three or less. Multiple levels of nested submenus
tend to clutter up the screen and can make it difficult for the user to access menu items.
The following screen shows a nested submenu. When you select the Add item (in the pull-down
menu of Edit), you can, while holding down the mouse SELECT button, pull another submenu
to the side. On Windows, submenus appear automatically as you drag down in the containing
menu.
Menus
2213
The following code fragment defines the menus shown in the previous screen. The code defines
a menu bar, mybar, that contains three submenus: myfile, mycolors, and myedit. The submenu
mycolors (1) is nested within the myedit submenu (2).
22.6 Enabling and Disabling Menu Items
You can selectively enable and disable menu items in your menus. When an item is enabled, it
is a valid selection. However, when an item is disabled, it appears grayed out and it cannot be
selected. For example, in the following code, when you select button b1 labeled Text Mode,
the first three items (which correspond to graphic commands) are disabled; the only item
enabled is the Text command. Conversely, when you select button b2 labeled Graphics Mode,
only the items for the graphic commands are enabled.
NOTE: The p-menu.p example produces a run-time warning in character interfaces because
it cannot create a dynamic window.
DEFINE SUB-MENU myfile
MENU-ITEM m1 LABEL "Save"
MENU-ITEM m2 LABEL "Save &As"
MENU-ITEM m3 LABEL "E&xit".
DEFINE SUB-MENU mycolors
MENU-ITEM m1 LABEL "Red"
MENU-ITEM m2 LABEL "Green"
MENU-ITEM m3 LABEL "Blue"
MENU-ITEM m4 LABEL "Black".
DEFINE SUB-MENU myedit
SUB-MENU mycolors LABEL "Add"
MENU-ITEM e1 LABEL "Delete"
MENU-ITEM e2 LABEL "Copy".
DEFINE MENU mybar MENUBAR
SUB-MENU myfile LABEL "File"
SUB-MENU myedit LABEL "Edit".
Submenu mycolors
nested in submenu
myedit
Progress Programming Handbook
2214
p-menu.p
DEFINE VARIABLE mywin AS WIDGET-HANDLE.
DEFINE SUB-MENU myfile
MENU-ITEM m1 LABEL "Save"
MENU-ITEM m2 LABEL "Save &As"
MENU-ITEM m3 LABEL "E&xit".
DEFINE SUB-MENU mycolors
MENU-ITEM m1 LABEL "Red"
MENU-ITEM m2 LABEL "Green"
MENU-ITEM m3 LABEL "Blue"
MENU-ITEM m4 LABEL "Black".
DEFINE SUB-MENU myedit
SUB-MENU mycolors LABEL "Add"
MENU-ITEM e1 LABEL "Delete"
MENU-ITEM e2 LABEL "Copy".
DEFINE MENU mybar MENUBAR
SUB-MENU myfile LABEL "File"
SUB-MENU myedit LABEL "Edit".
DEFINE BUTTON b1 LABEL "Text Mode".
DEFINE BUTTON b2 LABEL "Graphics Mode".
FORM
b1 at X 10 Y 120
b2 at x 120 Y 120
WITH FRAME x.
ON CHOOSE OF b1 IN FRAME x DO:
MENU-ITEM m1:SENSITIVE IN MENU mycolors = NO.
MENU-ITEM m2:SENSITIVE IN MENU mycolors = NO.
MENU-ITEM m3:SENSITIVE IN MENU mycolors = NO.
MENU-ITEM m4:SENSITIVE IN MENU mycolors = YES.
END.
ON CHOOSE OF b2 IN FRAME x DO:
MENU-ITEM m1:SENSITIVE IN MENU mycolors = YES.
MENU-ITEM m2:SENSITIVE IN MENU mycolors = YES.
MENU-ITEM m3:SENSITIVE IN MENU mycolors = YES.
MENU-ITEM m4:SENSITIVE IN MENU mycolors = NO.
END.
CREATE WINDOW mywin
ASSIGN MENUBAR = MENU mybar:HANDLE.
CURRENT-WINDOW = mywin.
ENABLE b1 b2 WITH FRAME x.
APPLY "CHOOSE" TO b1.
WAIT-FOR CHOOSE OF MENU-ITEM m3 IN MENU myfile.
DELETE WIDGET mywin.
Menu item names can
be identical in
different submenus
All menu items are
disabled except the
last one
If menu item names
are non-unique, use
IN MENU clause
to identify the
parent menu
Menus
2215
Note that the items in myfile and mycolors have identical names. Progress allows you to use
non-unique names for menu items as long as they belong to different submenus. To avoid
ambiguity when you subsequently refer to these items, use the IN MENU clause to identify the
parent menu of the item or the IN SUBMENU clause to identify the parent submenu of the
item. The IN MENU or IN SUBMENU clause is not necessary if the name of the menu item
is unique.
When you run this procedure, a window containing a menu bar and two buttons appears. If you
choose the Text Mode button and subsequently pull down the Edit menu, you see that only the
last item in the menu, Black, is enabled. The rest of the items (Red, Green, and Blue) associated
with the Graphics Mode button are grayed out. They are enabled when you select the Graphics
Mode button prior to accessing the Edit menu.
NOTE: There are situations where Progress cannot disambiguate a menu item. One is where
the same menu item occurs in a submenu that itself is duplicated in two menus, and
where the second menu also contains the same menu item. In that case Progress can
only reference the menu item in the first copy of the submenu. This is because
Progress cannot distinguish the three identical menu items contained in the first and
second copies of the submenu and also in the second menu.
22.7 Menu Toggle Boxes
You can specify the TOGGLEBOX option for selectable menu items in your pull-down menus
and pop-up menus. When you use this option, the user can alternately check and uncheck the
associated menu item by choosing it. The application can examine the CHECKED attribute for
the item to determine whether the item is currently checked or unchecked. You can also
initialize or change the condition of the menu item by setting the CHECKED attribute in the
program.
Progress Programming Handbook
2216
Depending on the interface, the state of the menu item may be shown visually to the user. For
example, on Windows, when you select the item Exclamation Point in p-popup.p, a check mark
appears, as shown in the following screen. When you subsequently choose the button, the
message ends with either an exclamation point or a period, depending on whether the
Exclamation Point item is highlighted.
When the user toggles a toggle-box item, Progress sends the VALUECHANGED event to the
menu item. Therefore, by defining a trigger on that event, you can take immediate action when
the user toggles the value.
22.8 Duplicating Menus
Once a menu or submenu is defined, you can make a duplicate of it and use it in another
submenu that you are defining with the LIKE phrase. This duplicate inherits all the properties
of the original menusuch as name, menu items, and definitional triggers. To avoid ambiguity
when you subsequently refer to menu items either in the original menu or in its duplicate, use
the IN MENU clause.
Menus
2217
The following code defines two buttons, each with a pop-up menu. The second pop-up menu is
defined to be like the first one and thereby inherits the formers characteristics.
p-popup2.p
DEFINE BUTTON hi1 LABEL "Hello".
DEFINE BUTTON hi2 LABEL "Hi".
DEFINE MENU popmenu1 TITLE "Button1 State"
MENU-ITEM ve LABEL "Hello"
MENU-ITEM vd LABEL "Howdy"
MENU-ITEM iv LABEL "Hey"
RULE
MENU-ITEM ep LABEL "Exclamation point" TOGGLE-BOX
RULE
MENU-ITEM ex LABEL "Exit".
DEFINE MENU popmenu2 TITLE "Button2 State" LIKE popmenu1.
FORM
hi1 AT x 30 Y 70
hi2 AT x 130 Y 70
WITH FRAME button-frame WIDTH 30.
/*Set popmenu1 and popmenu2 to be the pop-up menus for hi1 and hi2.*/
ASSIGN hi1:POPUP-MENU = MENU popmenu1:HANDLE
hi2:POPUP-MENU = MENU popmenu2:HANDLE.
/* Define action for menu selections. */
ON CHOOSE OF MENU-ITEM ve IN MENU popmenu1, MENU-ITEM vd IN MENU
popmenu1, MENU-ITEM iv IN MENU popmenu1
ASSIGN hi1:LABEL IN FRAME button-frame = SELF:LABEL.
ON CHOOSE OF MENU-ITEM ve IN MENU popmenu2, MENU-ITEM vd IN MENU
popmenu2, MENU-ITEM iv IN MENU popmenu2
ASSIGN hi2:LABEL IN FRAME button-frame = SELF:LABEL.
/* Define action for button selection. When the button is
selected, display the current button label as a message.
If Exclamation Point is checked, add an exclamation point
to the message; otherwise, add a period. */
ON CHOOSE OF hi1
MESSAGE hi1:LABEL IN FRAME button-frame +
(IF MENU-ITEM ep:CHECKED IN MENU popmenu1 THEN "!" ELSE ".").
ON CHOOSE OF hi2
MESSAGE hi2:LABEL IN FRAME button-frame +
(IF MENU-ITEM ep:CHECKED IN MENU popmenu2 THEN "!" ELSE ".").
/* Enable input on the button and wait
for the user to select Exit from menu. */
ENABLE hi1 hi2 WITH FRAME button-frame.
WAIT-FOR CHOOSE OF MENU-ITEM ex IN MENU popmenu1,
MENU-ITEM ex IN MENU popmenu2.
The IN MENU phrase
identifies the parent
menu of the item
The LIKE phrase
specifies the name
of the submenu you
want to duplicate
Progress Programming Handbook
2218
22.9 The MENUDROP Event
The MENUDROP event occurs every time you bring a pull-down menu or pop-up menu into
view. You can write a trigger on the MENUDROP event to enable or disable specific items
within the menu.
NOTE: To trigger this event for a menu widget, the menu widget must be a pop-up menu.
Otherwise, you can only trigger this event for a submenu widget.
CAUTION: Do not interact with the window manager from within a MENUDROP trigger.
Doing so causes the window manager to lose control of the system, forcing you to
reboot or restart the window manager. Actions to avoid include any window
system input or output. These include actions that can generate a warning or error
message, forcing window system output. Use the NOERROR option on
supported statements to help avoid this situation. Otherwise, check valid values,
especially for run-time resources like widget handles, to prevent Progress from
displaying unexpected messages.
22.10 Menu Item Accelerators
In both graphical and character interfaces, you can define an accelerator key for menu item. An
accelerator is a key or key combination that executes an item from a pull-down menu without
the user having to pull down the menu. Progress recognizes menu accelerators for the active
window, except during modal interactionsthat is, when the user is prompted by any type of
alert box or dialog box.
When the user presses an accelerator to invoke a menu item, Progress generates the
MENUDROP event for the menu, but does not display the menu. Also, for a standard menu
item Progress generates a CHOOSE event, and for a toggle box menu item Progress generates
a VALUECHANGED event.
22.10.1 Defining Accelerators
You can define an accelerator for a menu item by adding the ACCELERATOR option to the
menu item description.
This is the syntax for a menu accelerator:
In this syntax, the value keylabel must be a character-string constant that evaluates to a valid
Progress key label. You can modify the keylabel by specifying one or more of these
SYNTAX
ACCELERATOR keylabel
Menus
2219
keysSHIFT, CTRL, or ALT. For example, you can specify ALTF8", PAGEUP, etc. When
the user presses the specified key(s), the menu item is selected.
Note that Progress automatically adds the specified accelerator key to the menu item after the
label. You should not put the accelerator into the label. For example, if you specify "CTRLP,"
Progress adds CTRL+P to the menu item (CTRLP in character interfaces).
The following code fragment specifies two accelerator keys, CNTLN and CNTLP.
Progress displays the menu items as follows:
NOTE: Progress does not support accelerators for pop-up menu items. If you define them,
Progress ignores them.
DEFINE SUB-MENU move
MENU-ITEM forward LABEL "NextRec" ACCELERATOR "CNTL-N"
MENU-ITEM backward LABEL "PrevRec" ACCELERATOR "CNTL-P".
Progress Programming Handbook
2220
22.10.2 Accelerators in Character Interfaces
For character interfaces, the choice of key labels is limited by those keys available on your
keyboard. Thus, key labels are not necessarily portable between terminal types. For example,
some terminals have no F2 key, but might have a PF2 key or require a CTRL key combination to
emulate common F2 key functionality.
Specifying Portable Accelerators
For portability, you can use the KBLABEL function to specify the accelerator key label.
This allows you to use a portable Progress key function (such as HELP) to identify a valid key
label for the current keyboard (terminal type). The key label is one that is associated with the
key function for the terminal type in the current environment. On Windows, the current
environment might reside in the registry or in an initialization file. On UNIX, the current
environment resides in the PROTERMCAP file.
However, note that the current environment can (and often does) define more than one key label
for a given Progress key function. The KBLABEL function returns the first such definition
specified for the terminal type. For example, some terminal types define F2 and also ESC? as
the HELP key function. In this case, using KBLABEL(HELP) changes F2 to a menu item
accelerator (losing its HELP key function), but leaves ESC? as the one remaining HELP key
function.
Handling Invalid Accelerators
There are two types of invalid menu accelerators:
Those specified by using a key label that is unsupported by the terminal.
Those specified with supported key labels, but when invoked, generate unrecognized
keyboard codes.
If you specify an unsupported key label, say ALT in ALTF9, Progress substitutes a supported
key label in its place. Thus, Progress might substitute ESCF9 for ALTF9 as the accelerator
definition.
However, an accelerator like ESCF9 might not work at run time. In this case, ESCF9 generates
unrecognizable keyboard code sequences that fail to fire the appropriate Progress event. The
terminal might also indicate the failure with a beep or other warning signal.
When in doubt, specify the menu accelerator using the terminal-portable KBLABEL function
described earlier.
MENU-ITEM miHelp LABEL "Help Menu" ACCELERATOR KBLABEL("HELP").
Menus
2221
22.11 Menu Mnemonics
For quick access, the user can select an item from a pull-down menu or menu bar by pressing
ALT and one mnemonic character. Progress indicates the mnemonic character by underlining it
within the menu. For example, a menu bar might contain the entries File, Edit, and Exit. This
means that you can access the File menu by pressing ALTF, the Edit menu by pressing ALTE,
and the Exit menu by pressing ALTX.
NOTE: If you define a menu mnemonic key combination as an accelerator, the accelerator
takes precedence. For more information on key precedence, see Chapter 6,
Handling User Input.
In Progress, you can define mnemonics for items in a menu bar, pull-down menu, or pop-up
menu. To specify a mnemonic, insert an ampersand (&) before that letter in the label. For
example, to make x the mnemonic for Exit, you specify LABEL "E&xit". Note that in graphical
and character interfaces, the first character in an items label is the default mnemonic. You can
only specify one mnemonic for each label; do not precede more than one letter with an
ampersand.
To include a literal ampersand within a menu label, use two ampersands. For example, the label
"Undo && Restart" is displayed as Undo & Restart.
22.12 Dynamic Menus
Dynamic menus are similar to static menus in several ways:
They both share the same hierarchy of relationship to owners and descendents.
You can also add features such as toggle boxes, accelerators, mnemonicsall of which
were discussed in the Static Menu sectionto dynamic menus.
Dynamic menus are different from static menus in the following ways:
Dynamic menus are created during run time. Your application can create as many menu
items as needed or can add menu items to existing static or dynamic menus. In contrast,
static menus and menu items are fixed in quantity at compile time.
The syntax you use to generate dynamic menus, as well as to assign characteristics, are
different. Recall that to generate static menus, you use the DEFINE MENU and DEFINE
SUBMENU statements. For dynamic menus, you use the CREATE MENU, CREATE
SUBMENU, and CREATE MENUITEM statements.
To delete a dynamic widget, you use the DELETE WIDGET statement. When the
application deletes a dynamic widget, Progress automatically deletes all its children or
descendents.
Progress Programming Handbook
2222
22.12.1 Properties of Dynamic Menus
As discussed in the first part of this chapter, a menu bar has a window for an owner, while a
pop-up menu has an associated widget. The owner widget has a readable and setable
MENUBAR or POPUPMENU attribute that allows you to set or query the ownership status of
a widget. The menu widget has a read-only OWNER attribute that returns the widget handle of
the owner widget. You can reset the MENUBAR or POPUPMENU attribute to a different
menu widget handle at any point in your application. Progress automatically replaces the first
menu with the second after you reset that attribute.
If your application deletes a menu widget, Progress finds the owner widget that refers to it and
sets its ownership attribute to unknown (?). Progress then automatically deletes all the children
or descendents of that menu widget.
Like all dynamic widgets, the menu widgets go into a widget pool. You can specify a widget
pool with the IN WIDGETPOOL clause of the CREATE statement. If you do not specify a
widget pool, the menu widget is created in the default unnamed widget pool. Should you,
however, specify a widget pool that does not exist, Progress stops its execution of the procedure.
The menu widget exists until you explicitly delete it with the DELETE WIDGET statement or
until the widget pool is deleted.
You cannot set the geometry of menu, submenu, and menu item widgets; that is, you cannot set
characteristics such as X, Y, ROW, COLUMN, WIDTH, HEIGHT, HEIGHTROWS, and
WIDTHCOLUMNS. These characteristics are all fixed and set by Progress.
Menus
2223
22.12.2 Dynamic Menu Bar
The following sections contain steps on how to set up a dynamic menu bar, as well as a sample
code that generates the following menu bar and pull-down menu.
Setting Up a Dynamic Menu Bar
Setting up a dynamic menu bar is a three-part process:
1. Create the menu bar and assign it to a window.
2. Create submenus and attach them as children to the parent menu or to another submenu.
3. Create menu items and attach them to the submenu(s).
The following sections describe this process in detail.
Progress Programming Handbook
2224
Creating the Menu Bar
Recall that when you define a static menu widget (for example, a menu bar) you use the
DEFINE MENU statement and you must specify the MENUBAR attribute. Without the
MENUBAR attribute, Progress automatically treats the widget as a pop-up menu. For a
dynamic menu, the POPUPONLY attribute determines whether it is a menu bar or a pop-up
menu. To create a dynamic menu bar, you must do the following:
1 Define a variable for the menu as a widgethandle.
2 Use the CREATE MENU statement to create a menu widget. The POPUPONLY
attribute defaults to FALSE, so the menu is treated as a menu bar.
3 To display the menu bar, attach it to a window. Set the MENUBAR attribute of the
window equal to the widget handle of the menu.
The following code fragment defines a widget-handle variable, creates a dynamic menu, and
sets the MENUBAR attribute of the current window to mainbarptr.
Creating Submenus
To create a submenu, you use the CREATE SUBMENU statement. Each submenu must have
a unique label. Use the ASSIGN clause of the CREATE SUBMENU statement to set the
PARENT attribute equal to the widget handle of the menu or another submenu. You cannot
change the parent of submenus or menu items once you make the assignment. The only way you
can change the parent/child relationship is by deleting the child. Use the LABEL option to
define the text for the submenu.
Progress lays out dynamic menu widgets the same way it does static menu widgets. Submenus
are laid out consecutively in a left-to-right order on the menu bar; menu items are laid out
consecutively in a top-to-bottom order. If the application deletes a dynamic submenu or menu
item widget, Progress automatically shifts remaining widgets up or to the left. For example,
when a menu item is deleted, the remaining items are shifted up to fill the previously occupied
space. You can add submenus or menu items to existing menu structures at any time. Progress
appends the newly added widgets to the end of the list.
DEFINE VARIABLE main-bar-ptr AS WIDGET-HANDLE.
.
.
.
CREATE MENU main-bar-ptr.
CURRENT-WINDOW:MENUBAR = main-bar-ptr.
Menus
2225
The following code fragment creates a submenu srepmenptr and sets the parent attribute to
mainbarptr. The label of the submenu is Reps.
Creating Menu Items
Recall that when you create a menu item for a static menu widget, you use the MENUITEM
phrase to specify the menu item. However, for a dynamic menu, you must first define the
widget-handle variables, then use the CREATE MENUITEM statement to create each
individual menu item. Each menu item can be used in only one menu or submenu. Use the
ASSIGN clause of the CREATE MENUITEM statement to set the PARENT attribute of the
menu item equal to the widget handle of the menu or submenu. Use the LABEL option to define
the text for the menu item.
The following code fragment creates a menu item temphandptr, which is set to the parent
attribute of a previously defined submenu srepmenuptr. The label of the menu item is
salesrep.repname.
Each MENUITEM widget can be one of four different subtypes:
NORMAL A choosable menu item or toggle box item
READONLY A read-only menu item
SKIP A skip menu item
RULE A rule menu item
CREATE SUB-MENU srep-men-ptr
ASSIGN PARENT = main-bar-ptr
LABEL = "Reps".
CREATE MENU-ITEM temp-hand-ptr
ASSIGN PARENT = srep-men-ptr
LABEL = salesrep.rep-name
Progress Programming Handbook
2226
The default subtype is NORMAL, and only the first two subtypesNORMAL and
READONLYhave labels.
You must set the subtype before Progress realizes the menu item. Enter the subtype in uppercase
and in quotes, as shown in the following code fragment.
22.12.3 Querying Sibling Attributes
For both menus and submenus, your code can traverse all the menu items and nested submenus.
You query the FIRSTCHILD attribute of a menu or submenu, which returns the widget handle
of the first menu item or child submenu. The code then follows the NEXTSIBLING attribute
to the right, etc. You can achieve the same result by starting with the LASTCHILD attribute
and follow the PREVSIBLING attribute to the left. All these four attributes are read-only,
while the PARENT attribute is read-write. The p-dymenu.p procedure traverses the
FIRSTCHILD and NEXTSIBLING attributes of the Reps menu and disables the menu item
for each sales rep in the West region.
CREATE MENU-ITEM temp-hand-ptr
ASSIGN SUBTYPE = "RULE"
PARENT = srep-men-ptr.
Menus
2227
22.12.4 Example of a Dynamic Menu
The following procedure creates a menu bar with a pull-down menu that contains dynamically
created menu items of sales representatives names in the salesrep table:
p-dymenu.p (1 of 2)
DEFINE VARIABLE exit-item-ptr AS WIDGET-HANDLE.
DEFINE VARIABLE srep-menu-ptr AS WIDGET-HANDLE.
DEFINE VARIABLE main-bar-ptr AS WIDGET-HANDLE.
DEFINE VARIABLE temp-hand-ptr AS WIDGET-HANDLE.
FORM
salesrep.sales-rep rep-name salesrep.region month-quota
WITH FRAME x WITH SIDE-LABELS ROW 5 CENTERED.
VIEW FRAME x.
/* Create the main menu bar. */
CREATE MENU main-bar-ptr.
/* Create a pull-down menu to list all sales reps. */
CREATE SUB-MENU srep-menu-ptr
ASSIGN PARENT = main-bar-ptr
LABEL = "Reps".
/* Create a menu item for each record in the Salesrep table. */
FOR EACH Salesrep BY rep-name:
CREATE MENU-ITEM temp-hand-ptr
ASSIGN PARENT = srep-menu-ptr
LABEL = salesrep.rep-name
TRIGGERS:
ON CHOOSE
DO:
FIND FIRST salesrep WHERE rep-name = SELF:LABEL.
DISPLAY salesrep WITH FRAME x.
END.
END TRIGGERS.
END.
/* Add a rule to the srep-menu-ptr. */
CREATE MENU-ITEM temp-hand-ptr
ASSIGN SUBTYPE = "RULE"
PARENT = srep-menu-ptr.
Progress Programming Handbook
2228
When you run this procedure, a window containing a menu bar and a frame (to hold information
on the sales rep) appears. When you pull down the Reps menu, it displays menu items that are
created dynamically for each record in the salesrep table. The sales rep who services the West
region is grayed out. When you select one of the enabled sales reps, information on the sales rep
appears in the frame.
/* Add an exit item to the srep-menu-ptr. */
CREATE MENU-ITEM exit-item-ptr
ASSIGN PARENT = srep-menu-ptr
LABEL = "E&xit"
SENSITIVE = TRUE.
/* Set up the menu bar. */
CURRENT-WINDOW:MENUBAR = main-bar-ptr.
/* Disable menu items for all west coast sales reps.
To begin, find the first item in srep-men-ptr. */
temp-hand-ptr = srep-menu-ptr:FIRST-CHILD.test-items:
DO WHILE temp-hand-ptr <> ?:
/* Find the Salesrep record for this item (if any). */
IF temp-hand-ptr:SUBTYPE = "NORMAL"
THEN DO:
FIND FIRST salesrep WHERE salesrep.rep-name =
temp-hand-ptr:LABEL NO-ERROR.
/* Check if this rep is in the West region.
If so, disable the menu item. */
IF AVAILABLE(salesrep)
THEN IF salesrep.region = "West"
THEN temp-hand-ptr:SENSITIVE = FALSE.
END.
/* Find the next item in srep-men. */
temp-hand-ptr = temp-hand-ptr:NEXT-SIBLING.
END.
/* Wait for the user to select Exit. */
WAIT-FOR CHOOSE OF exit-item-ptr.
p-dymenu.p (2 of 2)
Menus
2229
22.12.5 Dynamic Pop-up Menus
Dynamic pop-up menus are associated with widgets. You set up a pop-up menu by using the
CREATE MENU statement. You must set the POPUPONLY attribute to TRUE. To associate
a pop-up menu with a widget, you assign the menu to the widgets POPUPMENU attribute.
The following procedure displays a button with a pop-up menu that is similar to the one seen in
the Static Pop-up Menus section. You can compare the two procedures to see the different
syntax used to create the dynamic pop-up menu:
p-dypop.p (1 of 2)
DEFINE BUTTON hi LABEL "Hello".
DEFINE VARIABLE pop-menu AS WIDGET-HANDLE.
DEFINE VARIABLE ve AS WIDGET-HANDLE.
DEFINE VARIABLE vd AS WIDGET-HANDLE.
DEFINE VARIABLE iv AS WIDGET-HANDLE.
DEFINE VARIABLE ep AS WIDGET-HANDLE.
DEFINE VARIABLE ex AS WIDGET-HANDLE.
DEFINE VARIABLE dummy-rule AS WIDGET-HANDLE.
FORM
hi AT x 30 y 70
WITH FRAME button-frame.
CREATE MENU pop-menu
ASSIGN POPUP-ONLY = TRUE
TITLE = "Button State".
CREATE MENU-ITEM ve
ASSIGN
PARENT = pop-menu
LABEL = "Hello".
CREATE MENU-ITEM vd
ASSIGN
PARENT = pop-menu
LABEL = "Howdy".
CREATE MENU-ITEM iv
ASSIGN
PARENT = pop-menu
LABEL = "Hey".
Progress Programming Handbook
2230
CREATE MENU-ITEM dummy-rule
ASSIGN
PARENT = pop-menu
SUBTYPE = "RULE".
CREATE MENU-ITEM ep
ASSIGN
PARENT = pop-menu
LABEL = "Exclamation point"
TOGGLE-BOX = yes.
/* Create another rule--okay to re-use same widget-handle,
because we arent going to ever refer to the first rule again. */
CREATE MENU-ITEM dummy-rule
ASSIGN
PARENT = pop-menu
SUBTYPE = "RULE".
CREATE MENU-ITEM ex
ASSIGN
PARENT = pop-menu
LABEL = "Exit".
/* Set pop-menu to be the popup menu for hi-but */
ASSIGN hi:POPUP-MENU = pop-menu.
/* Define action for menu selections */
ON CHOOSE OF ve, vd, iv
ASSIGN hi:LABEL IN FRAME button-frame = SELF:LABEL.
/* Define action for button selection. When the button is
selected, display the current button label as a message.
If Exclamation Point is checked, add an exclamation point
to the message; otherwise, add a period. */
ON CHOOSE of hi
MESSAGE hi:LABEL IN FRAME button-frame +
(IF ep:CHECKED THEN "!" ELSE ".").
/* Enable input on the button and wait
for the user to choose Exit from menu. */
ENABLE hi WITH FRAME button-frame.
WAIT-FOR CHOOSE of ex.
p-dypop.p (2 of 2)
23
Colors and Fonts
Progress allows you to control the colors and fonts that are displayed in a widget. The extent of
this control depends on your user interface and how you manage colors and fonts in your
application.
This chapter describes:
Making colors and fonts available to an application
Assigning colors and fonts to a widget
Color and font inheritance
Color in character interfaces
Color on Windows
Managing colors and fonts in graphical applications
Allowing the user to change colors and fonts
Accessing the current color and font tables
Retrieving and changing color and font definitions
Managing application environments
Progress Programming Handbook
232
23.1 Making Colors and Fonts Available to an Application
Progress applications typically access a subset of the systems colors and fonts. This subset is
specified in the following places:
On Windows, in the registry or in an initialization file
On UNIX, in the PROTERMCAP file.
These environment settings establish a one-to-one mapping between a range of integers and the
systems colors and fonts. These integers, in turn, correspond to entries in internal color and font
tables. For example, on Windows, Progress, as installed, maps the color red to the integer 4. To
assign red to a widget, an application assigns the value 4 to the appropriate widget attribute.
Thus, the widget is assigned the fourth color from the internal color table.
For graphical interfaces, you can specify up to 256 colors and 256 fonts. However, it is also
possible to define an arbitrary color, expanding beyond the 256 colors defined in the color table,
using the RGBVALUE function. The font for an ActiveX control is set through standard font
properties and has no programmatic relationship to the font table. For information about using
colors and fonts for ActiveX controls, see the chapter that discusses Active X controls in the
Progress External Program Interfaces manual.
For character interfaces, you can specify up to 128 colors. When Progress starts up, it loads the
color and font definitions from the specified environment into internal color and font tables. The
numbers for both your color and font definitions must increase by 1, starting from 0. Progress
stops loading its color or font table at the first skipped value.
For example, if colors 0, 1, 2, and 4 are defined, Progress loads only colors 0 through 2 because
color 3 is missing. If colors 1, 2, and 3 are defined, Progress loads no colors because the
definitions start after 0. The order of color and font definition does not matter; it is only
important that a complete sequence of color and font numbers is defined.
For more information on specifying and editing environments for graphical and character
interfaces, see the Progress Client Deployment Guide.
Colors and Fonts
233
23.1.1 Progress Default Colors
The environment that comes with your Progress installation specifies 16 colors. Table 231
shows these colors and the integers you use to reference them in an application. Note that for
backward compatibility, the 16 colors are the same as those available in Version 6 of Progress.
CAUTION: The Progress Application Development Environment (ADE) reserves these 16
colors (0 through 15) defined in your environment. If you change the mappings of
these colors, the ADE tools might not function properly. You can add your own
application colors beginning with number 16.
Table 231: Progress Default Colors
Color Number Windows RGB Values Actual Color Defined
0 0, 0, 0 Black
1 0, 0, 128 Dark blue
2 0, 128, 0 Dark green
3 0, 128, 128 Blue green
4 128, 0, 0 Red
5 128, 0, 128 Purple
6 128, 128, 0 Olive
7 128, 128, 128 Gray
8 192, 192, 192 Light gray
9 0, 0, 255 Blue
10 0, 255, 0 Green
11 0, 255, 255 Turquoise
12 255, 0, 0 Red
13 255, 0, 255 Pink
14 255, 255, 0 Yellow
15 255, 255, 255 White
Progress Programming Handbook
234
23.1.2 Progress Default Fonts
The default Progress environment specifies a set of fonts to be used with the Progress Tools. On
Windows, these fonts are MS Sans Serif (a proportional font) and Courier New (a fixed-space
font). If you have not added custom fonts to your environment, Progress uses these default fonts
for your application. Progress uses the default proportional font for alphanumeric data, and the
default fixed font for integer fields and for fields with format strings containing fill characters
(such as 9 or X).
CAUTION: The Progress ADE reserves the 8 fonts (0 through 7) defined in your environment.
If you change the mappings of these fonts, the ADE tools might not function
properly. You can add your own application fonts, beginning with number 8.
23.1.3 Application Colors and Fonts
For an example of application color and font definitions, see the environment installed with the
Progress Test Drive.
23.2 Assigning Colors and Fonts to a Widget
You can assign colors and fonts to a widget either in the widget definition statement or at run
time after the widget is displayed. Use the FGCOLOR, BGCOLOR, DCOLOR, PFCOLOR,
and FONT options at definition time and the FGCOLOR, BGCOLOR, DCOLOR, PFCOLOR,
and FONT attributes at run time.
Progress uses the foreground color you specify for any values that appear in the widget;
Progress uses the background color for the area around the widget values.
NOTE: For rectangle widgets, Progress uses the foreground color for the edge and the
background color to fill the interior.
Progress uses the font you specify for any text that appears within the widget.
Note that FGCOLOR, BGCOLOR, and FONT apply to graphical interfaces only, and
DCOLOR and PFCOLOR apply to character interfaces only. For more information on
specifying color in character interfaces, see theColor in Character Interfaces section.
Colors and Fonts
235
The p-clrfnt.p procedure demonstrates how to initialize colors and fonts at widget definition
and how to change them dynamically at run time:
p-clrfnt.p
DEFINE BUTTON quitbtn LABEL "QUIT" BGCOLOR 4 FGCOLOR 15.
DEFINE VARIABLE fgc_frm AS INTEGER BGCOLOR 15 FGCOLOR 0
VIEW-AS SLIDER MAX-VALUE 15 MIN-VALUE 0.
DEFINE VARIABLE bgc_frm AS INTEGER BGCOLOR 15 FGCOLOR 0
VIEW-AS SLIDER MAX-VALUE 15 MIN-VALUE 0.
DEFINE VARIABLE font_frm AS INTEGER FONT 1 VIEW-AS SLIDER
MAX-VALUE 15 MIN-VALUE 0.
FORM
SKIP (1) "Form Foreground" AT 7 SKIP
fgc_frm AT 6 SKIP (1)
"Form Background" AT 7 SKIP
bgc_frm AT 6 SKIP (1)
"Form Font" AT 10 skip
font_frm AT 6 SKIP (2)
quitbtn AT 12 SKIP (1)
WITH FRAME x TITLE "Color and Font Test"
NO-LABELS CENTERED ROW 2 WIDTH 50.
ASSIGN FRAME x:RULE-Y = 282
fgc_frm:MAX-VALUE = COLOR-TABLE:NUM-ENTRIES - 1
bgc_frm:MAX-VALUE = COLOR-TABLE:NUM-ENTRIES - 1
font_frm:MAX-VALUE = FONT-TABLE:NUM-ENTRIES - 1.
ON VALUE-CHANGED OF fgc_frm
FRAME x:FGCOLOR = INPUT FRAME x fgc_frm.
ON VALUE-CHANGED OF bgc_frm
FRAME x:BGCOLOR = INPUT FRAME x bgc_frm.
ON VALUE-CHANGED OF font_frm
FRAME x:FONT = INPUT FRAME x font_frm.
ENABLE ALL WITH FRAME x .
WAIT-FOR WINDOW-CLOSE OF CURRENT-WINDOW OR CHOOSE OF quitbtn.
Progress Programming Handbook
236
This procedure creates three sliders representing foreground color, background color, and font.
The maximum value of each slider is determined by examining the NUMENTRIES attributes
of the COLORTABLE and FONTTABLE system handles. As you move the appropriate
trackbar, the foreground color, background color, or font of the interface changes. Note that,
because the procedure does not explicitly set the size of any of the sliders, Progress resizes them
at run time as you change the font.
You can assign colors and fonts to all widgets, with the following exceptions:
On Windows, to assign colors to buttons, menus, and window titles, you must use the
Display Properties dialog box in the Control Panel. You cannot change these colors in your
Progress application.
In all environments, images and rectangles have no text and, therefore, cannot take a font
assignment.
For more information on the COLORTABLE and FONTTABLE handles, and on the
FGCOLOR, BGCOLOR, DCOLOR, PFCOLOR, and FONT options and attributes, see the
Progress Language Reference. For more information on using the COLORTABLE and
FONTTABLE handles for run-time color and font management, see the Accessing the
Current Color and Font Tables section in this chapter.
Colors and Fonts
237
23.3 Assigning Colors and Fonts to ActiveX Automation Objects and ActiveX
Controls
Properties associated with ActiveX Automation objects and ActiveX controls allow you to
define unique characteristics, such as color and font, for each object. For example, to set colors
for an ActiveX control, you need to set one or more of the color properties of the control to some
RGB value. There are three ways to obtain an RGB value. You can use the RGBVALUE
function, use the COLOR-TABLE:GET-RGB-VALUE( ) method, or you can get the value
from some color property of an ActiveX control or an ActiveXAutomation object. For fonts, an
ActiveX Automation Server or ActiveX control generally provide a Font object that allows you
to specify font properties.
For further information about assigning colors and fonts to ActiveX controls and ActiveX
Automation objects, see the Progress External Program Interfaces manual.
23.4 Color and Font Inheritance
If you do not specify colors or a font for a widget, Progress assigns colors and a font using the
following rules of precedence:
1. Field-level widgets take the colors and font of the containing frame.
2. Frame-level widgets take the default colors and fonts specified in the environment.
NOTE: In the environment, you can specify two default fonts but you cannot specify
default widget colors.
3. Otherwise, frame-level widgets take the default colors and fonts specified for the operating
system.
NOTE: On Windows, the default foreground color is color Window Text. For
two-dimensional widgets, the default background color is color Window. For
three-dimensional widgets, the default background color is color Button Face.
These colors are configurable using the Control Panel.
Note that frames do not inherit colors and fonts from the containing window. If you do not
specify the font for a frame, Progress uses the default font, not the font of the window. This is
because Progress determines the frame layout at compile time when the windows colors and
fonts (determined at run time) are not yet available.
Progress Programming Handbook
238
23.5 Color in Character Interfaces
Color in a character interface differs from color in a graphical interface in these ways:
In character interfaces, the BGCOLOR and FGCOLOR attributes have the unknown value
(?).
Each location in the color table contains a foreground/background color pair.
DCOLOR specifies a foreground/background color pair that a widget uses when it
displays data.
PFCOLOR specifies a foreground/background color pair that a widget uses when it
prompts for data.
When designing your interface, make sure that you specify different color pairs for DCOLOR
and PFCOLOR.
23.5.1 Widget States and Color
Frames, dialog boxes, and rectangles use only DCOLOR. For all other widgets, Progress
considers the widgets category and its state to determine when to use the display colors
(DCOLOR) and when to use the prompt-for colors (PFCOLOR). The categories are text input
widgets (fill-ins and editors) and selectable widgets (all other widgets). The widget states are
insensitive, sensitive, and focus.
Table 232 shows how Progress assigns colors based on the state and category of widget.
Note that for repaint, look, and usability reasons, text input widgets have the same colors for the
sensitive and focus states but different colors for the insensitive state. This is in contrast to
selectable widgets, which must change colors when they receive focus.
Table 232: Colors Used in Character Interfaces
Widget State Text Input Widget Selectable Widget
Insensitive DCOLOR DCOLOR
Sensitive PFCOLOR DCOLOR
Focus PFCOLOR PFCOLOR
Colors and Fonts
239
23.5.2 Color Specification
In character interfaces, Progress reserves color table locations 0 through 4 for the following
foreground/background color pairs:
Color 0 holds colors used as the NORMAL color by Progress.
Color 1 holds the colors used as the INPUT color by Progress. As installed, this is
underline mode.
Color 2 holds the colors used as the MESSAGE color by Progress. As installed, this is
reverse video.
Color 3 holds the colors used for high-intensity mode. This mode is not available for all
terminals.
Color 4 holds the colors used for blink mode. This mode is not available for all terminals.
By default, text-input widgets use the INPUT colors when in prompt-for mode. Note, however,
that when displaying an editor widget with scroll bars to display read-only text, using
NORMAL rather than INPUT for prompt-for mode might make more visual sense to the user.
In the PROTERMCAP file, you can specify additional application color pairs from color 5 to
127. For more information, see the Progress Client Deployment Guide.
23.6 Colors on Windows in Graphical Interfaces
On Windows in graphical interfaces, your system handles color differently depending on
whether it supports the Palette Manager.
23.6.1 Systems with the Palette Manager
If your system supports the Palette Manager, you can define a palette with some number of
simultaneous colors (usually 256). Of these colors, 20 are normally reserved for standard system
colors. The others are custom colors that you can define and modify. The exact numbers of color
definitions and reservations by the system depend on your display driver.
Managing Colors for Multiple Windows
Although you can define a separate palette for each window, sharing a single palette among
several windows maximizes environment efficiency and eliminates any color contention
between the sharing windows. For more information, see the Managing Application
Environments section.
Progress Programming Handbook
2310
Managing Colors for Bitmap Images
If you use bitmap images saved in 256color mode, Windows tries to create an individual color
map for the image each time it is realized in Progress. This has the following effects:
The background flashes as the color palette is loaded for each 256color image, unless the
color map is identical to the one previously loaded.
Performance degrades.
To reduce or eliminate these effects, convert your images from 256 colors to 16 colors or
recreate them with 16 colors at a time.
23.6.2 Systems without the Palette Manager
On systems that do not support the Palette Manager, Windows can only display 16 standard
colors. However, if you request another color, Windows tries to create that color by dithering;
that is, by filling a space with dots of different hues. This might work well, for example, in a
frame background. However, if a field-level widget, such as a fill-in or radio set, inherits a
dithered color, only one hue is used. The result might be undesirable. Therefore, avoid using
dithered colors for field-level widgets.
23.7 Managing Colors and Fonts in Graphical Applications
Graphical interfaces provide a variety of techniques to help manage color and font definitions
for an application. Using these techniques, Progress allows a graphical application to manage
colors and fonts at four levels of run-time operation:
1. User Access to the Color and Font Tables Allowing the user to change the definitions
for current dynamic color and font table entries using the SYSTEMDIALOG COLOR
and SYSTEMDIALOG FONT statements. For more information, see theAllowing the
User to Change Colors and Fonts section.
2. Procedure Access to the Color and Font Tables Examining and changing the
definitions for color table entries in the current environment using the COLORTABLE
handle, and examining height and width characteristics of font table entries in the current
environment using the FONTTABLE handle. Also, this section addresses setting the
definition for arbitrary colors using the RGBVALUE function. For more information, see
the Accessing the Current Color and Font Tables section.
Colors and Fonts
2311
3. Procedure Access to Color and Font Definitions Reading and writing color and font
definitions for the current environment using the GETKEYVALUE and
PUTKEYVALUE statements. For more information, see the Retrieving and Changing
Color and Font Definitions section.
4. Procedure Replacement of the Current Environment Replacing the applications
current color and font tables with tables from a different environment using the LOAD,
USE, and UNLOAD statements. For more information, see the Managing Application
Environments section.
23.8 Allowing the User to Change Colors and Fonts
In graphical interfaces, Progress allows you to invoke native common dialogs for color and font
selection in the 4GL using the SYSTEMDIALOG COLOR and the SYSTEMDIALOG
FONT statements. Using these dialogs, you can allow the user to choose a new value for a color
or font number. Once the user chooses a value, Progress stores the choice in the color or font
table location specified by the number. All visible widgets defined with the specified color or
font number immediately redisplay according to the user selection.
23.8.1 Establishing Dynamic Colors
You can use the SYSTEMDIALOG FONT statement to change any font in the font table.
However, you can use the SYSTEMDIALOG COLOR statement to update only those colors
that are dynamic. To make a color dynamic, use the COLORTABLE system handle.
In this example, n is the color table entry you want to make dynamic. For more information on
the COLORTABLE handle, see the Progress Language Reference and theAccessing the
Current Color and Font Tables section in this chapter.
DEFINE VARIABLE status-ok AS LOGICAL.
status-ok = COLOR-TABLE:SET-DYNAMIC(n, yes).
Progress Programming Handbook
2312
23.8.2 Color Dialog Box
Figure 231 shows the Windows system color dialog box.
Figure 231: Windows Color Dialog Box
The Windows color dialog provides several ways to specify a new color:
Custom Colors You can create a palette of 16 custom colors. To specify a color, you
select the color box from the 16 available custom colors. The specified color appears in
the ColorSolid box. To change a custom color, select one of the Basic Colors, or specify
a color by using the Numeric Colors or Visual Colors technique. Each change replaces an
existing color in the Custom Colors list.
Numeric Colors You can enter either the Hue, Sat (Saturation), and Lum (Luminosity)
values, or the RedGreenBlue (RGB) values in the fields provided. The specified color
appears in the Color|Solid box as you change each number.
Visual Colors You can visually choose the Hue and Sat values by pressing and holding
the mouse SELECT button and moving the mouse pointer in the large rainbow square to
the color you want. You can visually choose the Lum value by moving the luminosity
slider (at right) up or down. For each choice, the specified color appears in the Color|Solid
box as you move the mouse.
Colors and Fonts
2313
Clicking the OK button assigns the color that currently appears in the Color|Solid box of the
Windows dialog to the dynamic color number specified in your SYSTEMDIALOG COLOR
statement.
23.8.3 Color Dialog Example
The p-cdial1.p procedure opens the dialog box that allows you to change its own foreground
or background colors:
p-cdial1.p (1 of 2)
DEFINE VARIABLE FrontColor AS INTEGER INITIAL 16.
DEFINE VARIABLE BackColor AS INTEGER INITIAL 17.
DEFINE VARIABLE ColorSelect AS INTEGER INITIAL 16 VIEW-AS RADIO-SET
RADIO-BUTTONS "Foreground", 16, "Background", 17
HORIZONTAL.
DEFINE VARIABLE status-ok AS LOGICAL.
DEFINE BUTTON bOK LABEL "OK".
DEFINE BUTTON bCANCEL LABEL "CANCEL".
IF COLOR-TABLE:NUM-ENTRIES < 18 THEN
COLOR-TABLE:NUM-ENTRIES = 18.
status-ok = COLOR-TABLE:SET-DYNAMIC(16, TRUE).
IF NOT status-ok
THEN DO:
MESSAGE "Cannot make color 16 dynamic.".
RETURN.
END.
status-ok = COLOR-TABLE:SET-DYNAMIC(17, TRUE).
IF NOT status-ok
THEN DO:
MESSAGE "Cannot make color 17 dynamic.".
RETURN.
END.
Progress Programming Handbook
2314
When you run this procedure on Windows, the following frame appears:
If you choose or click the OK button, a color dialog box appears (see Figure 231) to assign a
new system color to the specified color number. Choosing CANCEL terminates the procedure
without any further color changes.
COLOR-TABLE:SET-RGB-VALUE(16,RGB-VALUE(0,0,0)).
COLOR-TABLE:SET-RGB-VALUE(17,RGB-VALUE(128,128,128)).
FORM
SKIP(0.5) SPACE(0.5)
ColorSelect SPACE(2) bOK SPACE(2) bCANCEL
SPACE(0.5) SKIP(0.5)
WITH FRAME fColor TITLE "Choose frame colors ..." FGCOLOR FrontColor
BGCOLOR BackColor VIEW-AS DIALOG-BOX.
ON CHOOSE OF bOK IN FRAME fColor
DO:
ASSIGN ColorSelect.
SYSTEM-DIALOG COLOR ColorSelect.
END.
ON CHOOSE OF bCANCEL IN FRAME fColor
STOP.
ENABLE ColorSelect bOK bCANCEL WITH FRAME fColor.
WAIT-FOR WINDOW-CLOSE OF FRAME fColor.
p-cdial1.p (2 of 2)
Colors and Fonts
2315
23.8.4 Saving a Modified Color
If you want to save any color definitions changed by the user, you can use the UPDATE option
of the SYSTEMDIALOG COLOR statement. This option sets a logical value to indicate
whether the user has changed the specified color. If the value is TRUE, you can then save the
new color definition using the PUTKEYVALUE statement.
23.8.5 Font Dialog
Figure 232 shows the native Windows system font dialog.
Figure 232: Windows Font Dialog Box
The Windows dialog box allows you to choose a single font by name and specify a style, size,
script, and optional cosmetic effect. A sample of each choice appears in the Sample box.
Progress Programming Handbook
2316
23.8.6 Font Dialog Example
The p-fdial1.p procedure opens the dialog box that allows you to separately change the font
of either its radio set or buttons to a custom font:
p-fdial1.p
IF FONT-TABLE:NUM-ENTRIES < 13 THEN
FONT-TABLE:NUM-ENTRIES = 13.
DEFINE VARIABLE RadioFont AS INTEGER INITIAL 11.
DEFINE VARIABLE ButtonFont AS INTEGER INITIAL 12.
DEFINE VARIABLE FontSelect AS INTEGER INITIAL 11
VIEW-AS RADIO-SET
RADIO-BUTTONS "Radio Font", 11, "Button Font", 12
FONT RadioFont.
DEFINE BUTTON bOK LABEL "OK" FONT ButtonFont.
DEFINE BUTTON bCANCEL LABEL "CANCEL" FONT ButtonFont.
FORM
SKIP(0.5) SPACE(0.5)
FontSelect SPACE(2) bOK SPACE(2) bCANCEL
SPACE(0.5) SKIP(0.5)
WITH FRAME fFont TITLE "Choose frame fonts ..."
VIEW-AS DIALOG-BOX.
ON CHOOSE OF bOK IN FRAME fFont
DO:
ASSIGN FontSelect.
SYSTEM-DIALOG FONT FontSelect.
END.
ON CHOOSE OF bCANCEL IN FRAME fFont STOP.
ENABLE FontSelect bOK bCANCEL WITH FRAME fFont.
WAIT-FOR WINDOW-CLOSE OF FRAME fFont.
Colors and Fonts
2317
When you run this procedure on Windows, the following dialog box appears.
When you run the procedure, the radio set displays in the default MS Sans Serif font and buttons
display in the default Courier New font. To change a font, choose the Radio Font (font 11) or
Button Font (font 12) radio item, then choose or click the OK button to open the font dialog box
shown in Figure 232. Choosing (or clicking) CANCEL terminates the procedure without any
further font changes.
To change the radio-set font selected in Figure 232 to bold 8-point Arial, set up the font dialog
as shown in Figure 233.
Figure 233: Changing Font 11 to Bold 8-Point Arial
Progress Programming Handbook
2318
After you choose OK, the Choose frame fonts dialog box reappears with the radio set in the new
font:
23.8.7 Saving a Modified Font
If you want to save any font definitions changed by the user, you can use the UPDATE option
of the SYSTEMDIALOG FONT statement. This option sets a logical value to indicate whether
the user has changed the specified font. If the value is TRUE, you can then save the new font
definition using the PUTKEYVALUE statement.
23.9 Accessing the Current Color and Font Tables
In graphical interfaces, you can get information about colors and fonts defined for the current
environment by querying attributes and methods of the COLORTABLE and FONTTABLE
system handles. You can also change the color configuration of the current environment using
additional methods of the COLORTABLE handle.
23.9.1 COLORTABLE Handle
The NUMENTRIES attribute sets and returns the number of colors available in the color table.
The following methods allow you to read existing color definitions and to set new definitions
for dynamic colors:
The GETREDVALUE(n), GETGREENVALUE(n), and GETBLUEVALUE(n)
methods let you read the red, blue, and green values for a specified color number. This
example gets the red value of the 16th color in the color table.
DEFINE VARIABLE red-val AS INTEGER.
red-val = COLOR-TABLE:GET-RED-VALUE(16)
Colors and Fonts
2319
You can determine whether a color is dynamic by reading the GETDYNAMIC(n)
method. You can make a color dynamic or nondynamic by using the
SETDYNAMIC(n, logical) method. This example makes color 16 dynamic if it is not
already set.
You can change the red, blue, and green values of a dynamic color by using the
SETREDVALUE(n, integer), SETGREENVALUE(n, integer), and
SETBLUEVALUE(n, integer) methods. Note that these methods change the effective
color values of a color number in the current color table. Thus, every visible widget set to
the specified color number changes to the corresponding colors immediately. However,
this does not change the color definitions originally specified in the current environment.
If you restart the application with the current environment, the original colors reappear.
This example sets color 16 to a blue value of 192.
There is an efficient alternative to calling the SETREDVALUE(n, integer),
SETGREENVALUE(n, integer), and SETBLUEVALUE(n, integer) methods to
change the RGB values of a dynamic color. You can specify the SETRGBVALUE()
method to substitute for all three of these methods. You can determine a combined RGB
value using the RGBVALUE function or by accessing a color property from an ActiveX
control.
The GETRGBVALUE() method returns an integer that represents a combination of the
red, green, and blue values associated with the nth entry. This combined RGB value is
most useful for setting colors in an ActiveX control.
For more information on the COLORTABLE handle, see the Progress Language Reference.
IF NOT COLOR-TABLE:GET-DYNAMIC(16) THEN
COLOR-TABLE:SET-DYNAMIC(16, TRUE).
COLOR-TABLE:SET-BLUE-VALUE(16, 192).
COLOR-TABLE:SET-RGB-VALUE (16, RGB-VALUE (128, 0, 128)).
Progress Programming Handbook
2320
23.9.2 FONTTABLE Handle
The NUMENTRIES attribute sets and returns the number of fonts available in the font table.
The FONTTABLE methods return information about font sizes. Each method takes an
optional font number as an argument:
GETTEXTHEIGHTCHARS( ) and GETTEXTHEIGHTPIXELS( ) return the
height of the default font in either character units or pixels. You can pass a font number to
these methods to get the height of a specific font. This example looks for the first font from
the start of the font table with a height of at least two character units.
GETTEXTWIDTHCHARS(string) and GETTEXTWIDTHPIXELS(string)
return the width of the passed string expression in the default font in either character units
or pixels. You can optionally pass a font number to these methods to get the width of the
string in a specific font. This example tests whether a title for a new window will fit based
on the font of the current window.
For more information on the FONTTABLE handle, see the Progress Language Reference.
DEFINE VARIABLE font-number AS INTEGER.
DEFINE VARIABLE max-count AS INTEGER.
max-count = FONT-TABLE:NUM-ENTRIES + 1.
REPEAT font-number = 1 to max-count:
IF font-number = max-count THEN LEAVE.
IF FONT-TABLE:GET-TEXT-HEIGHT-CHARS(font-number) >= 2 THEN LEAVE.
END.
DEFINE VARIABLE font-number AS INTEGER.
DEFINE VARIABLE in-title AS CHARACTER.
font-number = CURRENT-WINDOW:FONT.
SET in-title.
IF FONT-TABLE:GET-TEXT-WIDTH-CHARS(font-number, in-title) > 80 THEN
MESSAGE "Title" in-title "cannot fit in new window.".
Colors and Fonts
2321
23.10 Retrieving and Changing Color and Font Definitions
In graphical interfaces, you can retrieve and change the color and font definitions for your
current environment using the GETKEYVALUE and PUTKEYVALUE statements. These
statements read and write to the current environment.
23.10.1 Changing Resource Definitions
The GETKEYVALUE statement can read and the PUTKEYVALUE statement can change
the definition of any environment resource, including the definitions of colors and fonts stored
in the current environment. However, these statements by themselves do not affect the current
Progress environment and its color and font tables. To have any definitions that are created
using the PUTKEYVALUE statement take effect, you must replace the current environment
by reloading the current environment (see the Managing Application Environments section).
Portable Color and Font Definitions
The portable and most effective way to change color and font definitions is to first change the
definitions in the color and font tables, then use the PUTKEYVALUE statement to write the
new definitions from the modified tables. You can affect the color and font tables in the current
environment using the COLORTABLE and FONTTABLE handles (see the Accessing the
Current Color and Font Tables section), and you can allow the user to affect the current color
and font tables using the SYSTEMDIALOG COLOR and SYSTEMDIALOG FONT
statements (see the Allowing the User to Change Colors and Fonts section).
23.10.2 Using GETKEYVALUE and PUTKEYVALUE
The GETKEYVALUE and PUTKEYVALUE statements allow you to read or write a
specified value for any resource by accessing the registry or an initialization file. The registry
consists of sections called keys and subkeys arranged in a hierarchy. Keys and subkeys contain
value entries, each of which consists of a value name and value data. Initialization files, by
contrast, consist of a single level of sections. Sections contain entries, each of which consists of
a name, an equals sign (=), and a value.
For example, to retrieve the Windows definition for font 8 from the current environment, which
might be the registry or an initialization file, you might enter this statement. It returns the initial
environment definition for font 8 in the FontString variable.
DEFINE VARIABLE FontString AS CHARACTER FORMAT "x(128)".
GET-KEY-VALUE SECTION "Fonts" KEY "Font8" VALUE FontString.
Progress Programming Handbook
2322
To specify Times New Roman as the new definition for font 8 on Windows, you might enter
this statement. It sets the font8 parameter in the current environment.
Writing Portable Color and Font Definitions
To write portable color and font definitions directly from the current color and font tables, use
the PUTKEYVALUE statement with the COLOR or FONT option. For example, if you
allow the user to change color 8 during a session through the color common dialog
(SYSTEMDIALOG COLOR statement), you can save the new color definition in the current
environment using this statement.
You can save all current color definitions from the color table using this statement.
For more information on these statements, see the Progress Language Reference.
PUT-KEY-VALUE SECTION "fonts" KEY "font8" VALUE "Times New Roman".
PUT-KEY-VALUE COLOR 8.
PUT-KEY-VALUE COLOR ALL.
Colors and Fonts
2323
23.11 Managing Application Environments
For some graphical applications, you might want to replace whole Progress environments. This
applies especially to applications that build other applications, where you want to provide the
ability to test run each application in a separate environment. The Progress AppBuilder is an
example of such an application. You might also want to write a single application that has
greater flexibility to change its own environment (for example, changing the number of color
definitions available to the application).
23.11.1 Understanding Environment Management
When Progress starts up, it loads a default environment from the registry (Windows only) or
from an initialization file. This default environment is the initial current environment. The
initial current environment provides resources to the default window and any windows
subsequently created in that environment. At any point, you can replace the current environment
by loading and making a specified environment current.
23.11.2 Using Environment Management Statements
To replace the current environment with another application environment, Progress provides
these statements:
LOAD Loads a previously defined application environment into Progress memory, or
creates and loads an empty application environment to be defined. Generally, an
application environment resides in the Registry or in an initialization file with a
meaningful filename. You can load as many application environments as memory allows,
but only one environment can be current at a time.
USE Makes a previously loaded environment available as the current environment. The
new current environment has no effect on the default window or any previously created
windows. The resources of the new current environment are available only for windows
created after the new environment is made current.
UNLOAD Removes a loaded environment from Progress memory. Environments take
up very little memory and generally do not have to be unloaded. However, if you are
writing an application builder, this statement provides good housekeeping when you save
and remove an application from memory. This allows you to remove all memory
associated with a purged application.
Note that you must delete all windows created in an environment before you can unload
that environment, whether or not it is the current environment. If the unloaded
environment is the current environment, you must set a new current environment with the
USE statement before continuing operation.
Progress Programming Handbook
2324
For more information on these statements, see their reference entries in the Progress Language
Reference.
23.11.3 Managing Multiple Environments
If you are writing an application builder, you might want to maintain separate environments for
each application.
Typical Scenario
You can follow these steps to separate application environments:
1 Have the user select an existing application or create a new one.
2 Load the existing application environment, or create and load a new one using the LOAD
statement.
3 Execute the USE statement to make the selected application environment current.
4 If the application exists, run it according to user input.
If the application is new:
a) Allocate the number of available color and font table entries for the new application
by setting the NUMENTRIES attribute of the COLORTABLE and
FONTTABLE handles.
b) Set internal colors and fonts from user input using the SYSTEMDIALOG COLOR
and FONT statements, or set internal colors using the COLORTABLE handle.
c) Create application widgets and code from user input using the current color and font
definitions.
5 Test and revise application resources and widgets according to user input.
6 Repeat Steps 1 through 5 for as many applications as you need to work on simultaneously.
Colors and Fonts
2325
7 When the user terminates all work on an application and wants to save it:
a) Save the color and font table entries to the current environment using the
PUTKEYVALUE statement with the COLOR and FONT options.
b) Save the application code to procedure files.
c) Delete all widgets for the application from Progress memory using the DELETE
WIDGET statement.
d) Remove the current application environment from Progress memory using the
UNLOAD statement.
8 Execute the USE statement according to user input to set a new current environment, then
repeat Steps 4 through 8 until all work is done.
Helpful Hints for Environment Management
Many variations in the typical scenario are possible, but this is a summary of important tasks to
consider in any application that uses multiple environments:
Before defining colors and fonts for immediate use at run time, set or adjust the size of the
current color and font tables as required using the NUMENTRIES attribute of the
COLORTABLE and FONTTABLE handles.
Use the PUTKEYVALUE statement with the COLOR and FONT options to save newly
created and newly modified color and font definitions from the current color and font
tables to the current environment.
Always set a current environment with the USE statement before or after executing the
UNLOAD statement for the current environment, even if the current environment you set
is the default environment. Otherwise, Progress returns an error when you attempt to
display or enable a widget.
Progress Programming Handbook
2326
23.12 Managing Color Display Limitations
Although Progress allows up to 256 colors to be defined in an environment, whether and how
the widgets in each environment display these colors is system dependent. For example, if you
have 256-color widgets displayed from three separate environments, and each environment
defines an entirely different set of 256 colors, some of the widgets might not be displayed with
the correct colors. If your display (driver and hardware) supports a maximum of 256 colors at
one time, the 256-color widgets from only one of your environments can be displayed exactly
as specified. All other widgets must be displayed incorrectly to allow the widgets from one
environment to display correctly.
In general, no matter what your display limitations, Progress tries to ensure that the current
window (the window that has focus) displays correct colors at the expense of one or more other
windows that are displayed, but do not have focus. You can also do the following to ensure that
all of your widgets display with correct colors:
Do not define more colors for all loaded and used environments than your display can
simultaneously support.
Hide any widgets, especially images, that you no longer need to display and whose color
content exceeds the color capacity of your system.
24
Direct Manipulation
Progress lets you write applications that allow the user to directly manipulate (move and resize)
frames and field-level widgets using a mouse. For each widget that is to be manipulated, you
must set the appropriate attributes. For example, to allow the user to move a frame, you must
set the frames MOVABLE attribute to TRUE. The user can then use a mouse to move the frame
when running the application.
Progress also supports grids. A grid is a framework of crisscrossed bars appearing within a
frame that helps the user align widgets within the frame.
Progress Programming Handbook
242
24.1 Attributes
The following attributes are the main attributes associated with the direct manipulation of
widgets. Other attributes are also associated with direct manipulation but apply solely to grids.
For more information on these other attributes, see the Grids section.
Table 241: Direct-manipulation Attributes
Attribute Description
SELECTABLE Set this attribute to TRUE if you want to allow the user
to highlight a widget before moving or resizing it. This
attribute applies to frames and to all field-level widgets.
MOVABLE Set this attribute to TRUE to allow the user to move a
widget. This attribute applies to frames and to all
field-level widgets.
RESIZABLE Set this attribute to TRUE to allow the user to resize a
widget. This attribute applies to frames and to all
field-level widgets.
BOXSELECTABLE Set this attribute to TRUE if you want to allow the user
to use selection boxes to select and deselect widgets
within a frame. (For more information on selection
boxes, see the Box Selecting section.) This attribute
applies only to frames.
SELECTED When the user marks a widget with a mouse, Progress
sets this attribute to TRUE. You can also explicitly set
this attribute within your code. Progress sets this
attribute to FALSE when the user unmarks the widget.
This attribute applies to frames and to all field-level
widgets.
MANUALHIGHLIGHT Set this attribute to TRUE if you want to use a custom
highlight graphic design. When the user selects a
widget, Progress highlights the widget by placing a box
around that widget by default. By setting this attribute
to TRUE, you can override the Progress default and
highlighting the widget yourself.
NUMSELECTEDWIDGETS Use this read-only attribute to determine how many
frames and dialog boxes are selected in a window, or
how many field-level widgets are selected in a frame or
dialog box.
Direct Manipulation
243
By default, all of these attributes are set to FALSE, except for NUMSELECTEDWIDGETS,
which is an integer and read-only. In addition to the attributes listed in Table 241, the
GETSELECTEDWIDGET( ) method allows you to access the widget handle of all selected
widgets in a window, dialog box, or frame. For more information on this method, see the
Progress Language Reference.
Progress Programming Handbook
244
The following procedure illustrates how you might use some of these attributes.
p-dirman.p
DEFINE VARIABLE tmp AS INTEGER.
DEFINE RECTANGLE rect1 size-pixels 39 by 39 edge-pixels 3 no-fill.
DEFINE RECTANGLE rect2 size-pixels 40 by 40 edge-pixels 3 no-fill.
DEFINE RECTANGLE rect3 size-pixels 40 by 39 edge-pixels 3 no-fill.
DEFINE RECTANGLE rect4 size-pixels 39 by 40 edge-pixels 3 no-fill.
DEFINE BUTTON manipulable LABEL "Manipulable".
FORM SKIP (1) SPACE (2) rect1 SPACE(9) manipulable
rect2 SPACE(2) SKIP (1)
SPACE(9) rect3 SPACE(10)
rect4 SPACE(7) SKIP(3)
"Cust-Num: " tmp SKIP(1)
WITH FRAME a ROW 3 CENTERED NO-LABELS TITLE "Manipulable Widgets".
FRAME a:BOX-SELECTABLE = YES.
rect1:SENSITIVE IN FRAME A = YES. /* All Properties sensitive */
rect1:SELECTABLE IN FRAME A = YES.
rect1:MOVABLE IN FRAME A = YES.
rect1:RESIZABLE IN FRAME A = YES.
manipulable:SENSITIVE IN FRAME A = YES. /* All Properties sensitive */
manipulable:SELECTABLE IN FRAME A = YES.
manipulable:MOVABLE IN FRAME A = YES.
manipulable:RESIZABLE IN FRAME A = YES.
rect2:SENSITIVE IN FRAME A = YES. /* Movable, but not resizable */
rect2:SELECTABLE IN FRAME A = YES.
rect2:MOVABLE IN FRAME A = YES.
rect2:RESIZABLE IN FRAME A = NO.
rect3:SENSITIVE IN FRAME A = YES. /* Resizable but not Movable */
rect3:SELECTABLE IN FRAME A = YES.
rect3:MOVABLE IN FRAME A = NO.
rect3:RESIZABLE IN FRAME A = YES.
rect4:SENSITIVE IN FRAME A = YES. /* Not resizable or Movable */
rect4:SELECTABLE IN FRAME A = YES. /* but selectable */
rect4:MOVABLE IN FRAME A = NO.
rect4:RESIZABLE IN FRAME A = NO.
ENABLE ALL WITH FRAME a.
WAIT-FOR GO OF FRAME a.
Direct Manipulation
245
If you set any of the writable attributes in Table 241 to TRUE, Progress assumes that you want
the user to be able to perform direct manipulation on the widget. As a result, Progress interprets
user mouse actions differently and gives higher priority to those mouse events that are
associated with direct manipulation (see Table 242). For example, if you set a buttons
SELECTABLE attribute to TRUE, the user cannot choose the button with a mouse. To choose
the widget, the user must use the keyboard instead.
24.2 Mouse Buttons on Windows
On Windows, Progress maps the two logical buttons, MOVE and SELECT, to the left mouse
button. Every time the user presses the left mouse button, Progress must decide whether to
interpret it as either a MOVE or a SELECT. If the button is clicked (while pointing to a widget),
Progress interprets it as a SELECT. If the button is held down and dragged, Progress interprets
it according to the following rules:
If the mouse was pointing to an empty space in a frame, or to a nonmovable object,
Progress interprets it as a SELECT. Progress creates a selection box that the user can use
to select widgets (given that the frames BOXSELECTABLE attribute is set to TRUE).
All widgets contained within the selection box are selected; the widgets outside of the box
are deselected. For more information on selection boxes, see the Selecting Widgets
section.
If the mouse was pointing to a widget, Progress interprets it as a MOVE (given that the
widgets MOVABLE attribute is set to TRUE). If the widget is selected, Progress moves
the widget and all other widgets that were selected. If the widget is not selected, Progress
deselects all other selected widgets, selects the remaining widget, then moves the widget.
For more information on Progress logical mouse buttons, see Chapter 6, Handling User Input.
Table 242: Direct-manipulation Mouse Events
SELECTION STARTRESIZE
DESELECTION ENDRESIZE
EMPTYSELECTION STARTBOXSELECTION
STARTMOVE ENDBOXSELECTION
ENDMOVE
Progress Programming Handbook
246
24.3 Selecting Widgets
When the user selects a widget, Progress, by default, surrounds the widget with a highlight box.
If the widgets RESIZABLE attribute is set to TRUE, the widgets highlight box also has small
boxes, called resize handles, along its edges. The user can use these handles to resize the widget.
Figure 241 shows the different highlight boxes.
Figure 241: Highlight Boxes
When the user selects a widget, Progress sets its SELECTED attribute to TRUE. You can
override this behavior by writing a trigger for the SELECTION event and having that trigger
return a NOAPPLY. You can prevent Progress from changing a widgets SELECTED
attribute to FALSE by writing a trigger for the DESELECTION event. You can also set the
SELECTED attribute to TRUE or FALSE from within your application. When a widget is
deselected, Progress sets its SELECTED attribute to FALSE by default.
A user must select a widget before resizing it. The user has two ways to select a widget:
Pointing and clicking
Box selecting
24.3.1 Selecting by Pointing and Clicking
When the user points to a field-level widget and clicks the SELECT button, Progress selects the
widget by default (given that the widgets SELECTABLE attribute is set to TRUE). Progress
also draws a highlight box around the widget and deselects all previously selected widgets. If
the user points at an empty space within a frame and clicks the SELECT button, Progress
deselects all previously selected object within the frame, and selects the frame if it is selectable.
To select a frame, the user must point to an empty space in the frame, the frames border, or the
frames title, and click the SELECT button.
Resize
Handles
Highlight Boxes
Direct Manipulation
247
When the user points to a field-level widget and clicks the EXTEND button (CTRL plus the
SELECT button), Progress toggles the selected state of the widget. If the widget was selected,
Progress deselects it. If the widget was not selected, Progress selects it. Progress draws or erases
highlight boxes according to the selected state of the widget. If the user points at an empty space
within a frame and clicks the EXTEND button, Progress toggles the frame selection.
You can override the Progress default behavior by writing triggers for the SELECTION and
DESELECTION events and having the triggers return a NOAPPLY.
24.3.2 Box Selecting
If the user holds down the SELECT or EXTEND button and drags the mouse, Progress draws
a selection box if the frames BOXSELECTABLE attribute is set to TRUE. Figure 242 shows
a selection box.
Figure 242: Selection Box
The appearance of the selection box is the same for both the SELECT and EXTEND buttons.
But the effect of the selection boxes is different depending on which button the user presses. If
the SELECT button is pressed, Progress selects all widgets completely contained within the
box. Progress deselects all other previously selected widgets outside of the box. Deselection
occurs on mouse down. If the EXTEND button is pressed, Progress toggles the selected state of
the widgets within the box. All widgets outside of the box remain unaffected.
The user can create and use selection boxes only on field-level widgets within a frame. If the
user moves the mouse pointer outside of the frame where the selection box was started, the
selection box is confined to the frame. When the user reenters the frame, Progress redraws the
selection box to the current mouse position.
Current
Mouse
Position
Selection Box Original Mouse-down Point
Progress Programming Handbook
248
24.3.3 Sets of Selected Widgets
Within a frame, the user can simultaneously select multiple field-level widgets. The group of
selected widgets within a frame is called the set of selected widgets. Move operations that the
user performs on one selected widget also apply to the set of selected widgets. It is impossible,
however, for the user to perform a move operation against a widget whose attribute settings
dont allow for it. If a widgets MOVABLE attribute is set to FALSE, then the user cannot move
the widget, regardless of whether it is in the set of selected widgets.
24.4 Moving and Resizing Field-level Widgets
Follow these steps to move a widget or set of selected widgets:
1 To move more than one widget, select all widgets to move.
To move a single widget, put the mouse pointer inside the widget, press the MOVE button,
and drag the mouse.
2 Place the mouse pointer inside one of the selected widgets.
3 Press down the MOVE button and drag the mouse.
Progress draws a drag box (Figure 243) around each of the selected widgets; these drag boxes
move along with the mouse across the frame. A drag box is identical in appearance to a selection
box. When the user moves widgets, drag boxes hold their relative position as the user moves
them. A move operation only works for widgets whose MOVABLE attribute is set to TRUE.
Figure 243: Drag Boxes
Drag Boxes
Direct Manipulation
249
To resize a widget, the user must perform the following steps:
1 Select the widget to resize.
2 Place the mouse pointer on a resize handle of the selected widget.
3 Press down the SELECT button and drag the mouse.
Progress draws a drag box around the selected widget. The drag box changes in size as the user
moves the mouse across the frame. A resize operation only works for widgets whose
RESIZABLE attribute is set to TRUE.
Each resize handle allows the user to size a widget in a different direction. For example, if the
user chooses the top-middle resize handle, Progress resizes the widget vertically. If the user
chooses a corner resize handle, Progress resizes the widget diagonally in the direction of that
corner. See Figure 244.
Figure 244: Resize Operation
If the user resizes a fill-in field, Progress does not change the format of the field to accommodate
the greater amount of available space. You can, however, provide triggers for ENDRESIZE
events to modify the formats. You can use this feature to provide scrolling fill-ins. Set the
format to a large value, then set the widgets size to a small value. Progress will scroll the text
when the screen limit is reached.
During Resize
Operation
After Resize
Operation
Resize Handle Used
for Operation
Point Where User
Released Mouse Button
Progress Programming Handbook
2410
24.5 Moving and Resizing Frames
To move a frame, the user must point the mouse pointer to the frames border (the title is
included in the border) when pressing down the MOVE button. Or the user can press SHIFT plus
the MOVE button. To resize a frame, the user must first select the frame.
24.6 Custom Highlights
To customize a design for widget highlighting, you can set the MANUALHIGHLIGHT
attribute to TRUE. When you set this attribute to TRUE, you override Progresss default
highlight style. For an example of how you might design your own highlighting style, look at
the following procedure.
This procedure displays two rectangles that the user can highlight. One of the rectangles uses a
customized style; the other rectangle uses the default highlight style. If the user selects both
rectangles, the output of the procedure appears as follows.
The rectangle on the left, rect1, uses the customized highlight style of filling in the rectangle;
the rectangle on the right, rect2, uses the default Progress highlight style.
p-manual.p
DEFINE RECTANGLE rect1 SIZE-PIXELS 40 BY 40 EDGE-PIXELS 3 NO-FILL.
DEFINE RECTANGLE rect2 SIZE-PIXELS 40 BY 40 EDGE-PIXELS 3 NO-FILL.
FORM SKIP(1) SPACE (1) rect1 SPACE(8) rect2 WITH FRAME a NO-LABELS.
rect1:SELECTABLE IN FRAME a = yes.
rect1:SENSITIVE IN FRAME a = yes.
rect1:MANUAL-HIGHLIGHT IN FRAME a = yes.
rect1:BGCOLOR = 1.
rect2:SELECTABLE IN FRAME a = yes.
rect2:SENSITIVE IN FRAME a = yes.
ON SELECTION OF rect1 IN FRAME a SELF:FILLED = yes.
ON DESELECTION OF rect1 IN FRAME a SELF:FILLED = no.
ENABLE rect1 rect2 WITH FRAME a.
WAIT-FOR GO OF FRAME a.
Direct Manipulation
2411
24.7 Interaction Modes
There are three modes that Progress can enter during a direct-manipulation session:
Move mode
Resize mode
Box-selecting mode
The user directs Progress to enter one of these modes by pressing the SELECT, EXTEND, or
MOVE button and holding it down. The mode entered is determined by where the users mouse
pointer is pointing and by what button the user pressed. For example, if the user presses down
the SELECT button while pointing to an empty space and drags the mouse, Progress enters
box-selecting mode (given that the frames BOXSELECTABLE attribute is set to TRUE). But
if the user presses the SELECT button when pointing at a resize handle, Progress enters resize
mode. If the user presses the MOVE button, Progress always enter move mode. You can direct
Progress not to enter a mode by writing a trigger for the event that causes Progress to enter the
mode; you can have that trigger return a NOAPPLY.
The mode Progress is in affects how Progress interprets mouse-move events and
mouse-button-release events. When the user releases the mouse button, Progress exits the mode
it is in. If the user is in move mode, Progress generates an ENDMOVE event. If the user is in
resize mode, Progress generates an ENDRESIZE event.
24.8 Direct-manipulation Events
Direct-manipulation events have general characteristics that you must know to use them
effectively. For example, some events only apply to frames while others apply to both frames
and field-level widgets. In addition, you need to know the order in which Progress generates
these events during mouse operations. The following sections describe both aspects of
direct-manipulation events.
24.8.1 General Characteristics
Before you can write a program that takes advantage of direct-manipulation events, you need to
know:
Which widgets Progress can send the event to in response to user actions
Which user actions initiate the event
How Progress responds to the event by default
Progress Programming Handbook
2412
This information is described for each event in the following list. Additional comments are
added where necessary. Note that you can override the Progress default actions by writing a
trigger for the event and having that trigger return a NOAPPLY.
SELECT
Widgets: Frames and field-level widgets.
User actions: (1) The user clicks the SELECT button on a widget; (2) the user clicks the
EXTEND button on an widget that is not already selected; (3) the user uses the SELECT button
to draw a selection box around a widget; (4) the user uses the EXTEND button to draw a
selection box around a deselected widget.
Progress default actions: Progress draws a highlight box around the widget and sets its
SELECTED attribute to TRUE.
DESELECT
Widgets: Frames and field-level widgets.
User actions: (1) The user clicks the SELECT button on another unselected widget; (2) the user
clicks the EXTEND button on a widget that is already selected; (3) the user clicks the SELECT
button on empty space in the frame; (4) the user starts a box-selecting operation with the
SELECT button.
Progress default actions: Progress erases the highlight box around the widget and sets its
SELECTED attribute to FALSE.
EMPTYSELECTION
Widgets: Frames and windows.
User actions: The user clicks the SELECT button on empty space in the frame or window (or
performs a box-selecting operation that does not include any widgets).
Progress default actions: None.
Comment: If the user clicks on an empty space, Progress deselects all selected widgets before
it sends the EMPTYSELECTION event to the frame or window.
Direct Manipulation
2413
STARTMOVE
Widgets: Frames and field-level widgets.
User actions: The user presses down the MOVE button to start a move operation. For frames,
the user presses SHIFT plus the MOVE button.
Progress default actions: Progress selects the widget if its SELECTABLE attribute is set to
TRUE; if it is not already selected, Progress draws a drag box around the widget and enters
move mode. If there are selected widgets outside the frame being moved or outside of the frame
containing the field-level widgets being moved, Progress also sends DESELECTION events to
those widgets outside the frame. Deselection occurs on mouse down.
Comments: When the user clicks the MOVE button, Progress sends a STARTMOVE event
to every widget that is selected. If your application puts a trigger on STARTMOVE, and the
trigger returns NOAPPLY, then Progress does not enter move mode, and does not generate
subsequent ENDMOVE events.
ENDMOVE
Widgets: Frames and field-level widgets.
User actions: The user releases the MOVE mouse button while Progress is in move mode (that
is, during a move operation).
Progress default actions: Progress moves the widget to its new location, redraws it there, and
exits move mode.
Comment: Progress generates an ENDMOVE event for each widget that the user moves.
STARTRESIZE
Widgets: Frames and field-level widgets.
User actions: The user clicks the SELECT button on a resize handle of a widget (the widget
must be selected).
Progress default actions: Progress draws a drag box around the widget and enters resize mode.
ENDRESIZE
Widgets: Frames and field-level widgets.
User actions: The user releases the SELECT mouse button while Progress is in resize mode
(that is, during a resize operation).
Progress default actions: Progress resizes the widget to correspond to the new size and
location of its drag box and exits resize mode.
Progress Programming Handbook
2414
STARTBOXSELECTION
Widgets: Frames only.
User actions: The user starts to move the mouse button after having pressed the SELECT
mouse button.
Progress default actions: Progress draws a selection box, which initially appears quite small
but expands into a recognizable box when the user moves the mouse and enters box-selecting
mode.
Comment: If your application puts a trigger on STARTBOXSELECTION, and the trigger
returns NOAPPLY, then Progress does not enter box-selecting mode and does not generate
subsequent and ENDBOXSELECTION events.
ENDBOXSELECTION
Widgets: Frames only
User actions: The user releases the SELECT mouse button while Progress is in box-selecting
mode (that is, during a box-selecting operation).
Progress default actions: Progress normally erases the selection box and then sends a
SELECTION or DESELECTION event to each widget that is completely contained inside the
selection box. If the user starts the box-selecting operation by pressing the SELECT mouse
button, then Progress generates SELECTION events for all the widgets. If the user pressed
EXTEND instead, then Progress generates SELECTION events for the unselected widgets and
DESELECTION events for the selected widgets. Progress also exits box-selecting mode.
Direct Manipulation
2415
24.8.2 Order of Direct-manipulation Events
A few mouse actions may initiate a several events. The order in which Progress generates these
event depends on the type of operation the user performs.
The Box-selecting Operation
The following steps describe the order of events that Progress generates during a box-selecting
operation:
1 The user presses down and holds the SELECT or EXTEND button.
When the user presses SELECT, Progress generates an EMPTYSELECTION event.
Progress then generates a DESELECT event for all widgets that were selected. If the user
presses EXTEND, Progress generates a MOUSEEXTENDDOWN event.
2 The user begins to move the mouse.
Progress generates a STARTBOXSELECTION event for the frame.
3 The user releases the mouse button.
When the user releases SELECT, Progress generates DESELECTION events for all
selected widgets and SELECTION events for all of the widgets the user included in the
box-selecting operation. Then Progress generates an ENDBOXSELECTION event for
the frame. The key code that the mouse sends to Progress is MOUSESELECTUP.
When the user releases EXTEND, Progress generates SELECTION and DESELECTION
events for the widgets included in the box-selecting operation. Then Progress generates an
ENDBOXSELECTION event for the frame. The key code that the mouse sends to
Progress is MOUSEEXTENDUP.
Progress Programming Handbook
2416
The Move Operation
The following steps describe the order of events that Progress generates during a move
operation:
1 The user presses down the MOVE button while the graphics pointer is pointed at a widget.
Progress generates a MOUSESELECTDOWN event.
2 The user begins to move the mouse.
Progress generates a STARTMOVE event for the widgets the user moves.
3 The user releases the mouse.
Progress generates an ENDMOVE event for each widget moved.
The Resize Operation
The following steps describe the order of events that Progress generates during a move
operation:
1 The user presses down the SELECT button while the graphics pointer is pointed at a
widgets resize handle.
Progress generates a MOUSESELECTDOWN event and then a STARTRESIZE
event.
2 The user begins to move the mouse.
Progress generates a STARTRESIZE event for the widget the user resizes.
3 The user releases the mouse.
Progress generates an ENDRESIZE event for the widget the user resized.
Direct Manipulation
2417
24.9 Grids
Progress allows the user to use grids to align widgets within frames. A grid is a framework of
crisscrossed bars whose width and height you can control by setting the appropriate attributes.
Figure 245 shows how a grid appears on screen.
Figure 245: Grid
The dots in Figure 245 are called visible grid points. The distance between two of these small
dots is called a minor grid unit. The distance between one grid line and another grid line is called
a major grid unit. Inside each of the squares on the grid there are invisible grid points (see
Figure 246).
Figure 246: Grid Enlargement
You can control the appearance of grids with the following attributes (these attributes apply to
frames only):
GRIDVISIBLE Determines whether the grid is visible or not.
GRIDFACTORHORIZONTAL Determines the number of horizontal minor grid
units per major unit.
GRIDFACTORVERTICAL Determines the number of vertical minor grid units
per major unit.
Invisible Grid Point
Progress Programming Handbook
2418
GRIDSNAP Determines whether objects align to (snap to) the grid when the user
moves or resizes them.
GRIDUNITWIDTHCHARS Determines the width in character units of a minor
grid unit (that is, the distance in character units between adjacent horizontal grid points).
GRIDUNITWIDTHPIXELS Determines the width in pixels of a minor grid unit
(that is, the distance in pixels between adjacent horizontal grid points).
GRIDUNITHEIGHTCHARS Determines the height in characters of a minor grid
unit (that is, the distance in character units between adjacent vertical grid points).
GRIDUNITHEIGHTPIXELS Determines the height in pixels of a minor grid
unit (that is, the distance in pixels between adjacent vertical grid points).
When the user moves a widget and the frames GRIDSNAP attribute is set to TRUE, the
widgets upper left corner snaps to the nearest grid point. This is true even if the
GRIDVISIBLE attribute is set to FALSE. Note that widgets that are already placed in the
frame are not affected when you set GRIDSNAP to TRUE. However, if you move or resize a
widget in the frame, it then snaps to the nearest grid point.
25
Interface Design
This chapter describes issues that relate to the layout of your application interface. The topics
discussed include:
Progress windows
Frame characteristics
Frame design issues
Tab order
Active window display
Three-dimensional effects (Windows only)
Windows interface design options
Progress Programming Handbook
252
25.1 Progress Windows
The user interface of a Progress application consists of one or more windows whose size and
number affect the space available for frames. Depending on your environment (character or
graphical), there are different factors to consider when you lay out frames. The following
sections describe these factors.
25.1.1 Character-based Environment
A character-based application can use only one window (the default window), which maps
directly to the terminal screen. Progress can accommodate character-based terminal screens of
different sizes. The space within the terminal screen, except for the bottom three lines, is
available to frames. Progress reserves two of these lines for error messages and messages
produced with the MESSAGE statement. Progress reserves the remaining line for status and
help messages.
Figure 251 shows the basic layout of a character-based Progress window.
Figure 251: Character-based Window
Message Area
Display Area
Space available for frames-- 21 or 22
lines, depending on terminal.
Space reserved by Progress-- two lines
for error messages and messages
produced by a MESSAGE statement,
one line for status and help messages.
Interface Design
253
Screen Variations
Most character-based terminal screens have 24 display lines, leaving 21 lines for frames. Some
have 25 display lines, leaving 22 lines for frames. Other systems might have more display lines.
To see how many lines are available, use the SCREENLINES function.
Character Display Height
You must size your frames to fit the display area. If you think some users might run your
application on terminals that have 24 lines while others might run on terminals with more lines,
design your frames to fit in the smaller display area. This way, you can be sure that your frames
display in either situation.
Character Display Width
You must also size your frames to fit the width of the terminal screen. Most terminal screens are
80 characters wide, although some are wider. You can take advantage of the entire width of your
terminal screen (up to 255 characters) when developing an application. However, if you take
advantage of screen width greater than 80 characters, your frames do not fit or look the same on
a screen with a smaller width.
Multi-window Simulation
Although character-based applications can only work with the terminal screen as a single
window, you can simulate multiple windows by providing a menu bar and changing that menu
bar for each simulated window context.
For example, suppose you want to simulate two windows with the following menu bars:
p-wbars.p
DEFINE MENU BasicWindow MENUBAR
MENU-ITEM mfile LABEL "File"
MENU-ITEM medit LABEL "Edit"
MENU-ITEM moptions LABEL "Options"
MENU-ITEM madvance LABEL "Advanced..."
MENU-ITEM mexit LABEL "E&xit".
DEFINE MENU AdvancedWindow MENUBAR
MENU-ITEM mspec LABEL "Special"
MENU-ITEM medit LABEL "Edit"
MENU-ITEM moptions LABEL "Calculations"
MENU-ITEM madvance LABEL "Basic...".
Progress Programming Handbook
254
You can change between windows with code like this, used to complete p-wbars.p.
Of course, a real application likely has submenus in place of most of the menuitem entries
shown in this example.
For more information on creating menu bars and submenus, see Chapter 22, Menus.
25.1.2 Graphical Environment
A graphical application can create and use multiple windows, including the default static
window. Unlike character-based environments, where the window size is identical to the
terminal screen, applications in graphical environments can create windows with any
dimensions smaller, equal to, or larger than the terminal screen (with scroll bars, if necessary).
Also, whereas characters in character-based windows typically have a fixed size and font
(which is sometimes selectable), a graphical window can support many character sizes and
styles, depending on the application and user interface. Graphical windows can also support a
wide variety of frame sizes both smaller and larger than the window dimensions (with scroll
bars, if necessary).
Character Units and Pixels
In a graphical environment, window layouts are generally measured in pixels, not characters.
However, Progress lets you plan your window layout either in pixels or in character units. A
character unit is equal in height to a fill-in widget using the default system font; it is equal in
width to the average width of the characters in the default system font. Since the default system
font determines the size of a character unit, you must consider what system font will be used
when your application is running.
If you want your application to be portable across different platforms (or across different display
resolutions within a single platform), use character units to lay out your screen displays. This
way, you do not have to calculate your application layout in pixels, adjusting your calculations
for different screen resolutions or different platforms.
ON CHOOSE OF MENU-ITEM madvance
CURRENT-WINDOW:MENUBAR = MENU AdvancedWindow:HANDLE.
ON CHOOSE OF MENU-ITEM mbasic
CURRENT-WINDOW:MENUBAR = MENU BasicWindow:HANDLE.
CURRENT-WINDOW:MENUBAR = MENU BasicWindow:HANDLE.
WAIT-FOR CHOOSE OF MENU-ITEM mexit.
Interface Design
255
Character Unit and Pixel Conversion
Note that character units are decimal values and pixels are integer values in Progress. If you set
a pixel attribute to a decimal value, it is rounded to the nearest whole integer. This also means
that if you set a character unit attribute (such as HEIGHTCHARS) and then read it back,
Progress can return a different value than the one you set based on the actual corresponding
pixel value. This is a necessary rounding error because all graphic dimensions are ultimately
stored as pixels, and the nearest whole pixel dimension might not exactly match the character
units you specify.
For example, depending on the resolution and default system font of your interface, if you set
the HEIGHTCHARS attribute to 2.5, Progress might store and return its value as 2.51. This is
because 2.51 most closely matches the number of pixels corresponding to 2.5 character units.
Thus, if your application uses character units to track widget size and location, be sure to reset
your initial values to the values that Progress actually stores and returns.
Window Size
The window attributes in Table 251 let you query the interior size of a maximized window.
When a window is maximized, it fills the entire terminal screen. The values returned by these
attributes are the same whether or not the window is maximized.
The values returned by these attributes exclude the title bar, menu bar, message area, and status
area. The interior of a window cannot exceed the area returned by these attributes. For more
information on using graphical windows, Chapter 21, Windows.
Table 251: Maximized Window Attributes
Attribute Description
FULLHEIGHTCHARS The interior height of the window in character units.
FULLHEIGHTPIXELS The interior height of the window in pixels.
FULLWIDTHCHARS The interior width of the window in character units.
FULLWIDTHPIXELS The interior width of the window in pixels.
Progress Programming Handbook
256
Frame Borders
The frame attributes in Table 252 let you query the border widths of your frames.
The values returned by these attributes change depending on whether the frame is selectable or
movable. The BORDERTOP and BORDERTOPCHARS attributes also change if the frame
has a title.
Table 252: Frame Border Attributes
Attribute Description
BORDERBOTTOMCHARS The height of the bottom frame border in character units.
BORDERBOTTOMPIXELS The height of the bottom frame border in pixels.
BORDERTOPCHARS The height of the top frame border in character units.
BORDERTOPPIXELS The height of the top frame border in pixels.
BORDERLEFTCHARS The width of the left frame border in character units.
BORDERLEFTPIXELS The width of the left frame border in pixels.
BORDERRIGHTCHARS The width of the right frame border in character units.
BORDERRIGHTPIXELS The width of the right frame border in pixels.
Interface Design
257
25.1.3 Alert Boxes
An alert box is a special dialog box for displaying Progress messages instead of displaying them
in the window message area. Alert boxes have no handles, attributes, or methods that you can
access, and they do not respond to trigger events. They exist only to display messages. For
information on defining and using dialog boxes for other purposes, see the Dialog Boxes
section.
You can have Progress display both application and system messages in an alert box using these
techniques:
To put application messages in an alert box, invoke the MESSAGE statement with the
VIEWAS ALERTBOX option.
To have all Progress system messages displayed in an alert box, set the
SYSTEMALERTBOXES attribute of the SESSION system handle to TRUE.
25.2 Frame Characteristics
You can change the overall characteristics of a frame by using Frame phrase options or by
setting frame attributes. The following sections describe these options and attributes and the
rules for setting them.
25.2.1 Using Frame Phrase Options
You can use a Frame phrase to describe the overall characteristics of a frame. A Frame phrase
always begins with the word WITH and is followed by one or more options. The following
procedure shows how you can use Frame phrases to control the appearance of your frames:
p-frm15.p
DISPLAY "Customer Credit Status Report"
WITH TITLE "Report Type" CENTERED.
FOR EACH customer:
DISPLAY name address credit-limit
WITH NO-BOX 10 DOWN CENTERED RETAIN 2.
END.
Progress Programming Handbook
258
This procedure produces the the following output:
In this example, the first DISPLAY statement uses the Frame phrase WITH TITLE Report
Type CENTERED:
The CENTERED option centers the frame on the display screen.
The TITLE option names a title to use with the DISPLAY. Progress always places the title
in the center of the top line of the box that surrounds the frame being displayed.
The second DISPLAY statement displays customer information:
The 10 DOWN option displays only 10 customers on the screen at a time.
The RETAIN 2 option redisplays the last two customersat the top of the screen after
clearing the screen to make room for the next set of customers.
The CENTERED option centers the display of customer records.
Interface Design
259
For more information on Frame phrase options, see the Progress Language Reference.
25.2.2 Setting Frame Attributes
The following procedure shows how you can set frame attributes to control the appearance of
frames:
This procedure adjusts the height of frame using the HEIGHTCHARS attribute so that both a
and b fit on the screen. In each iteration of the FOR EACH loop, the procedure resets the title
of frame b to include the name of the current customer using the TITLE attribute.
p-frm16.p
DEFINE FRAME a
customer.cust-num customer.name
WITH DOWN USE-TEXT WIDTH 40 TITLE "Customers".
DEFINE FRAME b
salesrep
WITH USE-TEXT TITLE "Sales Rep".
FRAME a:HEIGHT-CHARS = SCREEN-LINES - (FRAME b:HEIGHT-CHARS + 1).
FOR EACH customer, salesrep OF customer WITH FRAME a:
DISPLAY cust-num name.
FRAME b:TITLE = "Sales Rep for " + customer.name.
DISPLAY salesrep WITH FRAME b.
END.
Progress Programming Handbook
2510
This procedure produces the following output:
Many frame attributes correspond in name and function to the options of the Frame phrase. For
more information on frame attributes, see the reference entries for the FRAME Widget and for
the frame attributes in the Progress Language Reference.
Interface Design
2511
25.2.3 Rules for Frame Phrases and Attributes
When you use a Frame phrase or frame attribute, the phrase or attribute applies to the entire
frame regardless of the statements that use that frame. Different Frame phrases that apply to the
same frame cannot conflict. For example:
conflicts with:
You cannot simultaneously display the same frame at column 15 and column 30.
25.3 Frame Design Issues
There are several issues that you must consider when you design frames. For example, you
might want to use a certain background image for a frame. This section describes this and other
frame design issues.
25.3.1 Frame-level Design
The following procedure shows how Progress designs a frame:
DISPLAY name WITH FRAME a COLUMN 15.
DISPLAY address WITH FRAME a COLUMN 30.
p-frm17.p
DEFINE BUTTON del-button LABEL "Delete Customer"
TRIGGERS:
ON CHOOSE
DELETE customer.
END.
DEFINE BUTTON next-button LABEL "Find Another Customer" AUTO-GO.
DEFINE BUTTON quit-button LABEL "Quit" AUTO-ENDKEY.
REPEAT:
PROMPT-FOR customer.cust-num quit-button WITH FRAME a.
FIND customer USING cust-num.
DISPLAY name.
UPDATE del-button next-button quit-button WITH FRAME a.
END.
Progress Programming Handbook
2512
Every data-handling statement in a procedure serves two purposes:
To specify frame contents and layout at compile time
To cause frame activity at run time
When Progress compiles this procedure, it designs frames as follows:
The REPEAT block, since it displays data, automatically receives a frame. This frame is
an unnamed frame and is the default frame for the block.
The PROMPTFOR statement uses frame a, not the default frame for the REPEAT block
(because frame a is named explicitly). Progress sets up a frame that is large enough to hold
the custnum field and the quitbutton.
The DISPLAY statement does not name a specific frame, so it uses the default frame for
the REPEAT block. Progress allocates enough room in that frame for the name field.
The UPDATE statement also names frame a. Progress allocates more room in that frame
to hold the buttons delbutton and nextbutton (the frame initially was only big enough to
hold the custnum field).
To summarize, there are two frames used in this procedure. One frame, the default frame for the
REPEAT block, displays the customer name. The second frame, frame a, holds the custnum
field and three buttons.
These are the defaults Progress uses when designing a frame:
Start the frame in column 1, at the first free row on the screen.
Enclose the frame in a box.
Display field labels above fields (that is, use column labels).
Underline the column labels.
Make the frame as wide as necessary to accommodate all the items in the frame, adding
more lines if everything does not fit on one line.
Interface Design
2513
25.3.2 Field- and Variable-level Design
Progress designs frames at compile time. In a top-to-bottom pass of your procedure, the
Progress Compiler encounters fields, variables, and expressions and their related format
specifications. The Compiler adds these fields and expressions to the layout of the appropriate
frame. Progress always designs for all possible cases. That is, if there are several fields that
might be displayed in a frame, Progress makes room for all of them.
As Progress designs each frame, it makes some decisions regarding the individual fields and
variables in the frame:
All references to the same field or variable map to the same frame position.
If labels are above the data (column labels), each field or variable is allocated a column.
The width of the column is either the width of the format or the width of the label,
whichever is larger.
Array frame field labels are followed by a subscript number in square brackets. Progress
determines the labels at compile time. It omits subscripts if the array subscript is variable
or if you specify a label in the frame definition by using the LABEL keyword in a format
phrase.
Constants used in DISPLAY are treated as expressions, each occupying a separate frame
field.
For more information on field and variable display formats, see the DISPLAY Statement
reference entry in the Progress Language Reference. Also see Chapter 17, Representing Data.
in this manual.
25.3.3 FORM and DEFINE FRAME Statements
Often, you want to describe characteristics of a frame but do not want to include that description
in the Frame phrase of a data-handling statement. This is especially true if the frame
characteristics are very complex. You can use the FORM or DEFINE FRAME statements to
describe the layout and processing properties of a certain frame. Specifically, you use these
statements to:
Lay out frame fields in one order when you are going to process them in another order.
Describe frame headers and backgrounds.
Progress Programming Handbook
2514
The FORM and DEFINE FRAME statements only describe the layout of a frame. They do not
actually bring the frame into view. To see the frame, you must use either a data-handling
statement that uses that frame or the VIEW statement, or set the frame VISIBLE attribute to
TRUE.
The location of the FORM statement affects the scope of your frame, so you must place any
FORM statements you use within the appropriate block or blocks of a procedure.
Normally, the first reference to a frame scopes the frame to the current block. However, the
DEFINE FRAME statement does not scope the frame. The FORM statement, like data handling
statements, does scope the frame. For more information on these statements, see the Progress
Language Reference.
Laying Out Frame Fields
When you are using a frame that has a very complex layout or when you want to use the same
frame layout many times in a single procedure, you can use the FORM or DEFINE FRAME
statements to describe the frame. That way, the frame description is in only one place, not
scattered throughout the procedure.
In the following procedure, the DEFINE FRAME statement describes the layout of frame a. By
default, Progress displays the fields in the order they appear in the DEFINE FRAME statement.
You can use the AT phrase to explicitly position a field. When you run the procedure and update
the fields, you move through them in the order they appear in the UPDATE statement. That is,
the order of the fields in the DEFINE FRAME (or FORM) statement affects the positions of the
fields, not their tab (or processing) order.
Interface Design
2515
When you run p-form4.p, press TAB to move through the fields. Notice that the cursor moves
among the fields in the order they are listed in the UPDATE statement:
NOTE: The AT n option, where n is an integer, specifies the column position of the field.
Progress chooses the row based on the previous widget and form item layout of the
frame. Form items include SKIP and SPACE. For more information on form items,
see the reference entries for the DEFINE FRAME statement and the FORM
statement in the Progress Language Reference.
p-form4.p
DEFINE FRAME a
name contact AT 40 SKIP
address credit-limit AT 40 SKIP
city customer.state NO-LABEL SKIP
postal-code AT COLUMN-OF customer.state ROW-OF city + 1 NO-LABEL
SKIP
phone balance AT COLUMN 40 ROW-OF city SKIP(1)
WITH SIDE-LABELS TITLE "Customer Maintenance".
ASSIGN customer.state:WIDTH-CHARS IN FRAME a = postal-code:WIDTH-CHARS.
FOR EACH customer WITH FRAME a:
DISPLAY balance.
UPDATE name address city state postal-code phone
contact credit-limit.
END.
Progress Programming Handbook
2516
This procedure produces the following output:
Note that because the DEFINE FRAME statement is used, the frame is scoped to the FOR
EACH block. It, therefore, becomes a down frame. A FORM statement would have scoped the
frame to the procedure block, making it a one-down frame. For more information on frame
scoping, see Chapter 19, Frames.
The syntax of the AT phrase of the FORM and DEFINE FRAME statements can specify either
the absolute column and row, or the X and Y coordinates of the display field. You can also
position a field relative to a previously positioned field.
For more information on this syntax, see the AT Phrase reference entry in the Progress
Language Reference.
SYNTAX
AT
{ n
| { COLUMN column | COLUMN-OF relative position }
{ROW row | ROW-OF relative-position }
|
{ X x | X-OF relative-position }
{Y y | Y-OF relative-position }
}
[ COLON-ALIGNED | LEFT-ALIGNED | RIGHT-ALIGNED ]
Interface Design
2517
The following sample fragment specifies X and Y coordinates for two fields in a frame:
If you position a field with X and Y coordinates, the position of the field depends on the
resolution of the terminal on which the procedure is compiled and run. For example, if the
resolution is 640 by 480 and you specify X 320, the field is displayed at the midpoint of the
display. If you run the same procedure on a display with a resolution of 1280 by 960, the field
is displayed at the one-quarter point of the screen.
Most interfaces provide a font that is compatible with the screen resolution. An 80-column
display width is common across most platforms. Therefore, if you use character (ROW and
COLUMN) coordinates rather than pixel (X and Y) coordinates, your code is more portable.
Because Progress allows you to specify fractional character units, you can still specify very
precise locations for a graphical environment. In a character environment, the ROW and
COLUMN values are truncated to integer values.
Describing Frame Headers
You can use the FORM or DEFINE FRAME statement to describe a header that appears at the
top of a frame. For example, the FORM statement in this procedure defines a frame that consists
of only a header. The VIEW statement brings that frame into view. The DISPLAY statement
uses a separate frame (the default frame for the FOR EACH block).
p-frm19.p
FORM
customer.cust-num AT X 50 Y 14
customer.name AT X 200 Y 50
WITH SIDE-LABELS FRAME xcust.
FIND FIRST customer.
DISPLAY cust-num name WITH FRAME xcust.
p-form3.p
FORM HEADER "Customer Credit Status Report" WITH CENTERED.
VIEW.
FOR EACH customer WITH CENTERED:
DISPLAY name credit-limit.
END.
Progress Programming Handbook
2518
This procedure produces the following output:
Interface Design
2519
Form Backgrounds
The BACKGROUND option of the FORM and DEFINE FRAME statements allow you to
specify a rectangle or image to display in the background for a frame. The contents of the frame
(both header and data) are displayed on top of the background.
For example, the following procedure displays a rectangle in the background of a frame:
p-back.p
DEFINE RECTANGLE back-rect SIZE 50 BY 4 NO-FILL EDGE-PIXELS 1.
DEFINE FRAME x
customer.cust-num SKIP(2)
customer.name AT 5 SKIP
customer.address AT 5 SKIP
customer.address2 AT 5 SKIP
customer.city AT 5 customer.state customer.postal-code SKIP(2)
customer.phone customer.balance customer.sales-rep
BACKGROUND back-rect AT COLUMN 2 ROW 8
WITH USE-TEXT.
FOR EACH customer:
DISPLAY cust-num name address address2 city state postal-code
phone balance sales-rep WITH FRAME x.
END.
Progress Programming Handbook
2520
This procedure produces the following output:
Note that you must define a rectangle or image before referencing it in the DEFINE FRAME or
FORM statement.
25.3.4 FRAMEROW and FRAMECOL Options
When you design an application, you can control where a frame appears on the screen and where
one frame overlays another. Progress has several functions that let you control the position of
frames in relation to the screen and in relation to other frames.
In the following procedure, the FRAMEROW and FRAMECOL functions indicate the
location of the order-information frame on the customer-information frame.
NOTE: The FRAMEROW and FRAMECOL attributes have no relation to the
FRAMEROW and FRAMECOL functions. These attributes return the position of
a child field-level widget relative to the upper left-hand corner of the parent frame.
However, for child frames, the ROW and COLUMN attributes return the position
relative to the upper left-hand corner of the parent frame. For more information on
child frames, see Chapter 19, Frames.
Interface Design
2521
The FRAMEROW and FRAMECOL functions each return an integer value that represents
the row or column position of the upper-left corner of the named frame. In the example, the
FRAMEROW function returns the value of the row position of the uppermost corner of the
custframe. Then the procedure adds 8 to that value and displays the overlay frame at that row
position. The column position is calculated to be 1 to the right of the right edge of custframe.
p-frrow.p
cust-loop:
FOR EACH customer:
DISPLAY customer WITH FRAME cust-frame
2 COLUMNS TITLE "CUSTOMER INFORMATION".
FOR EACH order OF customer
ON ENDKEY UNDO cust-loop, LEAVE cust-loop:
DISPLAY order-num order-date ship-date promise-date
carrier instructions po
WITH 2 COLUMNS 1 DOWN OVERLAY TITLE "CUSTOMERS ORDERS"
ROW FRAME-ROW(cust-frame) + 8
COLUMN FRAME-COL(cust-frame) + 1.
END.
END.
Progress Programming Handbook
2522
This procedure produces the following output:
If you move custframe to the third row on the screen, the FRAMEROW and FRAMECOL
functions place the order information frame in the same position relative to the customer
information framebelow the eighth row and next to the first column of custframe. The
following procedure shows this adjustment.
p-frrow1.p
cust-loop:
FOR EACH customer:
DISPLAY customer WITH FRAME cust-frame ROW 3
2 COLUMNS TITLE "CUSTOMER INFORMATION".
FOR EACH order OF customer
ON ENDKEY UNDO cust-loop, LEAVE cust-loop:
DISPLAY order-num order-date ship-date promise-date
carrier instructions po
WITH 2 COLUMNS 1 DOWN OVERLAY TITLE "CUSTOMERS ORDERS"
ROW FRAME-ROW(cust-frame) + 8
COLUMN FRAME-COL(cust-frame) + 1.
END.
END.
Interface Design
2523
See the Progress Language Reference for more information on the FRAMEROW and
FRAMECOL functions.
In most cases, a dialog box is better than an overlay frame. For example, p-frrow2.p is similar
to p-frrow1.p, but uses a simple dialog box instead of the overlay frame:
When you run this code in a graphical interface, you can move the dialog box to see all the data
in both frames. For more information, see the discussion of dialog boxes later in this chapter.
p-frrow2.p
DEFINE BUTTON ok-button LABEL "OK" AUTO-GO.
DEFINE BUTTON cancel-button LABEL "CANCEL" AUTO-ENDKEY.
cust-loop:
FOR EACH customer:
DISPLAY customer WITH FRAME cust-frame ROW 3
2 COLUMNS TITLE "CUSTOMER INFORMATION".
FOR EACH order OF customer
ON ENDKEY UNDO cust-loop, LEAVE cust-loop:
DISPLAY order-num order-date ship-date promise-date
carrier instructions po SKIP
ok-button AT 25 cancel-button AT 50
WITH 2 COLUMNS 1 DOWN OVERLAY TITLE "CUSTOMERS ORDERS"
ROW FRAME-ROW(cust-frame) + 8
COLUMN FRAME-COL(cust-frame) + 1
VIEW-AS DIALOG-BOX.
SET ok-button cancel-button.
END.
END.
Progress Programming Handbook
2524
25.3.5 Positioning Frames with FRAMELINE
The FRAMELINE function gives you information on the position of the current display line
in a frame. The following procedure uses this information to determine how to display other
information in the frame:
The p-frline.p procedure lets the user update a customers number, name, and credit limit.
The procedure displays information in the frame for one customer at a time. You can press
CTRLG at any time to see a prompt to delete the customer. The prompt always appears in its
own frame, below the last customer displayed.
This procedure produces the following output:
p-frline.p
DEFINE VARIABLE ans AS LOGICAL LABEL
"Do you want to delete this customer?".
DEFINE FRAME a customer.cust-num customer.name customer.credit-limit.
STATUS INPUT "Enter data, or press CTRL-G to delete the customer".
ON CTRL-G OF customer.cust-num, customer.name, credit-limit
DO:
UPDATE ans WITH ROW FRAME-ROW(a) + 2 + FRAME-LINE(a) COLUMN 10
SIDE-LABELS OVERLAY FRAME del-frame.
IF ans THEN DELETE customer.
HIDE FRAME del-frame.
END.
REPEAT WITH FRAME a 10 DOWN:
FIND NEXT cust.
UPDATE cust-num name credit-limit.
END.
Interface Design
2525
The FRAMELINE function in this procedure controls where the ans variable is displayed. The
position of the prompt is calculated from the upper-right corner of frame a and the current line
within the frame. That is, FRAMEROW + 1 + FRAMELINE gives the position of the current
line in the frame, taking into account the lines for the frame box and the labels. The prompt is
placed below the current line.
See the Progress Language Reference for more information on the FRAMELINE function.
25.3.6 Positioning Frames with FRAMEDOWN
The FRAMEDOWN function returns the number of iterations that can fit in a frame. You can
use this function to determine if a down frame is full. Then Progress can perform an action based
on the value of FRAMEDOWN.
For example, you can display all the customers in the database, which takes several screens. If
the customer you want to see is on the first screen, it is time-consuming to press RETURN until
every customer in the database is listed and the procedure ends. The following procedure uses
the FRAMEDOWN function to solve this problem:
p-frdown.p
DEFINE VARIABLE ans AS LOGICAL.
FOR EACH customer:
DISPLAY cust-num name credit-limit.
IF FRAME-LINE = FRAME-DOWN
THEN DO:
MESSAGE "Do you want to see the next page?" VIEW-AS ALERT-BOX
QUESTION BUTTONS YES-NO TITLE "Screen Full"
UPDATE ans.
IF NOT ans
THEN LEAVE.
END.
END.
Progress Programming Handbook
2526
This procedure produces the following output:
Each time the frame becomes full, the question Do you want to see the next page? appears in
a dialog box. If you answer No, the procedure ends.
In this procedure, the FRAMEDOWN function returns the number of iterations that can fit in
a frame, and the FRAMELINE function returns the current logical line in a frame. When the
current logical line equals the number of lines in the frame, the procedure displays the message
Do you want to see the next page? and gives the user the option to continue or to end the
procedure.
Interface Design
2527
Note that the DOWN and LINE attributes are analogous to the FRAMEDOWN and
FRAMELINE functions.
See the Progress Language Reference for more information on these functions and attributes.
25.3.7 Scrolling Frames
Scrolling frames are specialized down or one-down frames that allow the user to move a
highlight bar through multiple iterations of data. Most often, scrolling frames are down frames.
You use the SCROLL Frame phrase option together with the CHOOSE statement to create this
type of frame.
NOTE: This feature is supported for backward compatibility. You can usually use either a
browse widget or selection list instead of CHOOSE and SCROLL. For more
information on browse widgets, see Chapter 9, Database Access. For information
on selection lists, see Chapter 17, Representing Data.
The SCROLL statement opens a row and moves data in a frame with multiple rows. You can
use the SCROLL statement to scroll data up or down to display another line in a frame. The
following procedure shows how you might use this statement:
p-scroll.p (1 of 3)
DEFINE VARIABLE current_line AS INTEGER.
FORM customer.cust-num customer.name customer.address
customer.city customer.postal-code
WITH FRAME cust-frame SCROLL 1 5 DOWN WIDTH 90.
CURRENT-WINDOW:WIDTH-CHARS = 90.
FIND FIRST customer.
Progress Programming Handbook
2528
REPEAT current_line = 1 TO 5:
DISPLAY cust-num name address city postal-code
WITH FRAME cust-frame.
DOWN WITH FRAME cust-frame.
FIND NEXT customer NO-ERROR.
IF NOT AVAILABLE customer
THEN LEAVE.
END.
UP 5 WITH FRAME cust-frame.
REPEAT:
STATUS DEFAULT
"Use up and down arrows. Enter C to create, D to delete".
CHOOSE ROW customer.cust-num NO-ERROR GO-ON(CURSOR-RIGHT)
WITH FRAME cust-frame.
FIND customer WHERE cust-num = INTEGER(INPUT cust-num).
/* React to moving cursor off the screen */
IF LASTKEY = KEYCODE("CURSOR-DOWN")
THEN DO:
FIND NEXT customer NO-ERROR.
IF NOT AVAILABLE customer
THEN FIND FIRST customer.
DOWN WITH FRAME cust-frame.
DISPLAY cust-num name address city postal-code
WITH FRAME cust-frame.
NEXT.
END.
IF LASTKEY = KEYCODE("CURSOR-UP")
THEN DO:
FIND PREV customer NO-ERROR.
IF NOT AVAILABLE customer
THEN FIND LAST customer.
UP WITH FRAME cust-frame.
DISPLAY cust-num name address city postal-code
WITH FRAME cust-frame.
NEXT.
END.
p-scroll.p (2 of 3)
Interface Design
2529
/* CHOOSE selected a valid key. Check which key. */
IF KEYLABEL(LASTKEY) = "c"
THEN DO:
/* Open a space in the frame */
SCROLL FROM-CURRENT DOWN WITH FRAME cust-frame.
CREATE customer.
UPDATE cust-num name address city postal-code
WITH FRAME cust-frame.
NEXT.
END.
IF KEYLABEL(LASTKEY) = "d"
THEN DO:
/* Delete a customer from the database */
DELETE customer.
SCROLL FROM-CURRENT WITH FRAME cust-frame.
current_line = FRAME-LINE(cust-frame).
DOWN FRAME-DOWN(cust-frame) - FRAME-LINE(cust-frame) - 1
WITH FRAME cust-frame.
/* Place cursor on last active line in frame */
FIND customer WHERE cust-num = INTEGER(INPUT cust-num).
FIND NEXT customer NO-ERROR.
IF NOT AVAILABLE customer
THEN FIND FIRST customer.
DOWN WITH FRAME cust-frame.
DISPLAY cust-num name address city postal-code
WITH FRAME cust-frame.
UP FRAME-LINE(cust-frame) - current_line
WITH FRAME cust-frame.
/* Move cursor back to where it was at start of block */
END.
END.
STATUS DEFAULT.
p-scroll.p (3 of 3)
Progress Programming Handbook
2530
This procedure produces the following output:
Use the arrow keys to move the highlighted bar to the fifth customer. Type C for create.
Interface Design
2531
At this point, you can add a new customer to the database by typing the customer information
on the open line. The p-scroll.p procedure uses SCROLL and CHOOSE to allow the user to
browse through information, then perform actions with the information.
The p-scroll.p procedure creates a scrolling frame of five fields. The frame displays the
custnum, name, address, city, and postalcode for each customer. The status default message
displays Enter C to create, D to delete as long as the procedure runs. You use arrow keys to
move the highlighted cursor bar through lines of the scrolling frame, and you can add or delete
customers from the database. The CHOOSE statement allows you to move the highlight bar.
The SCROLL statement controls the scrolling action in the frame when you create and delete
customers. You add a customer to the database by typing C. Create opens a line in the frame
and the SCROLL statement moves data below the line down. Then you type the new customer
information into the frame. You type D to delete a customer from the database. When you delete
a customer, SCROLL moves the rows below the deleted customer row up into the empty line.
The p-scroll.p procedure works as follows:
The SCROLL option on the Frame phrase creates a scrolling frame for the customer
information.
The CHOOSE statement allows the user to scroll through the list of customer numbers
with a highlighted bar, and it also allows the user to select a location to insert or delete an
item from the list.
When the user types C for create, the SCROLL FROMCURRENT DOWN statement
opens a space for another customer above the highlighted customer.
When the user types D for delete, the SCROLL FROMCURRENT statement closes the
space left by the deleted customer. However, when you use a SCROLL
FROMCURRENT statement, Progress opens a line at the bottom of the scrolling frame.
The remaining statements in this block fill in the opened frame line with record
information.
The p-scroll.p procedure uses UP and DOWN statements to control the current cursor
position. These statements behave differently with scrolling frames than with regular down
frames. If the cursor is on the top line of a frame, the UP statement opens a line at the top of the
screen and moves the remaining frames lines down one line. This is also what happens if you
use the SCROLL DOWN statement. Similarly, if the cursor is on the bottom line of a frame, the
DOWN statement opens a line at the bottom of the screen and moves the remaining frame lines
down one line.
Progress Programming Handbook
2532
25.3.8 Scrollable Frames
You can define a scrollable frame that is bigger than the display space allotted for it. The user
can then use scroll bars to view the entire frame.
You can explicitly set the maximum dimensions of a frame with the
VIRTUALWIDTHPIXELS, VIRTUALHEIGHTPIXELS,
VIRTUALHEIGHTCHARS, and VIRTUALWIDTHCHARS attributes. You can specify
the display size of the frame with the SIZE option of the Frame phrase, as shown below.
If the SCROLLABLE attribute is FALSE, the frame is forced to fit into the display space. If
SCROLLABLE is TRUE, then if the size as specified by the SIZE phrase does not fit within the
display area, only a portion of the frame is displayed and scroll bars appear.
The SCROLLABLE option defaults to TRUE if you use the SIZE option; otherwise, it defaults
to FALSE.
For example, the following code defines a frame, chooseframe, that is bigger than its display
size:
SYNTAX
{ SIZE | SIZE-CHARS | SIZE-PIXELS } width BY height
p-scrlab.p
FORM
Customer.name Customer.address Customer.address2
Customer.city Customer.st
WITH FRAME choose-frame 33 DOWN SIZE 40 BY 5.
PAUSE 0 BEFORE-HIDE.
FOR EACH customer BREAK BY Customer.name:
DISPLAY name address address2 city st
WITH FRAME choose-frame.
IF NOT LAST(customer.name)
THEN DOWN WITH FRAME choose-frame.
END.
REPEAT:
CHOOSE ROW Customer.name WITH FRAME choose-frame.
FIND customer WHERE Customer.name = FRAME-VALUE.
DISPLAY customer WITH SIDE-LABELS.
END.
Interface Design
2533
If you run this code and select Urpon Frisbee, the following screen appears:
You can use scroll bars to move within chooseframe (in the upper-left corner of the display)
to see basic information on each customer. You can then choose to see more information on an
individual customer.
In character interfaces, you can scroll a frame by pressing SCROLLMODE (ESCT on most
terminals). This enters a separate scroll mode in which you can scroll the frame one row or
column at a time with the cursor keys. You can page up and down with the PAGEUP,
PAGEDOWN, END, HOME, RIGHTEND, and LEFTEND keys. No other frame input is possible
in scroll mode. To exit scroll mode, press SCROLLMODE or ENDERROR.
Progress Programming Handbook
2534
25.3.9 Strip Menus
A strip menu is a one-down frame that allows you to move a highlight bar between fields in an
array or among fields in a table.
NOTE: Strip menus are supported for backwards compatibility. You can use buttons or menu
widgets instead.
The following procedure sets up a simple strip menu:
p-strip.p
DEFINE VARIABLE abc AS character EXTENT 3
INITIAL ["Add", "Update", "Delete"].
DISPLAY abc NO-LABELS WITH ROW 8 CENTERED
TITLE "Customer Maintenance".
CHOOSE FIELD abc AUTO-RETURN.
IF FRAME-VALUE = "add" THEN MESSAGE "Add customer".
IF FRAME-VALUE = "update" THEN MESSAGE "Update customer".
IF FRAME-VALUE = "delete" THEN MESSAGE "Delete customer".
Interface Design
2535
This procedure produces the following output:
The p-strip.p procedure defines the array abc with an extent of 3 to display the selections on
the menu. The CHOOSE statement allows you to move the highlight bar among the three fields
in the array and to select a highlighted item by pressing RETURN. Progress displays a message
based on the item CHOOSE holds in the frame. In your own application you can have Progress
perform an action based on the item the user selects.
Progress Programming Handbook
2536
You can also use the CHOOSE statement to create a strip menu that appears at the bottom of
the screen. The following procedure shows how you might do this:
p-chsmnu.p
DEFINE VARIABLE menu AS CHARACTER EXTENT 4 FORMAT "x(7)"
INITIAL [ "Browse", "Create" , "Update", "Exit" ].
DEFINE VARIABLE proglist AS CHARACTER EXTENT 4
INITIAL [ "brws.p", "cre.p", "upd.p", "exit.p"].
FORM "Use the sample strip menu to select an action."
WITH FRAME instruc CENTERED ROW 5.
REPEAT:
VIEW FRAME instruc.
DISPLAY menu
WITH NO-LABELS ROW SCREEN-LINES - 2 NO-BOX FRAME f-menu CENTERED.
HIDE MESSAGE.
CHOOSE FIELD menu AUTO-RETURN WITH FRAME f-menu.
IF SEARCH(proglist[FRAME-INDEX]) = ?
THEN DO:
MESSAGE "The program" proglist[FRAME-INDEX] "does not exist.".
MESSAGE "Please make another choice.".
END.
ELSE RUN VALUE(proglist[FRAME-INDEX]).
END.
Interface Design
2537
This procedure produces the following output:
Progress Programming Handbook
2538
Use the arrow keys to move the highlight bar through the available selections and press RETURN
when the bar highlights the selection you want. Because this is a sample procedure, none of the
items perform actions other than returning messages.
Now look back at the p-chsmnu.p procedure. The procedure defines two arrays with an extent
of four. The menu array holds the items for selection on the menu, and the proglist array holds
the names of the programs associated with the menu selections. The CHOOSE statement allows
the user to select an item from the strip menu. Progress finds the number in the menu array
associated with the item, and then finds the program associated with the number in the proglist
array. Progress runs the program if it exists. If it does not exist, Progress displays a message and
allows the user to select another item from the strip menu.
In most cases, you use the SCROLL statement with the CHOOSE ROW statement to perform
the actual work of selecting an item from a scrolling menu and taking an action based on that
item.
25.3.10 Dialog Boxes
You can choose to display a frame as a dialog box. A dialog box appears in its own window
overlaying the current window. The window manager allows the user to move the dialog box
around the screen.
Interface Design
2539
To display a frame as a dialog box, specify the VIEWAS DIALOGBOX option within the
Frame phrase. For example, the following procedure defines a dialog box.
p-diagbx.p
DEFINE QUERY custq FOR customer.
DEFINE BROWSE custb QUERY custq DISPLAY cust-num name WITH 10 DOWN.
DEFINE BUTTON update-cust LABEL "Update Customer".
DEFINE BUTTON ok-button LABEL "OK" AUTO-GO SIZE 8 BY 1.
DEFINE BUTTON cancel-button LABEL "Cancel" AUTO-ENDKEY SIZE 8 BY 1.
DEFINE VARIABLE curr-rec AS ROWID.
FORM
custb SKIP(1)
update-cust AT 3 cancel-button AT 25
SKIP(1)
WITH FRAME main-frame CENTERED.
FORM
country name address address2 city state postal-code contact
phone sales-rep credit-limit balance terms discount comments SKIP(1)
ok-button AT 15 cancel-button AT 50
WITH FRAME upd-frame TITLE "Customer Update" VIEW-AS DIALOG-BOX.
FRAME upd-frame:HIDDEN = TRUE.
ENABLE ok-button cancel-button WITH FRAME upd-frame.
ON CHOOSE OF update-cust OR MOUSE-SELECT-DBLCLICK OF custb
DO: /* Transaction */
curr-rec = ROWID(customer).
FIND customer WHERE ROWID(customer) = curr-rec EXCLUSIVE-LOCK.
UPDATE customer EXCEPT cust-num WITH FRAME upd-frame.
END.
OPEN QUERY custq FOR EACH customer.
ENABLE ALL WITH FRAME main-frame.
WAIT-FOR WINDOW-CLOSE OF CURRENT-WINDOW.
Progress Programming Handbook
2540
When you run p-diagbx.p, you get the following output:
Interface Design
2541
25.3.11 Frames for Nonterminal Devices
Progress designs frames independently of the output destination of the frame, which is not
determined when the procedure runs, but when it is compiled. However, there are special
considerations when you design frames for display on something other than a terminal screen.
If you display frames to nonterminal devices, the following rules apply:
Unless you use the NOBOX Frame phrase option, Progress omits the bottom line of the
box and converts the top line to blanks. In addition, Progress does not print or reserve
space for the sides of the box.
Progress does not output a frame until it executes a data-handling statement that uses
another frame or until it executes a PUT statement. It collects all the changes to the frame
and sends only one copy of the frame to the output destination.
Progress ignores the ROW Frame phrase option. See the PUT statement in the Progress
Language Reference for more information on controlling the format of output to
nonterminal devices.
25.3.12 Multiple Active Frames
You can enable input for several frames simultaneously. In a graphical environment, the user
can move among the active frames with the mouseselecting any enabled field in a frame
makes that the current frame.
In a character or graphical interface, the user can move to the next or previous frame with the
NEXTFRAME and PREVFRAME keys. However, this moves focus between root frames
parented by the same window, not between child frames in the same frame family.
You can adjust the spacing between frames in a window by setting the FRAMESPACING
attribute of the SESSION system handle. Its value specifies the number of display units between
frames (pixels in graphical interfaces and character cells in character interfaces). By default, the
value of FRAMESPACING is the height of one row in the default system font.
The following procedure uses two frames to update the customer and salesrep records. They are
spaced according to the default value for FRAMESPACING:
Progress Programming Handbook
2542
p-frm18.p
FORM
customer
WITH FRAME cust-frame TITLE "Customer".
FORM
salesrep WITH FRAME rep-frame TITLE "Salesrep".
ON LEAVE, GO OF customer.cust-num
DO:
FIND customer USING customer.cust-num.
FIND salesrep OF customer.
DISPLAY customer WITH FRAME cust-frame.
DISPLAY salesrep WITH FRAME rep-frame.
ENABLE ALL WITH FRAME rep-frame.
DISABLE customer.cust-num WITH FRAME cust-frame.
END.
ON GO OF FRAME cust-frame, FRAME rep-frame
DO:
IF FOCUS <> customer.cust-num:HANDLE IN FRAME cust-frame
THEN DO:
ASSIGN customer.
ASSIGN salesrep.
RUN reset-frames.
END.
END.
ON END-ERROR OF FRAME cust-frame, FRAME rep-frame
DO:
IF FOCUS <> customer.cust-num:HANDLE IN FRAME cust-frame
THEN DO:
RUN reset-frames.
RETURN NO-APPLY.
END.
END.
ENABLE ALL WITH FRAME cust-frame.
WAIT-FOR WINDOW-CLOSE OF CURRENT-WINDOW.
PROCEDURE reset-frames:
CLEAR FRAME cust-frame.
CLEAR FRAME rep-frame.
DISABLE ALL WITH FRAME rep-frame.
ENABLE ALL WITH FRAME cust-frame.
APPLY "ENTRY" TO customer.cust-num IN FRAME cust-frame.
END PROCEDURE.
Interface Design
2543
The procedure initially prompts for a customer number. It then displays the customer record in
frame custframe and the associated salesrep record in frame repframe. Fields in both frames
are enabled for input. The cursor is initially placed in the custframe frame, but you can move
to repframe to modify the salesrep information. Note that the ASSIGN statements within the
GO trigger are necessary to preserve any changes you make to either record.
25.4 Tab Order
Tab order is the order in which field-level widgets receive TAB events in a frame family. Each
frame in a frame family has a separate tab order for the widgets that it owns, and each child
frame participates in the tab order of the field-level widgets owned by its parent frame. That is,
child frames are tab-order siblings of the field-level widgets owned by the same parent frame.
Thus, the tab order of field-level widgets in a frame family depends on how the individual tab
orders of the member child frames are organized.
25.4.1 Tab Order for a Frame
The tab order of widgets in a frame is affected by four factors:
1. The order in which you place widgets within the frame definition.
When Progress compiles a procedure, it lays out all the field-level widgets in static frames
used by the procedure, and assigns the default tab order in the order the widgets appear in
the frame definition. For a dynamic frame, Progress makes the default tab order the order
in which you assign the widgets to the frame. When parenting a child frame or dynamic
field-level widget to a static or dynamic frame, the parented widget assumes the last
position in the current tab order of the frame, by default.
2. The statements you use to enable the widgets for input in a static frame.
The PROMPTFOR, SET, and UPDATE statements explicitly enable widgets and change
their tab order. The ENABLE statement also establishes tab order if you explicitly enable
widgets by name. However, if you ENABLE ALL widgets for a frame, the tab order is not
affected.
Progress Programming Handbook
2544
3. Whether a static frame is defined with, or its definition is modified by, the
KEEPTABORDER option.
This option tells Progress that all statements that enable widgets for input, including the
ENABLE, PROMPTFOR, SET, and UPDATE statements will have no effect on the
frames tab order.
4. Whether you use methods or attributes to change the tab order.
The MOVEAFTERTABITEM( ) and MOVEBEFORETABITEM( ) methods
allow you to change the tab order of field-level widgets and child frames, overriding the
above considerations. At the fieldgroup level, you can also change the tab order of
field-level widgets and child frames using the FIRSTTABITEM and
LASTTABITEM attributes.
You can return the current tab order (relative to 1) of any field-level widget or child frame by
reading its TABPOSITION attribute. The NUMTABS attribute for the field group returns the
number of field-level widgets and child frames that have tab positions, and the
GETTABITEM( ) method for the field group returns the widget handle of the widget with the
specified tab position. You can also obtain the widget handle of the previous or next widget in
the tab order by reading its PREVTABITEM or NEXTTABITEM attribute, respectively.
25.4.2 Tab Order for a Frame Family
Once you have established the tab orders for the widgets of each frame in a frame family, the
tab order proceeds according to these definitions:
Tab-order widget A tab-order widget is a widget in a frames tab order, and can be
either a field-level widget or a child frame.
Tab item A tab item can only be a field-level widget that receives focus. If a child
frame occupies a tab item position, the actual first tab item in the child frame is the first
field-level widget descending in the tab order of the widgets owned by the child frame; the
actual last tab item in the child frame is the last field-level widget descending in the tab
order of the widgets owned by the child frame.
Interface Design
2545
Next tab item If the current tab item is the last field-level widget in the frame family
tab order, the next tab item is the first field-level widget in the frame family tab order. If
the current tab item is the last tab-order widget of a child frame, the next tab item is the
next widget in the tab order of the parent frame. If the next widget in the tab order of the
parent frame is a field-level widget, the next tab item is that field-level widget. If the next
widget in the tab order of the parent frame is a child frame, the next tab item is the first
descendant tab item of the child frame. If the current tab item is not the last tab-order
widget of a frame, the next tab item is determined from the next tab-order widget of the
frame.
Previous tab item If the current tab item is the first field-level widget in the frame
family tab order, the previous tab item is the last field-level widget in the frame family tab
order. If the current tab item is the first tab-order widget of a child frame, the previous tab
item is the previous widget in the tab order of the parent frame. If the previous widget in
the tab order of the parent frame is a field-level widget, the previous tab item is that
field-level widget. If the previous widget in the tab order of the parent frame is a child
frame, the previous tab item is the last descendant tab item of the child frame. If the current
tab item is not the first tab-order widget of a frame, the previous tab item is determined
from the previous tab-order widget of the frame.
Note that you cannot tab between frame families, only within a single frame family.
Figure 252 shows a frame family with four frames. Thus, the tab-order widgets in frame
FRAME 1 include:
Buttons RED, GREEN, and BLUE
Fill-in Field1
Frame FRAME 2
Frame FRAME 3
The tab-order widgets in frame FRAME 2 include:
Buttons RED, GREEN, and BLUE
Fill-in Field2
Frame Frame 4
Progress Programming Handbook
2546
The tab-order widgets in frame Frame 4 include only fill-in Field4, and in frame FRAME 3
include buttons RED, GREEN, and BLUE, and fill-in Field3.
Figure 252: Frame Family Tab Order
However, the actual tab items include the respective RED, GREEN, and BLUE buttons and the
Field1, Field2, Field3, and Field4 fill-ins.
Interface Design
2547
The following procedure (p-foftab.p) displays the frames in Figure 252. Note that because
FRAME f4 is assigned to the frame family before FRAME f3, f4 comes before f3 in the default
tab order:
p-foftab.p (1 of 2)
DEFINE BUTTON bred1 LABEL "RED".
DEFINE BUTTON bred2 LABEL "RED".
DEFINE BUTTON bred3 LABEL "RED".
DEFINE BUTTON bgreen1 LABEL "GREEN".
DEFINE BUTTON bgreen2 LABEL "GREEN".
DEFINE BUTTON bgreen3 LABEL "GREEN".
DEFINE BUTTON bblue1 LABEL "BLUE".
DEFINE BUTTON bblue2 LABEL "BLUE".
DEFINE BUTTON bblue3 LABEL "BLUE".
DEFINE VARIABLE field1 AS CHARACTER LABEL "Field1".
DEFINE VARIABLE field2 AS CHARACTER LABEL "Field2".
DEFINE VARIABLE field3 AS CHARACTER LABEL "Field3".
DEFINE VARIABLE field4 AS CHARACTER LABEL "Field4".
DEFINE FRAME f1 SKIP(.5)
SPACE(18) bred1 bgreen1 bblue1 field1
WITH SIDE-LABELS TITLE "FRAME 1" KEEP-TAB-ORDER
SIZE 80 BY 11.5.
DEFINE FRAME f2 SKIP(.5)
SPACE(3) bred2 bgreen2 bblue2 SKIP(.5) SPACE(1) field2
WITH SIDE-LABELS TITLE "FRAME 2" KEEP-TAB-ORDER
SIZE 30 BY 8 AT COLUMN 7 ROW 3.
DEFINE FRAME f3 SKIP(.5)
SPACE(3) bred3 bgreen3 bblue3 SKIP(.5) SPACE(1) field3
WITH SIDE-LABELS TITLE "FRAME 3" KEEP-TAB-ORDER
SIZE 30 BY 8 AT COLUMN 44 ROW 3.
DEFINE FRAME f4 SKIP(.5)
SPACE(1) field4
WITH SIDE-LABELS TITLE "Frame 4" KEEP-TAB-ORDER
SIZE 26 BY 3 AT COLUMN 2.5 ROW 4.5.
Progress Programming Handbook
2548
25.4.3 Tabbing and DATAENTRYRETURN
When you set the DATAENTRYRETURN attribute of the SESSION handle to TRUE, focus
changes to the next tab item for any fill-in that receives a RETURN event. However, if the fill-in
is the last tab item in the frame family, instead of tabbing back to the first tab item, focus remains
in the fill-in and a GO event is applied to all frames of the frame family. Thus, in p-foftab.p,
pressing the RETURN key in field3 causes a GO event to be applied to each of frames f1, f2, f3,
and f4. The GO event is also applied in order from the innermost child frame to the root. As
indicated in Figure 252, the event fires in order of frame f4, f2, f3, and then f1.
FRAME f2:FRAME = FRAME f1:HANDLE.
FRAME f4:FRAME = FRAME f2:HANDLE.
FRAME f3:FRAME = FRAME f1:HANDLE.
CURRENT-WINDOW:TITLE = "Frame Family Tab Behavior".
ON MOUSE-SELECT-CLICK, GO, TAB, RETURN ANYWHERE DO:
MESSAGE LAST-EVENT:FUNCTION "of" SELF:TYPE
IF SELF:TYPE = "FRAME" OR SELF:TYPE = "WINDOW"
THEN SELF:TITLE ELSE SELF:LABEL.
END.
ENABLE ALL WITH FRAME f1.
ENABLE ALL WITH FRAME f2.
ENABLE ALL WITH FRAME f4.
ENABLE ALL WITH FRAME f3.
SESSION:DATA-ENTRY-RETURN = TRUE.
WAIT-FOR WINDOW-CLOSE OF CURRENT-WINDOW.
p-foftab.p (2 of 2)
Interface Design
2549
25.5 Active Window Display
When you display a message, alert box, or dialog box, you generally want the message or widget
displayed in or near the window where the user is working. In multi-window applications,
especially those managed by persistent procedures, it is possible for the user to be working in
one window and have a persistent procedure display a dialog box in another. This can occur
because the current window of the persistent procedure that displays the dialog box might not
be the window where the user is working. The procedure controlling the users window might
be calling an internal procedure whose parent persistent procedure has a different current
window.
To ensure that your messages, alert boxes, and dialog boxes get displayed in the window the
user is working in, you can specify the ACTIVEWINDOW system handle for the IN
WINDOW option of the appropriate I/O statement. The ACTIVEWINDOW handle always
references the session window that has most recently received input focus. For an example using
the ACTIVEWINDOW handle and more information on managing multi-window
applications, see Chapter 21, Windows.
25.6 Three-dimensional Effects (Windows only; Graphical interfaces only)
On Windows, in graphical interfaces, you can specify that all widgets in a particular frame or
dialog box have a three-dimensional look and feel. (In character interfaces, this specification is
ignored.) By default, buttons, sliders, and browses already display with a three-dimensional
appearance. By specifying the THREED Frame phrase option or by setting the THREED
frame or dialog box option to TRUE, most other widgets in the specified frame or dialog box
display with a three-dimensional appearance, including the toggle box, radio set, editor,
selection list, and combo box widgets. This also means that the frame background color
becomes color Button Face rather than color Window. Note that you can only set the THREED
attribute before the frame or dialog box is realized.
NOTE: The THREED look and feel is the preferred style on Windows 95 and Windows NT
4.0 platforms. The THREED style provides an overall consistent look to the user
interface, thereby accommodating such widgets as the combo box, radio set, toggle
box, and LARGE editor, which cannot be displayed in two dimensions on these
platforms.
Progress Programming Handbook
2550
The following procedure, p-threed.p, displays two frames that are identical, except that one is
two-dimensional and the other is three-dimensional.
p-threed.p
DEFINE BUTTON bOK LABEL "OK".
DEFINE VARIABLE vSlide AS INTEGER LABEL "Slider"
VIEW-AS SLIDER MAX-VALUE 10 MIN-VALUE 1.
DEFINE VARIABLE vToggle AS LOGICAL LABEL "Toggle"
VIEW-AS TOGGLE-BOX.
DEFINE VARIABLE vRadio AS INTEGER LABEL "Radio Set"
VIEW-AS RADIO-SET RADIO-BUTTONS "First", 1, "Middle", 2, "Last", 3.
DEFINE VARIABLE vEdit AS CHARACTER LABEL "Editor"
VIEW-AS EDITOR SIZE 60 by 2.
DEFINE VARIABLE vCombo AS CHARACTER LABEL "Combo Box"
VIEW-AS COMBO-BOX LIST-ITEMS "Red", "White", "Blue", "Purple".
DEFINE FRAME D2-Frame
vSlide vToggle vRadio SKIP vEdit SKIP vCombo SKIP bOK
WITH AT COLUMN 8 ROW 1.1 SIDE-LABELS
TITLE "Two Dimensional Frame".
DEFINE FRAME D3-Frame
vSlide vToggle vRadio SKIP vEdit SKIP vCombo SKIP bOK
WITH AT COLUMN 8 ROW 9 THREE-D SIDE-LABELS
TITLE "Three Dimensional Frame".
CURRENT-WINDOW:TITLE = "".
ENABLE ALL WITH FRAME D2-Frame.
ENABLE ALL WITH FRAME D3-Frame.
WAIT-FOR CHOOSE OF bOK IN FRAME D2-Frame OR
CHOOSE OF bOK IN FRAME D3-Frame.
Interface Design
2551
When you run p-threed.p, the following window appears:
25.6.1 THREED, Rectangles, and Images
Rectangles and images do not automatically acquire three-dimensional characteristics when you
specify THREED for a frame. Rectangles must have border widths of two or more pixels to
display in a three-dimensional style. Images are completely unaffected by THREED. This
avoids any indication that you can interact with them. If you want, you can achieve
three-dimensional effects for an image by displaying it on an appropriate rectangle or by
including the effects within the image itself.
Progress Programming Handbook
2552
25.6.2 THREED and Window Widgets
You can also set the THREED attribute of the window to change the window background to
color Button Face, which matches three-dimensional frames. However, this is the only effect of
changing the THREED attribute for windows. Frame and dialog boxes do not inherit this
attribute value from windows. You must set this attribute before the window is realized.
25.6.3 2D and 3D Widget Size Compatibility
Three-dimensional widgets are different widgets and have different sizes than corresponding
two-dimensional widgets. This means that you must adjust your layouts to accommodate them.
Note that the height of a three-dimensional fill-in is larger than that of a character unit.
However, the height of a two-dimensional fill-in is, by default, the same as that of a
three-dimensional fill-in to provide layout compatibility between them.
If you want two-dimensional fill-ins to equal the height of a character unit, you must set the
Use3DSize key to No in the current environment, which might be the registry or an
initialization file. However, where two standard fill-in fields fit neatly on ROW 1 COLUMN 1
and ROW 2 COLUMN 1, two three-dimensional fill-in fields do not. For more information on
the Use3DSize key, see the section on specifying three-dimensional size in the chapter on
user interface environments in the Progress Client Deployment Guide. For more information on
setting keys in the current environment, see the reference entry for the PUTKEYVALUE
statement in the Progress Language Reference.
25.7 Windows Interface Design Options
When developing Progress applications in the Windows environment, you can incorporate
various elements that Progress supports to update the look and feel of your user interface. These
elements include:
ToolTip information
Windows system help
Small icon size
25.7.1 Incorporating Tooltip Details
ToolTips allow you to define a help text message for a text field or text variable. A ToolTip is
a runtime attribute that can be defined for the following widgets: browse, button, combo box,
editor, fill-in, image, radio-set, rectangle, selection-list, slider, text, and toggle box. However,
they are typically used with a button widget. Progress automatically displays this text when the
user pauses the mouse pointer over a widget for which a ToolTip value is defined.
Interface Design
2553
You can define ToolTip values using the TOOLTIP option available for the DEFINE family of
statements, the VIEW-AS phrase, or setting the TOOLTIP attribute of a widget. You can add
or change the TOOLTIP value at any time. If a ToolTip value is set to either or ? (the
unknown value), then the ToolTip is removed. The default is no ToolTip.
For more information on setting ToolTip values, see the specific DEFINE statement, VIEW-AS
phrase, or widget reference entry in the Progress Language Reference.
25.7.2 Accessing the Windows Help Engine
On Windows, Progress supports the SYSTEMHELP statement, which calls the Windows Help
engine, winhlp32.exe. The Windows Help engine displays a Help viewer, which displays Help
topics and lets the user navigate them. The SYSTEMHELP statement lets you, the developer,
integrate Windows help into your Progress application.
For more information on the SYSTEM-HELP statement, see the SYSTEMHELP Statement
reference entry in the Progress Language Reference. For a complete discussion of how to
provide on-line help for Progress applications, see the Progress Help Development Guide.
25.7.3 Displaying the Small Icon Size
The LOAD-ICON() and the LOAD-SMALL-ICON() methods allow you to associate icons
with windows. The LOADICON() method allows you to specify an icon to display in the title
bar of a window (maximized), in the task bar (minimized), and when selecting a program using
ALTTAB . The LOADSMALLICON() method allows you to specify an icon to display in the
title bar of a window and in the task bar only.
The value you assign with either the LOADICON() or the LOADSMALLICON() methods
must be the name of an icon (.ico) file. Both of these methods accommodate icons formatted
as small size (16x16), regular size (32x32), or both. However, their treatment of the small and
regular size icons differ.
The LOADICON() method looks for an icon defined as 32x32 pixels by default and will shrink
it down to 16x16 pixels if necessary. The resolution of an icon that is changed in this manner
may not be suitable for all icon images. Therefore, the alternative LOADSMALLICON()
method that looks for icons defined as 16x16 pixels by default when there are multiple icons in
an icon file may be more suitable for your icon style and presentation.
Progress Programming Handbook
2554
A
R-code Features and Functions
R-code is the intermediate binary code that Progress generates when it compiles 4GL source
files. This is the code that Progress actually runs when it executes a procedure, whether from
session compiles or from permanently generated r-code (.r) files.
Progress provides a dynamic r-code execution environment, as well as integrity and security
mechanisms to ensure that your r-code is running in a compatible environment.
This appendix provides information about the following topics:
R-code structure
R-code libraries
R-code libraries and PROPATH
R-code execution
R-code portability
Code page compatibility
Database CRCs and time stamps
R-code CRCs and procedure integrity
Progress Programming Handbook
A2
A.1 R-code Structure
R-code is divided into multiple segments of varying length. Each r-code file contains an object
header and segment location table followed by the actual r-code segments. The object header is
a fixed-length descriptor that identifies the file as an r-code file and contains information about
the version and size of the r-code file. The segment location table is a variable-length descriptor
that contains the size and location of each r-code segment in the file. The maximum size for
most segment types is 62K.
Table A1 describes and lists the types, maximum size, and maximum number of segments in
an r-code file.
Table A1: R-code Segments (1 of 2)
Segment
Type
Max.
Size
Max.
Number Description
Action code 62K 1 per
procedure
Holds the actual executable code in the form of
action cells. Action cells drive the Progress
interpreter and contain the executable code for
4GL verbs. There is a separate action code
segment for the main procedure and each
internal procedure.
Expression code 62K 4 Holds the executable expressions in Reverse
Polish Notation (RPN) for the main procedure,
all internal procedures, and all trigger blocks. An
r-code file can have up to 248K of executable
expressions.
Text 62K 1 per
language
Holds all literal character strings for the r-code
file. There is one text segment for the default
language and one for each language specified in
the COMPILE statement. Duplicate literals are
removed. Only one text segment is loaded into
memory at run time.
Initial value 62K 1 Contains information required to initialize an
r-code file for execution, including database and
table references, variable definitions,
TEMPTABLE and WORKTABLE
definitions, a list of defined frames, time stamps,
and CRCs. There is one initial value segment per
r-code file.
R-code Features and Functions
A3
A.1.1 Factors that Affect R-code Size
You can affect r-code size by how you write your 4GL, depending on the r-code segment. The
action code and initial value segments are among the most tunable.
Action Code Segment
You can reduce the size of the action code segment by consolidating multiple 4GL statements
into one. This also can increase the speed of execution, because the interpreter executes only
one action instead of several.
For example, you can reduce action code size by combining several consecutive assignment
statements into one ASSIGN statement.
NOTE: Both of these examples require the same amount of expression code.
Frame 32K 1 per
frame
Contains layout information for a frame,
including frame fields, attributes, and all RPN
expression code from the frame phrase. There is
one frame segment for each named and unnamed
frame in the r-code file. Each frame segment has
a 32K limit.
Debugger 62K 1 Used by the Application Debugger to maintain
the debugger context for the procedure. There is
one debugger segment per r-code file. This
segment is loaded into memory on demand only
when the Debugger is active. For information
about the Debugger, see the Progress Debugger
Guide.
a = 1.
b = 2.
c = 3.
ASSIGN a = 1
b = 2
c = 3.
Table A1: R-code Segments (2 of 2)
Segment
Type
Max.
Size
Max.
Number Description
Progress Programming Handbook
A4
Initial Value Segment
You can reduce the size of the initial value segment by limiting the number of SHARED
variables that are accessed in a procedure. The r-code required to support a SHARED variable
is larger than the r-code to support a NEW SHARED variable. Progress uses approximately 36
additional bytes in the initial value segment to resolve each SHARED variable at run time. This
value can change depending on the environment.
A.1.2 R-code File Segment Layout
Figure A1 shows the segment layout in an r-code file. A compiled procedure requires one
initial value segment, one action code segment, one expression code segment, one text segment,
and one debugger segment. There can be more action and expression code segments, up to the
limit, as required by your procedure. There can be multiple frame segments, one segment for
each frame in your procedure. The maximum number of frame segments is virtually unlimited.
There can also be multiple text segments, one segment for the default language and each
language that you choose for translation. The default language segment contains the literal
character strings defined in the original 4GL source file. You can create additional text segments
by using the Translation Manager. Although multiple text segments are possible, only one text
segment is available per language. Thus, the literal character strings for a given language cannot
exceed 62K. For more information about natural language translation for 4GL procedures, see
the Progress Translation Manager Guide.
R-code Features and Functions
A5
Figure A1: R-code File Segment Layout
A.2 R-code Libraries
You can organize and store r-code files in a Progress r-code library. A Progress r-code library
is a collection of r-code procedures combined in a single file. R-code libraries allow you to
manage and execute r-code procedures more efficiently. You create r-code libraries by using the
PROLIB utility.
Progress provides two types of r-code libraries: standard and memory-mapped. A standard
library contains r-code procedures that execute in local memory. A memory-mapped library
contains r-code procedures that execute in shared memory.
Object Header
Segment Location Table
Initial Value Segment
Action Code Segment (Main Procedure)
Expression Code Segment
Up to Four
Frame Segment
Text Segment
Debugger Segment
Progress Programming Handbook
A6
For information about using the PROLIB utility to create r-code libraries, see the Progress
Client Deployment Guide.
A.3 R-code Libraries and PROPATH
The following rules govern how standard and memory-mapped libraries interact with
PROPATH during a Progress session:
The first time you run a member procedure from a standard or memory-mapped library
that is specified in the PROPATH, Progress locates the procedure by searching through
each directory and library in the PROPATH until it finds the procedure. To search a library
for a member procedure, Progress must open the library. When Progress opens a standard
library, it loads the procedure into local memory. When Progress opens a memory-mapped
library, it maps the library in shared memory.
When searching through directories and libraries in the PROPATH, Progress starts at the
beginning of the PROPATH and searches each directory and library, in defined order, until
it finds the procedure. Thus, placing the library at the beginning of the PROPATH
improves performance.
A library remains open until the end of your Progress session or until you remove the
library from the PROPATH. If you remove a library from the PROPATH while a member
procedure is still active (either running, or waiting for a subprocedure to return), Progress
displays an error message and the library remains open until you end your Progress
session. Otherwise, Progress closes a standard library and unmaps a memory-mapped
library.
If you use a SEARCH or RUN statement to open a library that is not in the PROPATH,
the library remains open until you end your Progress session.
If you use libraries, Progress accesses r-code files as if you had specified the Quick
Request (q) startup parameter. That is, Progress searches the PROPATH only on the first
reference to a procedure. Thereafter, if the procedure still resides in memory, in the local
session compile file, or in an r-code library, Progress reuses the procedure instead of
searching the PROPATH again. For more information about the Quick Request (q)
startup parameter, see the Progress Startup Command and Parameter Reference.
R-code Features and Functions
A7
A.4 R-code Execution
Progress executes r-code procedures in different ways, depending on whether you store the
r-code files in an r-code library and the type of library.
When executing an r-code procedure from an operating system file in a directory, or from a
standard r-code library, Progress accesses and executes the r-code file segments in an execution
buffer in local memory. For more information about the standard r-code execution environment,
see the Standard R-code Execution Environment section later in this appendix.
When executing an r-code procedure from a memory-mapped r-code library, Progress accesses
and executes the r-code file segments in shared memory. For more information about the
memory-mapped r-code execution environment, see the Memory-mapped R-code Execution
Environment section later in this appendix.
For information about monitoring execution environment activity during a Progress client
session, see the R-code Execution Environment Statistics section later in this appendix.
For information about monitoring and optimizing r-code performance, see the Progress Client
Deployment Guide.
A.4.1 Standard R-code Execution Environment
At run-time, Progress manages the execution of a standard r-code procedure from either an
operating system file or a standard procedure library using the following components:
Execution buffer The portion of local memory that Progress allocates and uses to store
the chain of loaded r-code segments for all procedures executing in local memory.
Session sort file (.srt) A file that Progress uses to dynamically swap r-code segments
in and out of the execution buffer.
Segment descriptor table An in-memory table that references the r-code segments
required by all executing procedures, including the location of each r-code segment in the
execution buffer and usage count information.
R-code directory An in-memory table that contains information about each executing
r-code procedure, including r-code size, usage count, segment descriptions, and a
reference to the segment descriptor table for each segment.
Progress uses the segment descriptor table and the r-code directory to manage r-code procedures
from operating system files, standard libraries, and memory-mapped libraries.
Progress Programming Handbook
A8
Figure A2 shows the layout for the standard r-code execution environment.
Figure A2: Standard R-code Execution Environment
In Figure A2, Progress located and loaded the cust.r and orders.r files into local memory
from either operating system files or standard libraries. The execution buffer is shown with three
cust.r segments and two orders.r segments. Note that one orders.r segment is located in the
execution buffer, while the other segment is swapped to the session sort file. When space in the
execution buffer is needed for new r-code segments, Progress uses the session sort file to swap
out the least-recently used segments. When Progress needs a segment that has been swapped to
the session sort file, it reloads the segment from the session sort file into the execution buffer.
orders.r
Segment Descriptor Table
.srt
File
R-code Directory
R-code
Segment
R-code
Segment
R-code
Segment
R-code
Segment
R-code
Segment
R-code
Segment
cust.r cust.r Main Action Code
cust.r Text
orders.r Initial Value
cust.r Initial Value
orders.r Main Action Code
Execution Buffer
R-code Features and Functions
A9
Standard Execution Sequence
When you run a standard r-code procedure for the first time, from either an operating system
file or a standard library, Progress loads and executes the procedure as follows:
1. Opens the procedure file (.p or .r) or procedure library (.pl), if the library is not already
open.
2. Reads the r-code procedure into memory and creates an r-code directory entry for the
procedure. Progress first compiles a procedure into r-code, if necessary.
3. Registers each required r-code segment in the execution environment as follows:
a. Loads the r-code segment at the head of the segment chain in the execution buffer
b. Adds an r-code segment entry to the segment descriptor table that references the
segment in the execution buffer
c. Inserts a segment descriptor reference in the r-code directory entry for the procedure
If all the required r-code segments do not fit in the execution buffer, Progress attempts to
free space by swapping r-code segments already in the buffer to the session sort file. If
Progress cannot free enough space by swapping segments, it increases the execution buffer
ceiling and allocates more space for the execution buffer.
NOTE: When accessing r-code procedures stored in a standard library, Progress does not
swap r-code segments to the session sort file unless you specify the PROLIB
Swap (pls) startup parameter. By default, if Progress needs an r-code segment
in a standard library, it reloads the segment into the execution buffer from the
library in local memory.
4. Once the required r-code segments in the procedure are registered in the execution
environment, the interpreter begins executing the r-code procedure at the start of the main
action code segment and accesses the remaining segments directly from local memory as
required.
Progress Programming Handbook
A10
Standard Execution Environment Limits
The number of standard r-code procedures that you can run at one time and the memory used
are determined by the following factors:
R-code directory size The default is 100 entries, the minimum is 5 entries, and the
maximum is 500. You can set the initial number of entries using the Directory Size (D)
startup parameter. Progress dynamically increases the directory size up to the maximum,
as required. Use the Hardlimit (hardlimit) startup parameter to force Progress to adhere
to the limit specified by the Directory Size (D) startup parameter. .
Execution buffer ceiling The default is 3096K. You can set the initial ceiling for this
buffer up to 65,534K using the Maximum Memory (mmax) startup parameter. Progress
dynamically increases the execution buffer size up to the maximum, as required. Use the
Hardlimit (hardlimit) startup parameter to force Progress to adhere to the limit specified
by the Maximum Memory (mmax) startup parameter.
Available memory Available memory is a factor only if it is smaller than the execution
buffer ceiling or Progress needs to allocate memory beyond that ceiling.
Standard R-code Segment Management
While Progress loads an r-code file, all of its segments are locked in memory. After all required
segments are loaded for the procedure, Progress unlocks all segments except its main action
code and text segment. These two segments stay locked in memory until execution of the r-code
file terminates. Internal procedure action code segments stay locked only until they return to the
invoking procedure and are relocked each time they execute.
When a standard r-code segment does not fit in the execution buffer, Progress attempts to free
space by swapping r-code segments already in the buffer to the session sort file. Progress can
swap out any unlocked segments. Progress removes these segments from the tail end of the
execution buffer chain, in least recently used (LRU) order.
If Progress cannot free enough memory for a newly loaded segment by swapping out older
segments, it dynamically increases the maximum execution buffer size (execution buffer
ceiling) and allocates the required memory up to the memory available.
When Progress needs a segment that has been swapped to the sort file, it reloads the segment
from the sort file into the execution buffer. However, Progress keeps the reloaded segments in
the sort file.
NOTE: When accessing r-code procedures stored in a standard library, Progress does not
swap r-code segments to the session sort file unless you specify the PROLIB Swap
(pls) startup parameter. By default, if Progress needs an r-code segment in a
R-code Features and Functions
A11
standard library, it reloads the segment into the execution buffer from the library in
local memory.
A.4.2 Memory-mapped R-code Execution Environment
At run time, Progress manages the execution of memory-mapped r-code procedures using the
following components:
Shared memory buffer The portion of shared memory that the operating system
allocates and uses to store r-code segments for procedures in a memory-mapped procedure
library.
Segment descriptor table An in-memory table that references the r-code segments
required by all executing procedures, including the location of each r-code segment in
memory and usage count information.
R-code directory An in-memory table that contains information about each executing
r-code procedure, including r-code size, usage count, segment descriptions, and a
reference to the segment descriptor table for each segment.
Progress uses the segment descriptor table and the r-code directory to manage r-code procedures
from operating system files, standard libraries, and memory-mapped libraries.
Progress Programming Handbook
A12
Figure A3 shows the layout for the memory-mapped r-code execution environment.
Figure A3: Memory-mapped R-code Execution Environment
In Figure A3, Progress located the cust.r and orders.r files in a memory-mapped library.
Note that all r-code segments in a memory-mapped library are located in shared memory. When
Progress needs a segment, it executes the segment directly from shared memory.
Memory-Mapped Execution Sequence
When you run a memory-mapped r-code procedure for the first time, Progress loads and
executes the procedure as follows:
1. Opens the procedure library, if not already open, and memory-maps the library in shared
memory.
2. Reads the r-code procedure and creates an r-code directory entry for the procedure.
R-code
Segment
R-code
Segment
R-code
Segment
R-code
Segment
R-code
Segment
Shared Memory
Buffer
R-code Features and Functions
A13
3. Registers each required r-code segment in the execution environment as follows:
a. Adds an r-code segment entry to the segment descriptor table that references the
segment in shared memory
b. Inserts a segment descriptor reference in the r-code directory entry for the procedure
4. Once the required r-code segments in the procedure are registered in the execution
environment, the interpreter begins executing the r-code procedure at the start of the main
action code segment and accesses the remaining segments directly from shared memory
as required.
Memory-Mapped Execution Environment Limits
The number of memory-mapped r-code procedures that you can run at one time and the shared
memory used are determined by the following factors:
R-code directory size The default is 100 entries, the minimum is 5 entries, and the
maximum is 500. You can set the initial number of entries using the Directory Size (D)
startup parameter. Progress dynamically increases the directory size up to the maximum,
as required. Use the Hardlimit (hardlimit) startup parameter to force Progress to adhere
to the limit specified by the Directory Size (D) startup parameter.
Available memory The number of memory-mapped r-code libraries you can have
open is limited by the amount of shared memory available on the system.
Memory-Mapped R-code Segment Management
When Progress needs a memory-mapped r-code segment, it executes the segment directly from
shared memory. Progress does not store active memory-mapped r-code segments in the
execution buffer. Nor does it swap non-active segments to the session sort file. Progress relies
on the operating system to manage the swapping of r-code segments in and out of shared
memory.
A.4.3 R-code Directory Management
If the r-code directory is full when loading a new standard or memory-mapped r-code file,
Progress can reuse existing directory entries for any r-code files no longer in use.
To reuse an r-code directory entry, Progress performs the following steps:
1. Identifies the least recently used (LRU) r-code file.
2. Frees the r-code directory entry for the LRU r-code file.
Progress Programming Handbook
A14
3. Frees all of the segment descriptor entries in the segment descriptor table for the LRU
r-code file.
4. When reusing a standard r-code directory entry, Progress removes all r-code segments for
the LRU r-code file from both the execution buffer and the session sort file. When reusing
a memory-mapped r-code directory entry, the memory-mapped r-code file remains
mapped in shared memory.
When Progress needs to reload a standard r-code file, it follows the standard execution
sequence. When Progress needs to reload a memory-mapped r-code file, it follows the
memory-mapped execution sequence.
A.4.4 R-code Execution Environment Statistics
You can monitor execution environment activity using the Statistics (y) and Segment Statistics
(yd) startup parameters. These parameters cause Progress to write memory statistics to the
client.mon file. The yd parameter provides all of the information about memory usage
available with y plus additional information about r-code segments loaded during a client
session. For more information about the yd and y startup parameters, see the Progress Startup
Command and Parameter Reference.
For information about monitoring and optimizing r-code performance, see the Progress Client
Deployment Guide.
Figure A4 through Figure A8 show sections of client.mon output generated with the yd
startup parameter.
In Figure A4, the Progress client startup options section shows the r-code directory size
(D), in this case, set to the default of 100 entries. The y startup parameter also generates this
information.
Figure A4: Progress Client Startup Options for yd
Wed Mar 23 10:38:04 1994
Progress client startup options:
-A = 0 | -d = mdy | -D = 100 | -h = 7
.
.
.
R-code Directory Size
R-code Features and Functions
A15
In Figure A5, the Execution buffer map section shows the order and size of procedures
loaded into the execution buffer.
Figure A5: Execution Buffer Map for yd
In Figure A6, the Per procedure temp file access statistics section lists each r-code segment
that has been read or written to the session sort file (.srt). Each segment is listed under its r-code
file by segment type (IntProc Action), number (5), and size (1364 bytes).
Figure A6: Accessing the Session Sort File for yd
In this example, all segments shown have been read and written once. A large number of
segments read or written a large number of times indicates a likely need for more memory.
Execution buffer map: 11:27:28
Size Name
---- ----
3813 _edit.r
182871 adeedit/_proedit.r
1321 adecomm/_setcurs.r