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 Action Events that will execute the code of the corresponding Action Handler in the current interactive instruction of the program.
If no explicit action view is defined, such as a toolbar button, or a topmenu item or a simple button in the form layout, the front end creates a default action view for each MENU COMMAND or ON ACTION clause used in the current interactive instruction. Typically, the default action views appear as buttons in the action frame.
Note that when creating actions with ON KEY (or COMMAND KEY without a command name in a MENU), the default action view gets invisible. However, if you define a text attribute in the Action Defaults, the default button is made visible.
You can control the default action view visibility by using the DEFAULTVIEW Action Default attribute or the defaultView attribute in the 4ad files.
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 presentation of the default action views can be controlled with Window Styles.
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.
Details about Action Defaults appliance will be discussed later in this section.
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 a boolean expression
(0 or FALSE, 1 or TRUE) as arguments.
01
...02
BEFORE INPUT03
CALL DIALOG.setActionActive( "zoom", FALSE )04
...
Details about action activation and de-activation will be discussed in other parts of this section.
Remember that Action Views can produce Action 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 an 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
For backward compatibility, the COMMAND / ON KEY clauses are still supported. It is recommended that you use ON ACTION clauses instead, as the ON ACTION clauses identify user actions with an abstract name. However, in some situations you will have to use a COMMAND clause if you want to include the corresponding action view in the focusable form items.
In ON ACTION action-name, the name of the action must be a valid identifier, preferably written in lowercase letters: Keep in mind that 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 Genero BDL, however, identifiers are not case-sensitive. To avoid any confusion, the compiler always converts the action identifiers of ON ACTION clauses to lowercase.
Within the DIALOG instruction, we distinguish dialog actions from sub-dialog actions. When the ON ACTION handler is defined at the same level as a BEFORE DIALOG control block, it is a dialog action, and the action name is a simple identifier as in singular interactive instructions. When the ON ACTION handler is defined inside a sub-dialog, or if the action is an implicit action such as insert in INPUT ARRAY, it is a sub-dialog action and the action name gets the name of the sub-dialog as the prefix to identify the sub-dialog action with a unique name :
01
DIALOG02
INPUT BY NAME ... ATTRIBUTES (NAME = "cust")03
ON ACTION check -- sub-dialog action "cust.check"04
...05
END INPUT06
DISPLAY ARRAY arr_orders TO sr_ord.*07
...08
ON ACTION check -- sub-dialog action "sr_ord.check"09
...10
END DISPLAY11
BEFORE DIALOG12
...13
ON ACTION close -- dialog action "close"14
...15
END DIALOG
The INPUT ARRAY and DISPLAY ARRAY sub-dialogs are implicitly identified with the screen-record name defined in the form. For INPUT and CONSTRUCT sub-dialogs, the sub-dialog identifier can be specified with the NAME attribute.
By using the sub-dialog identifier, you can bind action views to specific sub-dialog actions. Action views bound to sub-dialog actions with qualified sub-dialog action names will always be active, even if the focus is not in the sub-dialog of the action. You typically use fully-qualified sub-dialog actions names for buttons in the form body or in TopMenu options. However, it does not make much sense to use this technique for ToolBar buttons, where buttons must be enabled/disabled according to the context.
If you bind an action view with a simple action name (without the sub-dialog prefix), the action view will be attached to any sub-dialog action with the matching name. This is especially useful for common actions such as the implicit insert / append / delete actions created by INPUT ARRAY, when the dialog handles multiple editable lists. You can bind ToolBar buttons to these actions without the sub-dialog prefix; the buttons will apply to the current list that has the focus. The action views bound to sub-dialog actions without the sub-dialog qualifier will automatically be enabled or disabled when entering or leaving the group of fields controlled by the sub-dialog (i.e. typical navigation buttons in the toolbar will be disabled if the focus is not in a list).
If a sub-dialog action is invoked when the focus is not in the sub-dialog of the action, the focus will automatically be given to the first field of the sub-dialog, before executing the user code defined in the ON ACTION clause. This will trigger the same validation rules and control blocks as if the user had selected the first field of the sub-dialog by hand.
When using DIALOG.setActionActive()
(or any method that takes an action name as parameter), you can specify the action name with or without a sub-dialog
identifier. If you
qualify the action with the sub-dialog identifier, the sub-dialog action
is clearly identified. If you don't specify a sub-dialog prefix, the action will be identified
based on the focus context - when the focus is in the sub-dialog of the action, non-qualified
action names
identify the local sub-dialog action; otherwise, they identify a dialog action if
one exists with the same name. Disabling an action by the program
with setActionActive()
, will take precedence over the
built-in activation rules (i.e. if the action is disabled by the program, the action
will not be activated when entering the sub-dialog).
For action views bound to sub-dialog actions with qualifiers, the Action Defaults defined with the corresponding action name will be used to set the attributes with the default values. In other words, the prefix will be ignored. For example, if an action view is defined with the name "custlist.append", it will get the action defaults defined for the "append" action.
The ON ACTION interaction block of singular dialogs or sub-dialogs in DIALOG instruction, can also be defined with the INFIELD field-name clause:
01
INPUT ARRAY custarr WITHOUT DEFAULTS FROM sr_cust.*02
ON ACTION zoom INFIELD cust_city03
LET custarr[arr_curr()].cust_city = zoom_city()04
ON ACTION zoom INFIELD cust_state05
LET custarr[arr_curr()].cust_state = zoom_state()06
END INPUT
If an action is defined with the INFIELD field-name clause, the action object name is implicitly prefixed with the field name:
field-name . action-name
You can then bind action views by using the field name prefix to identify the action. Without the field name prefix, the action view is enabled and disabled automatically according to the current field. If you bind the action view with the fully-qualified name including the field name prefix, the action view will always be active.
Actions defined in sub-dialogs of the DIALOG instruction get the name of the sub-dialog as prefix. If ON ACTION action-name INFIELD field-name is used in a sub-dialog, the action object name is prefixed with the name of the sub-dialog, followed by the name of the field. The fully-qualified action name will be:
sub-dialog-name . field-name . action-name
When the field-specific action is invoked (for example by a button of the toolbar bound with the fully-qualified action name) and if the field does not have the focus, the runtime system first selects that field before executing the code of the ON ACTION INFIELD block. The field selection forces data validation and AFTER FIELD of the current field, followed by BEFORE FIELD of the target field associated to the action.
Note that it's still possible to enable/disable field-specific
action objects by the program using the DIALOG.setActionActive()
method. When specifying a fully-qualified action name with the field name prefix, that field-specific action will be enabled or disabled. When disabled by
the setActionActive()
method, the corresponding action views will always be
disabled, even if the field has the focus. If you do not specify a
fully-qualified name in the method call, and if several actions are defined with
the same action name in different sub-dialogs and/or using the INFIELD
clause, the method will identify the action according to the current focus
context. For example, if you define ON ACTION zoom INFIELD cust_city
and ON ACTION zoom INFIELD cust_addr, when the focus is in cust_city,
a call to DIALOG.setActionActive("zoom", FALSE) will disable
the action specific to the cust_city field.
Fields can be enabled or disabled dynamically with the DIALOG.setFieldActive() method. If an ON ACTION INFIELD is declared on a field and if you enable/disable the field dynamically, then the field-specific action (and corresponding action views in the form) will be enabled or disabled accordingly.
We have seen in the above sections that actions can be defined at three levels in the context of a DIALOG instruction (this would be only two levels in singular dialogs like INPUT):
Note that it is not good practice to use the same action name at different levels of a dialog: This makes action view bindings and action handling (i.e. enabling / disabling) very complex, because there are many possible combinations. Therefore, when using the same action name at different dialog levels, the fglcomp compiler will raise a warning -8409.
Understand that it is legal to use the same action name for a given level of action handlers in a sub-dialogs or for field-actions. For example, using the "zoom" action name for multiple ON ACTION INFIELD handlers is a common practice.
When binding action views with full qualified names, the ON ACTION handler is clearly identified, and the corresponding user code will be executed. However, when you do not specify the complete prefix of a sub-dialog or field action, the runtime system searches for the best ON ACTION handler to be executed, according to the current focus context.
Take for example a DIALOG instruction defining three ON ACTION print handlers at the dialog, sub-dialog and field level:
01
DIALOG02
INPUT BY NAME ... ATTRIBUTES (NAME = "cust")03
...04
ON ACTION print INFIELD cust_name -- field-level action (1)05
...06
ON ACTION print -- sub-dialog-level action (2)07
...08
END INPUT09
...10
ON ACTION print -- dialog-level action (3)11
...12
END DIALOG
The action views of the form will behave as follows:
cust_name
field.cust
sub-dialog, but
not in cust_name
field.cust
sub-dialog.cust
sub-dialog,
and invoke the ON ACTION print handler according to the current focus context:
cust_name
field.cust
sub-dialog, but
not in cust_name
field.Note that if the first field of a sub-dialog defines an ON ACTION INFIELD with the same action name as a sub-dialog action, and the focus is not in that sub-dialog when the user selects an action view bound with the name sub-dialog-name.action-name, the runtime system gives the focus to the first field of the sub-dialog. This field becomes the current field, and the runtime system executes the field-specific action handler instead of the sub-dialog action handler.
To avoid mistakes and complex combinations, you should use specific action names for each dialog level.
Some front-ends display a default context menu, showing all possible (active) actions in the current context. You can define the action to be represented in this context menu by using the CONTEXTMENU Action Default attribute.
When you use the UNBUFFERED mode, the current field value is validated before ON ACTION is invoked. If you want to disabled this validation (typically, for a user-defined cancel or quit action), you can set the validate Action Default attribute to zero. This is especially needed in DIALOG instructions; in singular dialogs like INPUT, predefined actions like cancel do not validate the current field value when UNBUFFERED mode is used.
The BDL language predefines some action names for common operations of interactive instructions. These actions are called Predefined Actions, and get a default handler implemented in the dialog internals. For example, the accept action is a predefined action that validates the current dialog.
There are three types of predefined actions:
Note also that dialogs can handle ON IDLE events, but such timeout events are not considered as actions.
Default decoration attributes and keyboard shortcuts are defined in the FGLDIR/lib/default.4ad global action defaults file for predefined actions.
Some predefined actions exist as both automatic actions and as local actions. The automatic actions are created according to the dialog context. If an automatic action has to be defined and if a local action exists with the same name, the automatic action takes precedence over the local action. For example, if the dialog context requires a editcopy runtime action, the local editcopy action will not be handled by the front-end. Identical action names are used for automatic and local action to bind with the same action view. For example, the same toolbar button created with the editcopy name will trigger the automatic action or the local action, according to the context.
If you define your own ON ACTION handler with the name of a predefined action, the default action processing is bypassed and the program code is executed instead.
The next code example defines an ON ACTION clause with the accept predefined action name:
01
INPUT BY NAME customer.*02
ON ACTION accept03
...04
END INPUT
In this case, the default behavior of the automatic accept action is not performed; the user code is executed instead.
Local actions can be overwritten in the same manner, however, this is not recommended (use your own action names).
Some predefined actions (such as insert, append and delete in INPUT ARRAY) are enabled and disabled automatically by the dialog according to the context (for example, when a static array is full, the insert and append actions get disabled).
Even when overwriting such actions with your own action handler, the runtime system will continue to enable and disabled the actions automatically.
As for user-defined actions, if you design forms with 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 the predefined actions.
Action Name | Description | ON ACTION block is required | Context |
Automatic Actions | Automatically created by the runtime system | ||
accept | Validates the current interactive instruction (singular dialogs only) | can overwrite | (1) |
cancel | Cancels the current interactive instruction (singular dialogs only) | can overwrite | (1) |
close | Triggers a cancel key in the current interactive instruction (by default) | can overwrite | (7) |
insert | Inserts a new row before current row | can overwrite | (2) |
append | Appends a new row at the end of the list | can overwrite | (2) |
delete | Deletes the current row | can overwrite | (2) |
find | Opens the fglfind dialog window to let the user enter a search value, and seeks to the row matching the value | can overwrite | (4) |
findnext | Seeks to the next row matching the value entered during the fglfind dialog | can overwrite | (4) |
nextrow | Moves to the next row (only if list using one flat screen record) | can overwrite | (8) |
prevrow | Moves to the previous row (only if list using one flat screen record) | can overwrite | (8) |
firstrow | Moves to the first row (only if list using one flat screen record) | can overwrite | (8) |
lastrow | Moves to the last row (only if list using one flat screen record) | can overwrite | (8) |
help | Shows the help topic defined by the HELP clause | can overwrite | (1) |
editcopy | Copy selected rows (or current row if MRS is off) to the clipboard | can overwrite | (9) |
Special Actions | 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) |
Local Actions | Handled by the front end | ||
editcopy | Copies the current selected text to the clipboard | can overwrite | (7) |
editcut | Copies the current selected text to the clipboard and removes the text from the current input widget | can overwrite | (7) |
editpaste | Pastes the clipboard content to the current input widget | can overwrite | (7) |
nextfield | Moves to the next field in the form | can overwrite | (3) |
prevfield | Moves to the previous field in the form | can overwrite | (3) |
nextrow | Moves to the next row in the list | can overwrite | (4) |
prevrow | Moves to the previous row in the list | can overwrite | (4) |
firstrow | Moves to the first row in the list | can overwrite | (4) |
lastrow | Moves to the last row in the list | can overwrite | (4) |
nextpage | Moves to the next page in the list | can overwrite | (4) |
prevpage | Moves to the previous page in the list | can overwrite | (4) |
nexttab | Moves to the next page in the folder | can overwrite | (6) |
prevtab | Moves to the previous page in the folder | can overwrite | (6) |
Contexts description (last column in above table):
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 invoke 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 first row of the list.
The following table lists all the keyboard accelerator names:
Accelerator Name | Description |
none | Special name indicating 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 |
BackSpace | The BACKSPACE key (do not confuse with DELETE key) |
Delete | The DELETE key (navigation keyboard group) |
Down | The DOWN key (arrow keyboard group) |
End | The END key (navigation keyboard group) |
Enter | The ENTER key (numeric keypad, see Note) |
Escape | The ESCAPE key |
Home | The HOME key (navigation keyboard group) |
Insert | The INSERT key (navigation keyboard group) |
Left | The LEFT key (arrow keyboard group) |
Minus | The MINUS sign key (-) |
Next | The NEXT PAGE key (navigation keyboard group) |
Prior | The PRIOR PAGE key (navigation keyboard group) |
Return | The RETURN key (alphanumeric keypad, see Note) |
Right | The RIGHT key (arrow keyboard group) |
Space | The SPACE-BAR key |
Tab | The TABULATION Key |
Up | The UP key (arrow 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.
Note that 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 "db_busy.per":
01
LAYOUT02
GRID03
{04
Database query in progress...05
[sb ]06
}07
END08
END09
ATTRIBUTES10
BUTTON sb : interrupt, TEXT="Stop";11
END
Program:
01
MAIN02
DEFINE oc INT03
DEFER INTERRUPT04
OPTIONS SQL INTERRUPT ON05
DATABASE stores06
OPEN FORM f FROM "db_busy"07
DISPLAY FORM f08
CALL ui.Interface.refresh()09
LET int_flag=FALSE10
SELECT COUNT(*) INTO oc FROM orders11
IF int_flag THEN12
ERROR "Database query has been interrupted..."13
END IF14
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
The dialogtouched action works for any field controlled by the current interactive instruction, and with any type of form field: Every 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.
You may only want to detect the beginning of a record modification, to enable a "save" action for example. To prevent further dialogtouched action events, just disable that action with a setActionActive() call. If the dialogtouched action is enabled, the ON ACTION block will be invoked each time the user modifies the value in the current field.
Remember that you must disable / enable the dialogtouched action in accordance with the status of the dialog: If this action is enabled, the ON ACTION block will be invoked each time the user types characters (or modifies the value with copy/paste) in the current field; This can generate a lot of network traffic.
The current field may contain some text that does not represent a valid value of the underlying field data type. For example, a form field bound to a
DATE variable may contain only a part of a valid date string, such as
[12/24/ ]
. 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.
To avoid data validation on action code execution, 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 invoked. Since the text of the current field will in most cases contain only a part of a valid data value, that would always result in a conversion error.
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 invoke 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, named close. When the user closes a graphical window, the program gets
a close action.
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 if an ON ACTION cancel handler is defined. If neither ON
ACTION close, nor ON
ACTION cancel are defined, nothing will happen if the user tries to close
the window with the X cross
button or an ALT+F4
keystroke. The int_flag register will not be
set in the context of DIALOG.
When executing an INPUT, INPUT ARRAY, CONSTRUCT, DISPLAY ARRAY or PROMPT, if an ON ACTION close handler is defined, the handler code will be executed. Otherwise, the close action acts the same as the cancel predefined action. So by default when the user clicks the X cross button, the interactive instruction stops and int_flag is set to 1. If there is an explicit ON ACTION cancel block defined, int_flag is set to 1 and the user code below ON ACTION cancel will be executed. Note that if the CANCEL=FALSE option is set, no cancel and no close action will be created, and you must write an ON ACTION close handler to proceed with the close action. In this case, the int_flag register will not be set when the close action is invoked.
When executing a MENU instruction, if an ON ACTION close handler is defined, the handler code will be executed. If no specific close action handler is defined, the code of the COMMAND KEY(INTERRUPT) or ON ACTION cancel will be executed, if defined. if neither COMMAND KEY(INTERRUPT) nor ON ACTION cancel are defined, nothing happens and the program stays in the MENU instruction. Regarding the close action, the value of int_flag is undefined in a MENU instruction.
You typically implement a close action handler to open a confirmation dialog box as in 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