Summary:
See also: Dynamic User Interface, Form Files, Windows and Forms.
The Dynamic User Interface architecture is based on the Model-View-Controller (MVC) paradigm. The Model defines the object to be displayed (typically the application data that is stored in program variables). The View defines the decoration of the Model (how the Model must be displayed to the screen, this is typically the form). The Controller is the program code that implements the processing that manages the Model. Multiple Views can be associated to a Model and a Controller.
With BDL, you define the Views in the Abstract User Interface tree or through built-in classes designed for this (such as Window or Form). You store Models in the program variables, and you implement the Controllers with interactive instructions, such as DIALOG or INPUT.
Normally the Controllers should not provide any decoration information, as that is the purpose of Views. Because of the history of the language, however, some interactive instructions such as MENU define both the Controller and some presentation information such as menu title, command labels, and comments. In this case, the runtime system automatically creates the View with that information; you can still associate other Views to the same controller.
In the user interface of the application, you can have interactive elements (such as buttons) that can trigger an event transmitted to the Runtime System for interpretation. To manage such events, the Action Views can produce Actions Events that will execute the code of the corresponding Action Handler in the current interactive instruction of the program.
Action Views (like buttons) are bound to an action by the 'name' attribute. For example, a Toolbar button with the name 'cancel' is bound to the Action Handler using the name 'cancel'. Action Handlers are defined in interactive instructions with ON ACTION clause or COMMAND / ON KEY clauses:
01
INPUT ARRAY custarr WITHOUT DEFAULTS FROM sr_cust.*02
ON ACTION printrec03
CALL PrintRecord(custarr[arr_curr()].*)04
ON ACTION showhelp05
CALL ShowHelp()06
END INPUT
The name of the action must be a valid identifier in the program.
Warning: In the Abstract User Interface tree (where the action views are defined), action names are case-sensitive (as they are standard DOM attribute values). In BDL, however, identifiers are not case-sensitive. To avoid any confusion, the compiler automatically converts action identifiers to lowercase.
For singular dialogs such as INPUT or DISPLAY ARRAY, actions only have a simple name. Action names in a DIALOG instruction, however, have both simple names and prefixed names. When using a DIALOG instruction, one can define the same action (or have predefined actions such as insert, append, delete) in different sub-dialogs. These action names automatically get a prefix to identify actions properly across sub-dialogs. These actions are prefixed with the sub-dialog identifier. For example, when defining an ON ACTION save handler in a DISPLAY ARRAY sub-dialog using a screen-array named "sa", the runtime system will identify that action with both the "save" name and the "sa.save" prefixed name. In the form files, you can then bind Action Views to sub-dialog Action Handlers by using the sub-dialog prefix.
For backward compatibility, the COMMAND / ON KEY clauses are still supported. However, it is strongly recommended that you use ON ACTION clauses instead, as the ON ACTION clauses identify user actions with an abstract name.
A default decoration for action views can be centralized in an external file. This is strongly recommended, to separate the decoration of the action view from action usage (the action handler) as much as possible. See Action Defaults for more details.
During an interactive instruction, you can enable or disable an action with the setActionActive()
method of the ui.Dialog built-in class. This
method takes the name of the action (in lowercase letters) and an boolean expression
(0 or FALSE, 1 or TRUE) as arguments.
01
...02
BEFORE INPUT03
CALL DIALOG.setActionActive("zoom",FALSE)04
...
If no explicit action view is defined, such as a toolbar button or a topmenu command, the front end creates a default action view for each MENU COMMAND, ON KEY or ON ACTION clause used in the current interactive instruction (typically, the default action views appear as buttons in the action frame). You can control the presentation of these default views with Window Styles.
If one or more action views are defined explicitly for a given action, the front end considers that the default view is no longer needed, and hides the corresponding button. Typically, if you define in the form a BUTTONEDIT field or a BUTTON that triggers an action, you do not need an additional button in the action frame.
The BDL language predefines some action names for common operations of interactive instructions. Predefined actions can be:
If you define your own ON ACTION handler with the name of an Automatic action, the default action processing is bypassed and the program code is executed instead. For more details, see Overwriting Automatic Actions in Interactive Instructions.
As for user-defined actions, if you design action views using predefined action names, they will automatically attach themselves to the actions of the interactive instructions. It is also possible to define default images, texts, comments and accelerator keys in the Action Defaults resource file for these predefined actions.
Action Name | Description | ON ACTION block | Context |
Runtime Controlled | Automatically created by the runtime system (except for DIALOG) | ||
accept | Validates the current interactive instruction | possible | (1) |
cancel | Cancels the current interactive instruction | possible | (1) |
close | Triggers a cancel key in the current interactive instruction (by default) | possible | (7) |
insert | Inserts a new row before current row | possible | (2) |
append | Appends a new row at the end of the list | possible | (2) |
delete | Deletes the current row | possible | (2) |
nextrow | Moves to the next row in a list | possible | (4) |
prevrow | Moves to the previous row in a list | possible | (4) |
firstrow | Moves to the first row in a list | possible | (4) |
lastrow | Moves to the last row in a list | possible | (4) |
help | Shows the help topic defined by the HELP clause | possible | (1) |
Special | Special behavior | ||
interrupt | Sends an interruption request to the program when processing | no | (5) |
dialogtouched | Sent by the front end each time the user modifies the value of a field | yes | (7) |
Front-end controlled | Handled by the front end (the runtime does not know about these actions) | ||
editcopy | Copies to the clipboard the current selected text | no | (1) |
editcut | Copies to the clipboard and removes the current selected text | no | (1) |
editpaste | Pastes the clipboard content to the current input widget | no | (1) |
nextfield | Moves to the next field in the form | no | (3) |
prevfield | Moves to the previous field in the form | no | (3) |
nextrow | Moves to the next row in the list (supported for backward compatibility - is a runtime action) | no | (4) |
prevrow | Moves to the previous row in the list (supported for backward compatibility - is a runtime action) | no | (4) |
firstrow | Moves to the first row in the list (supported for backward compatibility - is a runtime action) | no | (4) |
lastrow | Moves to the last row in the list (supported for backward compatibility - is a runtime action) | no | (4) |
nextpage | Moves to the next page in the list | no | (4) |
prevpage | Moves to the previous page in the list | no | (4) |
nexttab | Moves to the next page in the folder | no | (6) |
prevtab | Moves to the previous page in the folder | no | (6) |
The ON ACTION clause can be used with a predefined action name in an interactive instruction :
01
INPUT BY NAME customer.*02
ON ACTION accept03
...04
END INPUT
In this case, the default behavior is not performed; the user code is executed instead.
Some parts of the user interface can define accelerators keys. With Action Defaults, you can define up to four accelerator keys for the same action, by setting the acceleratorName, acceleratorName2, acceleratorName3 and acceleratorName4 attributes.
If no accelerators are defined in the Action Defaults, the runtime system sets default accelerators for predefined actions, according to the user interface mode. For example, the accept action will get the Return and Enter keys in GUI mode; the Escape key would be used in TUI mode.
If you want to force an action to have no accelerator, specify "none" as the accelerator name.
If one of the user-defined actions uses an accelerator that would normally be used for a predefined action, the runtime system does not set that accelerator for the predefined action. For example (in GUI mode), if you define an ON ACTION quit with an action default using the accelerator "Escape", the cancel predefined action will not get the "Escape" default accelerator. User settings take precedence over defaults.
Note that text edition and navigation accelerators such as Home and End are usually local to the widget. According to the context, such accelerators might be eaten by the widget and will not fire the action bound to the corresponding accelerator defined in the Action Defaults. For example, even if the Action Defaults for firstrow action defines the Home accelerator, when using an INPUT ARRAY, the Home keystroke will jump to the beginning of the edit field, not the the first row of the list.
The following table lists all the keyboard accelerator names recognized by the runtime system:
Accelerator Name | Description |
none | Special name indicating the the runtime system must not set any default accelerator for the action. |
0-9 | Decimal digits from 0 to 9 |
A-Z | Letters from A to Z |
F1-F35 | The functions keys |
Return | The RETURN key (alphanumeric keypad, see Note) |
Enter | The ENTER key (numeric keypad, see Note) |
Space | The SPACE-BAR key |
Escape | The ESCAPE key |
Tab | The TABULATION Key |
BackSpace | The BACKSPACE key (do not confuse with DELETE key) |
Up | The UP key (arrow keyboard group) |
Down | The DOWN key (arrow keyboard group) |
Left | The LEFT key (arrow keyboard group) |
Right | The RIGHT key (arrow keyboard group) |
Insert | The INSERT key (navigation keyboard group) |
Delete | The DELETE key (navigation keyboard group) |
Home | The HOME key (navigation keyboard group) |
End | The END key (navigation keyboard group) |
Next | The NEXT PAGE key (navigation keyboard group) |
Prior | The PRIOR PAGE key (navigation keyboard group) |
The "Enter
" key represents the ENTER key available on
the numeric keypad of standard keyboards, while "Return
"
represents the RETURN key of the
alphanumeric keyboard. By default, the validation action is configured to accept
both "Enter
" and "Return
" keys. See the
Action Defaults file.
All of the key names listed in the previous table can be combined with CONTROL /
SHIFT / ALT modifiers, by adding "Control-
",
"Shift-
", or "Alt-
" to the name of the accelerator.
For example:
Control-P
Shift-Alt-F12
Control-Shift-Alt-Z
When the BDL program executes an interactive instruction, the front end can send action events based on user actions. When the program performs a long process like a loop, a report, or a database query, the front end has no control. You might want to permit the user to stop a long-running process.
To detect user interruptions, you define an action view with the name 'interrupt'. When the runtime system takes control to process program code, the front end automatically enables the local 'interrupt' action to let the user send an asynchronous interruption request to the program.
Warning: The front end can not handle interruption requests properly if the display generates a lot of network traffic. In this case, the front end has to process a lot of user interface modifications and has no time to detect a mouse click on the 'interrupt' action view. A typical example is a program doing a loop from 1 to 10000, just displaying the value of the counter to a field and doing a refresh. This would generate hundreds of AUI tree modifications in a short period of time. In such a case, we recommended that you calculate a modulo and display steps 10 by 10 or 100 by 100.
Form file "f1.per":
01
LAYOUT02
GRID03
{04
Step: [pb ]05
[sb ]06
}07
END08
END09
ATTRIBUTES10
PROGRESSBAR pb = FORMONLY.progress, VALUEMIN=0, VALUEMAX=100;11
BUTTON sb : interrupt, TEXT="Stop";12
END
Program:
01
MAIN02
DEFINE i,j INTEGER03
DEFER INTERRUPT04
OPEN FORM f1 FROM "f1"05
DISPLAY FORM f106
LET int_flag=FALSE07
FOR i=1 TO 10008
DISPLAY i TO progress09
CALL ui.Interface.refresh()10
FOR j=1 TO 1000 -- Loop to emulate processing11
DISPLAY j12
IF int_flag THEN EXIT FOR END IF13
END FOR14
IF int_flag THEN EXIT FOR END IF15
END FOR16
END MAIN
You can use a special predefined action to detect user changes immediately and execute code in the program to set up your interactive instruction. This special action has the name "dialogtouched" and must be declared with an ON ACTION clause to be enabled:
01
DIALOG02
...03
ON ACTION dialogtouched04
LET changing = TRUE05
CALL DIALOG.setActionActive("dialogtouched", FALSE)06
...07
END DIALOG
Each time the user modifies the value of a field (without leaving the field), the ON ACTION dialogtouched block will be executed; This can be triggered by typing characters in a text editor field, clicking a checkbox / radiogroup, or modifying a slider.
The dialogtouched action works for any field controlled by the current interactive instruction, and with any type of form field.
Note that the current field may contain some text that does not represent a value of the underlying field data type. For this reason, the target variable cannot hold the current text displayed on the screen when the ON ACTION dialogtouched code is executed, even when using the UNBUFFERED mode.
You may only want to detect the beginning of record modification, to enable a "save" action for example. To prevent further dialogtouched action events, just disable that action with a setActionActive() call. If this action is enabled, the ON ACTION block will be fired each time the user modifies the value in the current field.
To avoid data validation, the dialogtouched action is defined with validate="no" in the default Action Defaults file. This is mandatory when using the UNBUFFERED mode; otherwise the runtime would try to copy the input buffer into the program variable when a dialogtouched action is fired.
When using the UNBUFFERED mode of interactive instructions such as INPUT or DIALOG, if the user triggers an action, the current field data is checked and loaded in the target variable bound to the form field. For example, if the user types a wrong date (or only a part of a date) in a field using a DATE variable and then clicks on a button to fire an action, the runtime system will throw an error and will not execute the ON ACTION block corresponding to the button.
If you want to prevent data validation for some actions, you can use the validate Action Default attribute. This attribute instructs the runtime not to copy the input buffer text into the program variable (requiring input buffer text to match the target data type).
The validate Action Default attribute can be set in the global action default file, or at the form level, in a line of the ACTION DEFAULTS section.
In graphical applications, windows can be closed by the user, for
example by pressing ALT+F4
or by clicking the cross button in the
upper-left corner of the window. A Predefined Action is dedicated
to this specific event. When the user closes a graphical window, the program gets
a close action.
By default, when the program is in a MENU instruction, the close action is converted to an INTERRUPT keypress (the key that cancels an interactive instruction). This means that a COMMAND KEY(INTERRUPT) block is invoked if it is defined in the MENU statement. If there is no COMMAND KEY(INTERRUPT), nothing happens.
When executing an INPUT, INPUT ARRAY, CONSTRUCT or DISPLAY ARRAY, the close action acts by default the same as the cancel predefined action. So when the user clicks the X cross button, the interactive instruction stops and int_flag is set to 1. Note that if the CANCEL=FALSE option is set, no close action will be created, and you must write an ON ACTION close control block to create an explicit action. In this case, you define an action handler for close and the int_flag register will not be set.
When executing a DIALOG instruction,
the close action executes the ON
ACTION close block if defined. Otherwise, the close action is mapped to the
cancel action and the ON ACTION cancel block is fired is one
is defined. If neither ON
ACTION close, nor ON
ACTION cancel is defined, the window cannot be closed with the X cross
button or an ALT+F4
keystroke.
You typically implement a close action handler as in the following example:
01
INPUT BY NAME cust_rec.*02
...03
ON ACTION close04
IF msg_box_yn("Are you sure you want to close this window?") == "y" THEN05
EXIT INPUT06
END IF07
...08
END INPUT