Need to have a user login, get some settings, and then direct them to confirm information? You need a dialog box. To create and use dialog boxes, you need some basic information to get started. This article will introduce dialog boxes and teach you the fundamentals about using them.
Friday, March 16. 2018
LDC #76: Introduction to Dialog Boxes
Introduction
Dialog boxes are a method of interchanging data with the user. A dialog box is a temporary window created by the script to retrieve user input. Dialog boxes usually contain one or more controls (child windows) with which the user can interact to enter text, choose options, or direct some process or action. A control can be something as simple as text providing the user with information or as complex as a box that populates its contents automatically depending on selections within other controls on the dialog. Common dialog controls include text fields, radio boxes, checkboxes, list boxes, and combo boxes. Legato controls conform to Windows standards for appearance and behavior.
Dialog operation is considered modal or modeless. A modal dialog requires the user to respond to the dialog before the script will continue. It will lockout pretty much all foreground processing. Message boxes and common dialogs, such as the Browse Open File dialog, are examples of modal dialogs. Conversely, a modeless dialog can remain on the screen, available for use at any time, while allowing the script to continue its execution. In this series we will be covering modal dialog boxes.
To create a dialog box, you need three things: a resource template, code to create the dialog box, and service code to manage the dialog box. Let’s look at a simple program to retrieve a user name:
string u_name; int remember_me; int main() { int rc; rc = DialogBox("LittleDialog", "ld_"); if (IsError(rc)) { MessageBox('x', "Returned Error:\r\r%s", GetLastErrorMessage()); } else { MessageBox('i', "User Name: %s\r\rRemember Flag: %d", u_name, remember_me); } return ERROR_NONE; } int ld_validate() { u_name = EditGetText(101); if (u_name == "") { MessageBox('x', "Please Enter a User Name!"); return ERROR_SOFT | 101; } remember_me = CheckboxGetState(102); return ERROR_NONE; } #beginresource LittleDialog DIALOGEX 0, 0, 209, 45 EXSTYLE WS_EX_DLGMODALFRAME STYLE DS_MODALFRAME | DS_3DLOOK | WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU CAPTION "Little Dialog" FONT 8, "MS Shell Dlg" { CONTROL "User &Name:", -1, "static", SS_LEFT | WS_CHILD | WS_VISIBLE, 5, 8, 40, 8, 0 CONTROL "", 101, "edit", ES_LEFT | WS_CHILD | WS_VISIBLE | WS_BORDER | WS_TABSTOP, 48, 6, 100, 12, 0 CONTROL "&Remember Me", 102, "button", BS_AUTOCHECKBOX | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 70, 23, 60, 12, 0 CONTROL "OK", IDOK, "button", BS_DEFPUSHBUTTON | BS_CENTER | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 160, 6, 40, 12, 0 CONTROL "Cancel", IDCANCEL, "button", BS_PUSHBUTTON | BS_CENTER | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 160, 25, 40, 12, 0 } #endresource
This will display a little dialog:
Notice the dialog resource is contained within the #beginresource and #endresource directives. The main part of the script opens the dialog and provides code to handle the user’s interaction with it. Resource data can be located anywhere in the script or in a separate include file.
The dialog is opened using the DialogBox Legato function, which specifies one or more resource names and procedure prefixes. The data to be returned is held in global variables. The only procedure in the main script file is ld_validate, and it serves to retrieve the user name.
We’ll drive into an expanded version of this example later.
What is a Dialog Box?
A dialog box consists of two main components: a template or design (resource) and the supporting code to control the behaviors of the dialog. This is unlike message boxes or common dialogs, whose resource and underlying code is managed by the operating system and the script engine. Custom dialogs allow the programmer to design unique dialogs to suit needs specific to his or her application. However, the functionality of that dialog, from adjusting the contents and states of controls to retrieving input data and validating it, is the responsibility of the programmer.
The structure of a dialog is rather simple: it is a window that contains other windows. The Dialog window is a operating system level class that manages all the controls by: positioning and scaling them, managing keyboard interaction (such as quick keys and the Tab or Enter key), and managing interaction between the program and the dialog. For a multiple page or property sheet style dialog, there is another layer that provides a tab rack and pages that holds controls.
A dialog box consists of two or three basic components depending on whether it is a basic or a property sheet style dialog:
The caption area (also known as the Windows non-client area) is an optional area that can contain a title (caption) and various system menu items. In the above example, the context help [?] is enabled and the close box [X] icon appear on the right side of the caption. The second part is the dialog page area, which in turn contains controls. For a basic dialog, the page area will contain all controls and there is a single container window.
For a property sheet style dialog, there are multiple pages controlled by tabs:
The caption is the same, however, as shown above. There can be multiple pages, accessed by selecting the tabs. The OK and Cancel buttons do not belong to any one page, which is a significant difference between basic dialogs and property sheets. They belong to the property sheet container and therefore are not easily accessible. The tab text is the caption for the specific page.
The sizes of the dialog and its controls are determined by the dialog resource. For basic dialogs, the dimensions are as specified by the resource. For property sheets, however, the dimensions specify the size of the inside area of the page with the frame adjusting to the size of the largest page.
Controls are normally composed of a series of common controls or window classes specifically designed for Windows applications. Below is a sample of some of the controls:
There are dozens of common controls and many change their look and feel based on various style flags. For example, the “static” class can display text (legends), lines, boxes and various types of images. For this article, we will be skimming through a couple of controls. Later articles will contain a deeper dive into specific controls.
The appearance of a dialog depends on several factors: the version of Windows, the theme selected by the user, possible third-party modifications, whether the dialog is basic or a property sheet, the settings in the dialog resource, and finally what the programmer changes during the dialog load process.
Since Legato dialogs are based upon Windows dialogs, Legato employs defined terms and control names used by other Windows development platforms. Certain predefined terms are exactly the same and the resource script (.rc file) format follows the Windows resource file convention. As such, it can be opened and edited by a number of common applications, such as Microsoft Visual Studio or ResEdit.
The Windows SDK can be used to obtain further information on many of the underlying concepts and defined styles discussed here and employed within the Legato scripting environment.
The Dialog Resource
As the above examples illustrate, dialog boxes can be rather complex. The heart of the presentation format is the dialog resource template. It is formatted in two sections: a header and a list of controls. Let’s look at our little dialog:
#beginresource LittleDialog DIALOGEX 0, 0, 209, 45 EXSTYLE WS_EX_DLGMODALFRAME STYLE DS_MODALFRAME | DS_3DLOOK | WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU CAPTION "Little Dialog" FONT 8, "MS Shell Dlg" { CONTROL "User &Name:", -1, "static", SS_LEFT | WS_CHILD | WS_VISIBLE, 5, 8, 40, 8, 0 CONTROL "", 101, "edit", ES_LEFT | WS_CHILD | WS_VISIBLE | WS_BORDER | WS_TABSTOP, 48, 6, 100, 12, 0 CONTROL "&Remember Me", 102, "button", BS_AUTOCHECKBOX | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 70, 23, 60, 12, 0 CONTROL "OK", IDOK, "button", BS_DEFPUSHBUTTON | BS_CENTER | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 160, 6, 40, 12, 0 CONTROL "Cancel", IDCANCEL, "button", BS_PUSHBUTTON | BS_CENTER | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 160, 25, 40, 12, 0 } #endresource
Header:
The header includes the following general information:
• The location and dimensions of the dialog box. Note that by default all dialogs are initially centered. The format is:
x, y, w, h, [id]
where the measurements are in dialog units with an optional help ID. Note that with the standard Microsoft dialog statements are DIALOG and DIALOGEX, only the latter allows for a help ID. Legato compiles both these types as DIALOGEX.
• The window and dialog box styles for the dialog box. This consists of STYLE and EXSTYLE which in turn set various Windows style flags. The most common item is WS_SYSMENU which causes little [X] on the upper right corner. The styles definitions (bits) are predefined in the Windows and Legato SDKs. The logical OR (vertical bar) is used to combine styles.
• An optional menu resource for the dialog box.
• A string that specifies the title for the dialog box window or the tab on a property sheet. If the string is empty, the title bar of the dialog box is blank. If the dialog box does not have the WS_CAPTION style, the system sets the title to the specified string but does not display it.
• If the dialog box has the FONT statement in the header, it specifies the point size and typeface name of the font to use for the text in the client area and controls of the dialog box.
Conventional style designations for dialogs will change depending on whether the page is a basic dialog or a page of a property sheet. For example, specifying a modal frame thickness on a property sheet will result in an additional box around the dialog page. Also, frame styles vary greatly between Windows versions. For example, the modal frame designation on Windows 10 looks the same as a non-modal frame but in Windows 8 these styles appeared differently.
Controls:
Following the template header is a body containing one or more control definitions that describe the controls of the dialog box. Each control definition includes the following information:
• The CONTROL or related statement.
• A string that specifies the initial text of the control or an ordinal value that identifies a resource, such as an icon, in an executable file. Depending on the class, the value may be ignored or set to an initial state of a control. For example, a legend normally has the text value set. Dynamic values should generally be left blank.
• The control identifier. The identifier or ID is used to access the control within a dialog page. This is a 16-bit value (signed or unsigned) where -1 has the meaning of no specific control ID. The value -1 is frequently used for controls, such as legends, that do not need to be addressed. There are also standard IDs for IDOK, IDCANCEL, etc. It is good practice to use defined values that can be used in the resource and the code. To keep our example simple, raw numbers were used, but this can lead to confusion. It is also common to start IDs at 100 and group controls together by value. Do not use IDs less than 100 as these number positions are reserved for standard system button values such as OK, Help, Apply, etc. Similarly, values in the 65,000 range are used for system menu commands, such as Move or Maximize.
• The window class name of the control. This can be either the ordinal value of a predefined system class or a string that specifies the name of a registered window class. In our example we are using three classes: “static”, “edit” and “button”. The look and behavior of the control can change dramatically depending on the window styles used for the control.
• The window and control styles for the control. There are three types of styles: window styles, class styles, and extended styles. Window styles include information, such as bits to indicate if a control is disabled, if it starts a group, or if the control is a tab stop. Class styles are specific to the control class. For example, for a “combobox”, one class style indicates whether the control is a list, a dropdown, or an editable dropdown. These bit positions may be shared across various classes, but their meaning will be different. Finally, extended styles apply to the window and the class. The styles definitions (bits) are predefined in the Windows and Legato SDKs. The logical OR (vertical bar) is used to combine styles.
• The location and dimensions of the control. The format is:
x, y, w, h, [id]
This is expressed in dialog units from the top left of the client area of the dialog page. It does not include the caption, tab or any frame areas. The optional ID can be supplied and is passed to the dialog processing code when the user asks for context help.
A legacy format is also supported where the statement specifies the type of control. It essentially combines the CONTROL statement with a class name and certain styles. For example, the AUTOCHECKBOX statement combines the “button” class with the BS_AUTOCHECKBOX style.
At this time, the Legato IDE does not feature a visual editor for dialog resources. There are third party editors available, each with their own pros and cons. In the meantime, resource data can be manually edited.
Resources are compiled during script preprocessing and any syntax errors are displayed at that time.
Measurements and Dialog Units
Every dialog box template contains measurements that specify the position, width, and height of the dialog box and the controls it contains. These measurements are device independent, so a script can use a single template to create the same dialog box for all types of display devices. This ensures that a dialog box will have the same proportions and appearance on all screens despite differing resolutions and aspect ratios between screens. The measurements in a dialog box template are specified in dialog template units. Dialog units are not necessarily scaled the same vertically and horizontally.
Further, the font specified for the overall dialog will also change the scaling.
Opening a Dialog
The DialogBox statement opens a dialog and will not return to the caller until the dialog receives an exit command or encounters an error. For example, OK and Cancel will conventionally end a dialog, which in turn causes the function to return.
int = DialogBox ( string resource | string[] resource | int id,
[string prefix | string[] prefix ], [string caption] );
The prototype for DialogBox ay look a little complex but the design is given you a lot of options. On the simplest level, a resource is specified along with a prefix for the serving procedures. A caption can also be specified at the time of creation. For basic dialogs, specifying a caption will override the resource caption. However, for a property sheet dialog, this is how one specifies a dialog caption. After all, the caption in resource becomes the tab name so we need a method to add a caption to the container window.
Resource names are generally specified as strings, not as a symbol like a function name. This can also be specified as a resource ID as a number. Numeric IDs are another method of referencing resources. To create a property sheet, simply specify an array of dialog resources. This, of course, means a string variable must be used. This is an example of a basic dialog box (as above):
rc = DialogBox("LittleDialog", "ld_");
Now this is a property sheet style:
string dr[2], dp[2]; dr[0] = "UserProps01Dlg"; dp[0] = "up1_"; dr[1] = "UserProps02Dlg"; dp[1] = "up2_"; rc = DialogBox(dr, dp, "User Properties");
For the property sheet style, each page is specified in two arrays: one for the resource names and one for the procedures. It is important to note that for property sheets, a page is not activated and loaded unless it is displayed. That also means that validation will not occur unless the user clicked on the tab or it has been otherwise displayed.
The prefix determines the names of the procedures that are called to service various actions. The most basic are “load”, “action” and “validate”. In our little dialog example, we only have validate, specified as “ld_validate”. If a procedure cannot be located by the dialog processor, default processing is performed.
An int is returned upon exit of the dialog. For normal operation, the return value is the value ‘posted’ to the dialog processor, forcing an exit. As such, cancelling a dialog by pressing a Cancel button (any control with IDCANCEL or 2) will return ERROR_CANCEL.
Specifying a dialog resource that does not exist will result in a runtime error. In addition, if one of the controls on a dialog cannot be created, a runtime error will occur. These can sometimes be difficult to diagnose. Generally, the divide and conquer method works best to identify the offending control. For example, misspelling the name of a class or a class being unavailable will result in the dialog creation process failing.
While runtime coding errors will obviously cause the script to terminate, dialog boxes are a little different in that the dialog procedure must unwind. Also, step debugging does not work within a dialog, so breakpoints are inert. Use the trace log or the #pragma statement to assist in debugging.
Procedures
To service the dialog, procedures must be established. While the dialog box is open, Windows will send messages (or requests) to the script dialog processor. It then looks for a service procedure. If that’s available, it is called. If not, default processing occurs.
There are dozens of available procedures that can be connected to a script simply by defining the name. The Legato Reference manual details each one. As a side note, if you misspell a procedure name, it will not result in an error. Rather, the dialog box processor will not locate the procedure and will perform default processing.
Let’s look at summary of three procedures: load, action and validate.
int prefix_load()
This procedure is called on the initial load of a page. It is called only once and only when a page becomes visible. Within Property Sheet style dialogs (tabbed dialogs), scripts cannot rely on this procedure to be called if the page is not the first to be displayed.
The load procedure is commonly used to load and setup controls while also setting the initial state of each control. For example, combo boxes can be loaded or certain controls disabled or hidden depending on conditions.
The return value is not used.
int prefix_action(int id, int code)
This procedure is called for any control action that generates a message, such as a button press (except OK, Cancel, Help, etc.) or selection change. Note this procedure is called frequently including control focus changes.
The procedure definition should include two int values in the definition, the control id and action code for the control’s ID and the submessage, respectively. Each control has its own defined for submessage values. By convention, the submessage values from command actions are positive and messages passed from a notification are negative. The return value is not used.
An example of using the action procedure would be to enable or disable a set of controls based on a check box change. Another example might be to accept a double click on a list box in the same way as select and OK.
int prefix_validate()
This procedure is called when a page is ready to be validated prior to exit. Returning ERROR_NONE allows the dialog to continue to close and eventually proceed to the ok procedure. Returning any other value prevents the close operation. If an ERROR_ mode is not set, then the low word of the value is used as the control ID on which to set focus (offending value).
There are also non-dialog and non-control messages such as a timer notification or a clipboard change.
Conclusion
I have just touched on how dialog work in this article. There are commonalties in dialog processing across various languages. Most have a template, a method to load, procedures to track or modify the behavior of controls, and methods to validate user input. In the coming months I will delve into various common controls and techniques for designing and implementing quality dialog boxes. Depending on the application, your dialog boxes may be the only part of a program that your users ever see, so it’s important for them to function well.
Scott Theis is the President of Novaworks and the principal developer of the Legato scripting language. He has extensive expertise with EDGAR, HTML, XBRL, and other programming languages. |
Additional Resources
Legato Script Developers LinkedIn Group
Primer: An Introduction to Legato