• Solutions
    • FERC XBRL Reporting
    • FDTA Financial Reporting
    • SEC Compliance
    • Windows Clipboard Management
    • Legato Scripting
  • Products
    • GoFiler Suite
    • XBRLworks
    • SEC Exhibit Explorer
    • SEC Extractor
    • Clipboard Scout
    • Legato
  • Education
    • Training
    • SEC and EDGAR Compliance
    • Legato Developers
  • Blog
  • Support
  • Skip to blog entries
  • Skip to archive page
  • Skip to right sidebar

Friday, April 13. 2018

LDC #80: Dialog Boxes Part II — A Simple Dialog Box

In my first article about dialog boxes, I introduced how dialogs work and provided an overview dialog resources. In the article, we’ll dive into three dialog procedures and some common controls employed in a simple survey program.


Introduction


To explore some procedures and controls, I created a little mock-up media survey program (don’t worry so much about the content or application of the data). The concepts discussed here apply to all dialogs. The survey’s dialog looks like this:


Dialog Example


For this dialog, I opted to try the group style boxes, which I have not used for many years. It turns out that the box style is very lightly colored which does not display well in Windows 10. So I set the background to white. Given a little extra time, I would change it to etched frames.


The point behind this exercise is to demonstrate the load, action, and validate dialog procedures. Along the way we will show one method of storing parameters and also discuss functions related to a few common controls.


Let’s look at our code:


//
//      Dialog Example -- Media Survey
//      ------------------------------
//
//
//

                                                                        ////////////////////////////////////////
                                                                        // Resources

#beginresource
#define ES_NAME                         201
#define ES_FEMALE                       202
#define ES_MALE                         203
#define ES_SELF_ID                      204
#define ES_SELF_ID_TITLE                205
#define ES_SELF_ID_TEXT                 206
#define ES_AGE_GROUP                    207
#define ES_AYS_TERRESTRIAL              211
#define ES_AYS_STREAMING                212
#define ES_AYS_CABLE                    213
#define ES_AYS_SATELLITE                214
#define ES_AYS_DVR                      215
#define ES_OM_DVD                       221
#define ES_OM_BLUE_RAY                  222
#define ES_OM_COMPUTER                  223
#define ES_OM_PLEX                      224
#define ES_OM_OTHER                     225
#define ES_OM_OTHER_TEXT                226
#define ES_SPORTS                       231
#define ES_POLICE                       232
#define ES_DRAMA                        233
#define ES_COOKING                      234
#define ES_HEALTH                       235
#define ES_NEWS                         236
#define ES_REALITY                      237
#define ES_MUSIC                        238
#define ES_CARTOONS                     239
#define ES_FANTASY                      240
#define ES_SCI_FI                       241
#define ES_EDUCATIONAL                  242


EntertainSurveyDialog DIALOGEX 0, 0, 330, 205
EXSTYLE WS_EX_DLGMODALFRAME
STYLE DS_MODALFRAME | DS_3DLOOK | WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU
CAPTION "Home Entertainment"
FONT 8, "MS Shell Dlg"
{
 CONTROL "About You", -1, "button", BS_GROUPBOX | WS_CHILD | WS_VISIBLE | WS_GROUP, 6, 4, 260, 64, 0
 CONTROL "First &Name:", -1, "static", SS_LEFT | WS_CHILD | WS_VISIBLE, 12, 18, 42, 8, 0
 CONTROL "", ES_NAME, "edit", ES_LEFT | ES_AUTOHSCROLL | WS_CHILD | WS_VISIBLE | WS_BORDER | WS_TABSTOP, 60, 16, 100, 12, 0
 CONTROL "Gender:", -1, "static", SS_LEFT | WS_CHILD | WS_VISIBLE, 12, 34, 42, 8, 0
 CONTROL "&Female", ES_FEMALE, "button", BS_AUTORADIOBUTTON | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 60, 32, 40, 12, 0
 CONTROL "&Male", ES_MALE, "button", BS_AUTORADIOBUTTON | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 110, 32, 40, 12, 0
 CONTROL "Self-&Identify", ES_SELF_ID, "button", BS_AUTORADIOBUTTON | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 160, 32, 60, 12, 0
 CONTROL "as:", ES_SELF_ID_TITLE, "static", SS_LEFT | WS_CHILD | WS_VISIBLE, 180, 50, 15, 8, 0
 CONTROL "", ES_SELF_ID_TEXT, "edit", ES_LEFT | ES_AUTOHSCROLL | WS_CHILD | WS_VISIBLE | WS_BORDER | WS_TABSTOP, 196, 48, 60, 12, 0
 CONTROL "&Age Group:", -1, "static", SS_LEFT | WS_CHILD | WS_VISIBLE, 12, 50, 42, 8, 0
 CONTROL "", ES_AGE_GROUP, "combobox", CBS_DROPDOWNLIST | CBS_SORT | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 60, 48, 50, 35, 0
 CONTROL "About Your Service", -1, "button", BS_GROUPBOX | WS_CHILD | WS_VISIBLE | WS_GROUP, 6, 72, 155, 60, 0
 CONTROL "&Terrestrial TV", ES_AYS_TERRESTRIAL, "button", BS_AUTOCHECKBOX | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 12, 84, 60, 12, 0
 CONTROL "Streamin&g", ES_AYS_STREAMING, "button", BS_AUTOCHECKBOX | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 82, 84, 60, 12, 0
 CONTROL "Cable &Box", ES_AYS_CABLE, "button", BS_AUTOCHECKBOX | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 12, 98, 60, 12, 0
 CONTROL "Sate&llite", ES_AYS_SATELLITE, "button", BS_AUTOCHECKBOX | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 82, 98, 60, 12, 0
 CONTROL "&DVR Capable", ES_AYS_DVR, "button", BS_AUTOCHECKBOX | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 29, 112, 60, 12, 0
 CONTROL "Other Media", -1, "button", BS_GROUPBOX | WS_CHILD | WS_VISIBLE | WS_GROUP, 169, 72, 155, 60, 0
 CONTROL "&DVD", ES_OM_DVD, "button", BS_AUTOCHECKBOX | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 180, 84, 60, 12, 0
 CONTROL "Blue Ra&y", ES_OM_BLUE_RAY, "button", BS_AUTOCHECKBOX | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 250, 84, 60, 12, 0
 CONTROL "&Computer", ES_OM_COMPUTER, "button", BS_AUTOCHECKBOX | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 180, 98, 60, 12, 0
 CONTROL "&Plex", ES_OM_PLEX, "button", BS_AUTOCHECKBOX | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 250, 98, 60, 12, 0
 CONTROL "&Other", ES_OM_OTHER, "button", BS_AUTOCHECKBOX | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 180, 112, 40, 12, 0
 CONTROL "", ES_OM_OTHER_TEXT, "edit", ES_LEFT | ES_AUTOHSCROLL | WS_CHILD | WS_VISIBLE | WS_BORDER | WS_TABSTOP, 223, 112, 81, 12, 0
 CONTROL "What You Watch", -1, "button", BS_GROUPBOX | WS_CHILD | WS_VISIBLE | WS_GROUP, 6, 137, 318, 60, 0
 CONTROL "S&ports", ES_SPORTS, "button", BS_AUTOCHECKBOX | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 12, 148, 60, 12, 0
 CONTROL "&Police Drama", ES_POLICE, "button", BS_AUTOCHECKBOX | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 12, 162, 60, 12, 0
 CONTROL "D&rama", ES_DRAMA, "button", BS_AUTOCHECKBOX | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 12, 176, 60, 12, 0
 CONTROL "Coo&king", ES_COOKING, "button", BS_AUTOCHECKBOX | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 82, 148, 60, 12, 0
 CONTROL "&Health", ES_HEALTH, "button", BS_AUTOCHECKBOX | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 82, 162, 60, 12, 0
 CONTROL "Ne&ws", ES_NEWS, "button", BS_AUTOCHECKBOX | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 82, 176, 60, 12, 0
 CONTROL "&Reality TV", ES_REALITY, "button", BS_AUTOCHECKBOX | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 152, 148, 60, 12, 0
 CONTROL "M&usic", ES_MUSIC, "button", BS_AUTOCHECKBOX | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 152, 162, 60, 12, 0
 CONTROL "Cartoons", ES_CARTOONS, "button", BS_AUTOCHECKBOX | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 152, 176, 60, 12, 0
 CONTROL "Fantasy T&V ", ES_FANTASY, "button", BS_AUTOCHECKBOX | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 222, 148, 60, 12, 0
 CONTROL "&Sci F&i", ES_SCI_FI, "button", BS_AUTOCHECKBOX | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 222, 161, 60, 12, 0
 CONTROL "&Educational", ES_EDUCATIONAL, "button", BS_AUTOCHECKBOX | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 222, 175, 60, 12, 0
 CONTROL "OK", IDOK, "button", BS_DEFPUSHBUTTON | BS_CENTER | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 273, 8, 50, 14, 0
 CONTROL "Cancel", IDCANCEL, "button", BS_PUSHBUTTON | BS_CENTER | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 273, 27, 50, 14, 0
}

#endresource

                                                                        ////////////////////////////////////////

#define AGE_GROUPS      "18 – 24,25 – 34,35 – 44,45 – 54,55 – 64,65+"
#define FILE_NAME       "Legato Practice Survey.txt"


                                                                        ////////////////////////////////////////
                                                                        // Data
string  data[];

                                                                        ////////////////////////////////////////
int main() {                                                            // Program Entry

    string              s1, s2;
    
    s1 = GetTempFileFolder() + FILE_NAME;
    s2 = FileToString(s1);
    data = ParametersToArray(s2);

    if (DialogBox("EntertainSurveyDialog", "es_") == ERROR_NONE) {
      s2 = ArrayToParameters(data, "\r\n");
      StringToFile(s2, s1);
      }
    return ERROR_NONE;
    }

                                                                        ////////////////////////////////////////
void set_state() {                                                      // Set Control State

    if (CheckboxGetState(ES_SELF_ID)) {                                 // Gender Controls
      ControlEnable(ES_SELF_ID_TITLE);
      ControlEnable(ES_SELF_ID_TEXT);
      }
    else {
      ControlDisable(ES_SELF_ID_TITLE);
      ControlDisable(ES_SELF_ID_TEXT);
      }
    
    if (CheckboxGetState(ES_AYS_CABLE)) {                               // Cable DVR
      ControlEnable(ES_AYS_DVR);
      }
    else {
      ControlDisable(ES_AYS_DVR);
      }

    if (CheckboxGetState(ES_OM_OTHER)) {                                // Media Other
      ControlEnable(ES_OM_OTHER_TEXT);
      }
    else {
      ControlDisable(ES_OM_OTHER_TEXT);
      }
    }

                                                                        ////////////////////////////////////////
int es_load() {                                                         // Load Dialog

    DialogSetPageColor("white");
                                                                        // About You
    EditSetText(ES_NAME, data["Name"]);

    switch (data["Gender"]) {
      case "":
        break;
      case "Female":
        CheckboxSetState(ES_FEMALE);
        break;
      case "Male":
        CheckboxSetState(ES_MALE);
        break;
      default:
        CheckboxSetState(ES_SELF_ID);
        EditSetText(ES_SELF_ID_TEXT, data["Gender"]);
        break;
      }      

    ComboBoxLoadList(ES_AGE_GROUP, AGE_GROUPS);
    ComboBoxSelectItem(ES_AGE_GROUP, data["Age Group"]);
                                                                        // About Your Service
    if (IsTrue(data["Service Terrestrial"])) { 
      CheckboxSetState(ES_AYS_TERRESTRIAL); }
    if (IsTrue(data["Service Streaming"])) { 
      CheckboxSetState(ES_AYS_STREAMING); }
    if (IsTrue(data["Service Cable"])) { 
      CheckboxSetState(ES_AYS_CABLE); }
    if (IsTrue(data["Service Satellite"])) { 
      CheckboxSetState(ES_AYS_SATELLITE); }
    if (IsTrue(data["Service DVR"])) { 
      CheckboxSetState(ES_AYS_DVR); }

                                                                        // Media
    if (IsTrue(data["Media DVD"])) { 
      CheckboxSetState(ES_OM_DVD); }
    if (IsTrue(data["Media BlueRay"])) { 
      CheckboxSetState(ES_OM_BLUE_RAY); }
    if (IsTrue(data["Media Computer"])) { 
      CheckboxSetState(ES_OM_COMPUTER); }
    if (IsTrue(data["Media Plex"])) { 
      CheckboxSetState(ES_OM_PLEX); }
    if (IsTrue(data["Media Other"])) { 
      CheckboxSetState(ES_OM_OTHER); }
    EditSetText(ES_OM_OTHER_TEXT, data["Media Other Description"]);

                                                                        // What You Watch
    if (IsTrue(data["Media Sports"])) { 
      CheckboxSetState(ES_SPORTS); }
    if (IsTrue(data["Media Police"])) { 
      CheckboxSetState(ES_POLICE); }
    if (IsTrue(data["Media Drama"])) { 
      CheckboxSetState(ES_DRAMA); }
    if (IsTrue(data["Media Cooking"])) { 
      CheckboxSetState(ES_COOKING); }
    if (IsTrue(data["Media Health"])) { 
      CheckboxSetState(ES_HEALTH); }
    if (IsTrue(data["Media News"])) { 
      CheckboxSetState(ES_NEWS); }
    if (IsTrue(data["Media Reality"])) { 
      CheckboxSetState(ES_REALITY); }
    if (IsTrue(data["Media Music"])) { 
      CheckboxSetState(ES_MUSIC); }
    if (IsTrue(data["Media Cartoons"])) { 
      CheckboxSetState(ES_CARTOONS); }
    if (IsTrue(data["Media Fantasy"])) { 
      CheckboxSetState(ES_FANTASY); }
    if (IsTrue(data["Media Sci Fi"])) { 
      CheckboxSetState(ES_SCI_FI); }
    if (IsTrue(data["Media Educational"])) { 
      CheckboxSetState(ES_EDUCATIONAL); }

    set_state();
    return ERROR_NONE;
    }
    

                                                                        ////////////////////////////////////////
void es_action(int c_id, int c_code) {                                  // Control Actions

    if ((c_id == ES_FEMALE) || (c_id == ES_MALE) ||                     // Gender
        (c_id == ES_SELF_ID)) {
      if (CheckboxGetState(ES_SELF_ID) == FALSE) {
        EditSetText(ES_SELF_ID_TEXT, "");
        }
      set_state();
      return ;
      }
    
    if (c_id == ES_AYS_CABLE) {                                         // Cable Can have DVR
      if (CheckboxGetState(ES_AYS_CABLE) == FALSE) {
        CheckboxSetState(ES_AYS_DVR, FALSE);
        }
      set_state();
      return ;
      }

    if (c_id == ES_OM_OTHER) {                                          // Media Other
      if (CheckboxGetState(ES_OM_OTHER) == FALSE) {
        EditSetText(ES_OM_OTHER_TEXT, "");
        }
      set_state();
      return ;
      }
    }

                                                                        ////////////////////////////////////////
int es_validate() {                                                     // Get Controls (Validate)

    string              s, a[];
    int                 flag;
                                                                        // About You

    a["Name"] = EditGetText(ES_NAME);
    if (a["Name"] == "") {
      MessageBox('x', "Please enter your first name.");
      return ERROR_SOFT | ES_NAME;
      }

    s = "";
    if (CheckboxGetState(ES_FEMALE)) { s = "Female"; }
    if (CheckboxGetState(ES_MALE)) { s = "Male"; }
    if (CheckboxGetState(ES_SELF_ID)) { 
      s = EditGetText(ES_SELF_ID_TEXT);
      if (s == "") {
        MessageBox('x', "Please enter your self-identified gender.");
        return ERROR_SOFT | ES_SELF_ID_TEXT;
        }
      }      
    if (s == "") {
      MessageBox('x', "Please select or enter a gender.");
      return ERROR_SOFT | ES_FEMALE;
      }
    a["Gender"] = s;
    
    a["Age Group"] = ComboBoxGetSelectString(ES_AGE_GROUP);
    if (a["Age Group"] == "") {
      MessageBox('x', "Please enter your first name.");
      return ERROR_SOFT | ES_AGE_GROUP;
      }
                                                                        // About Your Service
    flag = FALSE;
    if (CheckboxGetState(ES_AYS_TERRESTRIAL)) { 
      a["Service Terrestrial"] = "True"; 
      flag = TRUE;
      }
    if (CheckboxGetState(ES_AYS_STREAMING)) {
      a["Service Streaming"] = "True"; 
      flag = TRUE;
      }
    if (CheckboxGetState(ES_AYS_CABLE)) { 
      a["Service Cable"] = "True"; 
      flag = TRUE;
      }
    if (CheckboxGetState(ES_AYS_SATELLITE)) {
      a["Service Satellite"] = "True"; 
      flag = TRUE;
      }
    if (CheckboxGetState(ES_AYS_DVR)) {
      a["Service DVR"] = "True"; 
      flag = TRUE;
      }
    if (flag == FALSE) {
      MessageBox('x', "Please select one or more boxes under 'About Your Service'.");
      return ERROR_SOFT | ES_AYS_TERRESTRIAL;
      }
    

    flag = FALSE;
    if (CheckboxGetState(ES_OM_DVD)) {
      a["Media DVD"] = "True"; 
      flag = TRUE;
      }
    if (CheckboxGetState(ES_OM_BLUE_RAY)) {
      a["Media BlueRay"] = "True"; 
      flag = TRUE;
      }
    if (CheckboxGetState(ES_OM_COMPUTER)) {
      a["Media Computer"] = "True"; 
      flag = TRUE;
      }
    if (CheckboxGetState(ES_OM_PLEX)) {
      a["Media Plex"] = "True"; 
      flag = TRUE;
      }
    if (CheckboxGetState(ES_OM_OTHER)) {
      a["Media Other"] = "True"; 
      flag = TRUE;
      a["Media Other Description"] = EditGetText(ES_OM_OTHER_TEXT); 
      }
    if (flag == FALSE) {
      MessageBox('x', "Please select one or more boxes under 'Other Media'.");
      return ERROR_SOFT | ES_OM_DVD;
      }

                                                                        // What You Watch
    flag = FALSE;
    if (CheckboxGetState(ES_SPORTS)) {
      a["Media Sports"] = "True"; 
      flag = TRUE;
      }
    if (CheckboxGetState(ES_POLICE)) {
      a["Media Police"] = "True"; 
      flag = TRUE;
      }
    if (CheckboxGetState(ES_DRAMA)) {
      a["Media Drama"] = "True"; 
      flag = TRUE;
      }
    if (CheckboxGetState(ES_COOKING)) {
      a["Media Cooking"] = "True"; 
      flag = TRUE;
      }
    if (CheckboxGetState(ES_HEALTH)) {
      a["Media Health"] = "True"; 
      flag = TRUE;
      }
    if (CheckboxGetState(ES_NEWS)) {
      a["Media News"] = "True"; 
      flag = TRUE;
      }
    if (CheckboxGetState(ES_REALITY)) {
      a["Media Reality"] = "True"; 
      flag = TRUE;
      }
    if (CheckboxGetState(ES_MUSIC)) {
      a["Media Music"] = "True"; 
      flag = TRUE;
      }
    if (CheckboxGetState(ES_CARTOONS)) {
      a["Media Cartoons"] = "True"; 
      flag = TRUE;
      }
    if (CheckboxGetState(ES_FANTASY)) {
      a["Media Fantasy"] = "True"; 
      flag = TRUE;
      }
    if (CheckboxGetState(ES_SCI_FI)) {
      a["Media Sci Fi"] = "True"; 
      flag = TRUE;
      }
    if (CheckboxGetState(ES_EDUCATIONAL)) {
      a["Media Educational"] = "True"; 
      flag = TRUE;
      }
    if (flag == FALSE) {
      MessageBox('x', "Please select one or more boxes under 'What You Watch'.");
      return ERROR_SOFT | ES_SPORTS;
      }

    data = a;
    return ERROR_NONE;
    }

This program contains quite a bit of code, about 400 lines. Why? Well, for each field we need: (1) a control defined in the resource, (2) a loader and (3) a retriever/validator. We actually saved a bunch of code by using an array with key names to store the data.


Let’s graphically break this down. Our code appears in the green boxes:


Dialog Process Diagram


Our main() function loads the information to the data array, calls the DialogBox SDK function, and then saves the information, assuming the user did not press the Cancel button.


While there are many dialog procedures that our script can hook, this time we will be focusing on load, action and validate. For most basic dialogs, this is all you need.


Passing Data In and Out


The easiest way to pass data in and out of a dialog box is via global variables. If you have a several dialogs, you can share variables or have a set for each dialog, perhaps prefixed with the same sequence as the serving procedure names. In this case, we are using a global string array called data. For illustration purposes, the ParametersToArray and ArrayToParameters functions are used to load and save the data. Our sample will create a file called “Legato Practice Survey.txt” in your temporary file area. It is named with the define FILE_NAME. We are not being careful about errors; as you can see, failure to load the data results in a dialog with no fields set. Not checking for errors would obviously not be optimal in a real-world environment, but in this case it’s alright. 


Procedures


When the DialogBox function is called, our resource name is passed along with a procedure prefix. This prefix is used to determine which functions are called. For example, if the prefix was “mydialog” the DialogBox function would call mydialogload for the load procedure. Adding an underscore to your prefix will make the function names easier to read. The procedures used are as follows:


load (es_load with the example’s prefix)


This procedure is called on the initial loading of a page. It is called only once and only when a page becomes visible. As such, within a Property Sheet style dialog (tabbed dialog), a script should not rely on this procedure to be called if the page is not the first to be displayed.


The load procedure is commonly used to setup controls, load data, and set the initial state of the dialog page. For example, combo boxes can be loaded and certain controls disabled or hidden depending on conditions.  We will go into detail on this below.


For a basic dialog, the return value can be TRUE or FALSE (ERROR_NONE). If it’s FALSE, keyboard focus is set to the specified control using the ControlSetFocus function, or by default, to the first control that can accept keyboard focus in the resource order. If TRUE, focus is set to the first control in the resource order that will accept input without regard to any other focus changes.


For property sheet dialogs, focus is always set to the first control in the resource order that will accept input without regard to any other focus changes.


Returning a formatted error code will abort the dialog load. This is not considered best practice for property sheets since the load procedure is not called until a tab’s page is displayed. If a page tab is clicked and the called load procedure returns an error, the whole property sheet dialog will immediately close and return the error code used to exit the load procedure. For the first tab, a returned error code will cause the page to briefly appear and then close.


action (es_action with the example’s prefix)


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. This means that complex processing should be kept out of this procedure unless it is tied to a specific action such as a button press.


The procedure definition should include two int values: control id and action code for the control’s ID and the submessage, respectively. See each control for submessage values. By convention, submessage values from command actions are positive and messages passed from a notification are negative.


You will notice that there is a notify procedure that is grayed out in the above diagram. This procedure can be used for more advanced common controls and operates in a similar manner to action. We will discuss this in a later article.


The return value for the action procedure is not used. 


validate (es_validate with the example’s prefix)


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 dialog from closing. If an ERROR_ mode is not set, then the lower word of the value is used as the control ID on which to set focus (in other words, the control with the offending value). The high word of the value can be used to change the active page for the control. We will discuss this in a later article.


The validate procedure is not called when a user presses the Cancel button. In that case, the default operation is to close the dialog and return ERROR_CANCEL from the DialogBox function.


Loading Controls


The load procedure allows the programmer to set up a dialog page. Controls can be loaded, items can be enabled or disabled, or any other relevant tasks can be performed. The actual page is not displayed during the load procedure. Only after the procedure exits does the page appear with all of its controls.


Let’s look at some of our dialog load procedure code:


int es_load() {                                                         // Load Dialog

    DialogSetPageColor("white");
                                                                        // About You
    EditSetText(ES_NAME, data["Name"]);

    switch (data["Gender"]) {
      case "":
        break;
      case "Female":
        CheckboxSetState(ES_FEMALE);
        break;
      case "Male":
        CheckboxSetState(ES_MALE);
        break;
      default:
        CheckboxSetState(ES_SELF_ID);
        EditSetText(ES_SELF_ID_TEXT, data["Gender"]);
        break;
      }      

    ComboBoxLoadList(ES_AGE_GROUP, AGE_GROUPS);
    ComboBoxSelectItem(ES_AGE_GROUP, data["Age Group"]);
                                                                        // About Your Service
    if (IsTrue(data["Service Terrestrial"])) { 
      CheckboxSetState(ES_AYS_TERRESTRIAL); }
    if (IsTrue(data["Service Streaming"])) { 
      CheckboxSetState(ES_AYS_STREAMING); }
    if (IsTrue(data["Service Cable"])) { 
      CheckboxSetState(ES_AYS_CABLE); }
    if (IsTrue(data["Service Satellite"])) { 
      CheckboxSetState(ES_AYS_SATELLITE); }
    if (IsTrue(data["Service DVR"])) { 
      CheckboxSetState(ES_AYS_DVR); }

                                                                        // Media
    if (IsTrue(data["Media DVD"])) { 
      CheckboxSetState(ES_OM_DVD); }
    if (IsTrue(data["Media BlueRay"])) { 
      CheckboxSetState(ES_OM_BLUE_RAY); }
    if (IsTrue(data["Media Computer"])) { 
      CheckboxSetState(ES_OM_COMPUTER); }
    if (IsTrue(data["Media Plex"])) { 
      CheckboxSetState(ES_OM_PLEX); }
    if (IsTrue(data["Media Other"])) { 
      CheckboxSetState(ES_OM_OTHER); }
    EditSetText(ES_OM_OTHER_TEXT, data["Media Other Description"]);

                                                                        // What You Watch
    if (IsTrue(data["Media Sports"])) { 
      CheckboxSetState(ES_SPORTS); }
    if (IsTrue(data["Media Police"])) { 
      CheckboxSetState(ES_POLICE); }
    if (IsTrue(data["Media Drama"])) { 
      CheckboxSetState(ES_DRAMA); }
    if (IsTrue(data["Media Cooking"])) { 
      CheckboxSetState(ES_COOKING); }
    if (IsTrue(data["Media Health"])) { 
      CheckboxSetState(ES_HEALTH); }
    if (IsTrue(data["Media News"])) { 
      CheckboxSetState(ES_NEWS); }
    if (IsTrue(data["Media Reality"])) { 
      CheckboxSetState(ES_REALITY); }
    if (IsTrue(data["Media Music"])) { 
      CheckboxSetState(ES_MUSIC); }
    if (IsTrue(data["Media Cartoons"])) { 
      CheckboxSetState(ES_CARTOONS); }
    if (IsTrue(data["Media Fantasy"])) { 
      CheckboxSetState(ES_FANTASY); }
    if (IsTrue(data["Media Sci Fi"])) { 
      CheckboxSetState(ES_SCI_FI); }
    if (IsTrue(data["Media Educational"])) { 
      CheckboxSetState(ES_EDUCATIONAL); }

    set_state();
    return ERROR_NONE;
    }

The procedure is broken into five major sections: initialization, ‘about you’, ‘about your service’, ‘media’, ‘what you watch’, and wrap-up processing. In this case, the only initialization for the overall dialog is to set the background color. Next we address the ‘about you’ section. This is a good time to introduce a little information about the API for common controls. Legato features a series of API functions to access specific type of controls. We will be introducing edit, checkbox and the combobox controls.


The first thing we will do is set text into a control:


EditSetText(ES_NAME, data["Name"]);


The EditSetText function takes a control ID and a string. This function can be used to set text into any control that supports displaying text, including static controls.


The dialog box above also makes use of checkboxes. For example, the participant selecting his or her gender lends itself to a checkbox control (as a radio button style):


    switch (data["Gender"]) {
      case "":
        break;
      case "Female":
        CheckboxSetState(ES_FEMALE);
        break;
      case "Male":
        CheckboxSetState(ES_MALE);
        break;
      default:
        CheckboxSetState(ES_SELF_ID);
        EditSetText(ES_SELF_ID_TEXT, data["Gender"]);
        break;
      }      

Some Legato API functions control both checkboxes and radio buttons. Checkboxes and radio buttons are both styles of the button control class and are differentiated by the BS_AUTOCHECKBOX and BS_AUTORADIOBUTTON styles (or the BS_CHECKBOX and BS_RADIOBUTTON for manual versions). Checkboxes are independent of each other while radio buttons flip among controls in a group, and only one button in the group may be checked.


In our example, there are four allowed states: (i) empty, nothing set; (ii) female; (iii) male; and, (iv) self-identified. Depending on the state, one of the radio buttons is set using the following function:


int = CheckboxSetState ( int id, [int state] );


The CheckboxSetState function sets the state of buttons. If not specified, the default value of state is BST_CHECKED (1 or TRUE). The groups that control which radio buttons are linked are controlled by the order of the buttons in the resource data and the WS_GROUP style. Using a group box does not control the scope of the automatic radio buttons. When more than one group of radio buttons is being deployed a control with WS_GROUP style flags delineates the groups. As highlighted below the three radio buttons are all part of a group since they are all after the “About You” box with the WS_GROUP style.


CONTROL "About You", -1, "button", BS_GROUPBOX | WS_CHILD | WS_VISIBLE | WS_GROUP, 6, 4, 260, 64, 0
CONTROL "First &Name:", -1, "static", SS_LEFT | WS_CHILD | WS_VISIBLE, 12, 18, 42, 8, 0
CONTROL "", ES_NAME, "edit", ES_LEFT | ES_AUTOHSCROLL | WS_CHILD | WS_VISIBLE | WS_BORDER | WS_TABSTOP, 60, 16, 100, 12, 0
CONTROL "Gender:", -1, "static", SS_LEFT | WS_CHILD | WS_VISIBLE, 12, 34, 42, 8, 0
CONTROL "&Female", ES_FEMALE, "button", BS_AUTORADIOBUTTON | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 60, 32, 40, 12, 0
CONTROL "&Male", ES_MALE, "button", BS_AUTORADIOBUTTON | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 110, 32, 40, 12, 0
CONTROL "Self-&Identify", ES_SELF_ID, "button", BS_AUTORADIOBUTTON | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 160, 32, 60, 12, 0
CONTROL "as:", ES_SELF_ID_TITLE, "static", SS_LEFT | WS_CHILD | WS_VISIBLE, 180, 50, 15, 8, 0
CONTROL "", ES_SELF_ID_TEXT, "edit", ES_LEFT | ES_AUTOHSCROLL | WS_CHILD | WS_VISIBLE | WS_BORDER | WS_TABSTOP, 196, 48, 60, 12, 0

During the load procedure it is important to note that the “auto” functionality of radio buttons is ignored. This means if you use the CheckboxSetState function on multiple radio buttons they will all be checked regardless of their grouping and style. Finally, on default the self-identify ‘as:’ edit control is loaded.


On a side note, you may have noticed ‘&‘ characters like ‘First &Name’ on some resource text. These help the dialog processor highlight and process Alt quick keys. So ‘First Name’ can be accessed via Alt+N. When pressed, the processor moves focus to that control or the first control that will accept keyboard input. If there are conflicts, it rotates the highlighting though each match.


The last part of loading controls worth noting here is the combobox used for the age of the participant:


ComboBoxLoadList(ES_AGE_GROUP, AGE_GROUPS);
ComboBoxSelectItem(ES_AGE_GROUP, data["Age Group"]); 


The ComboBoxLoadList function loads the combo control with the list. It is loading a define called AGE_GROUPS:


#define AGE_GROUPS "18 – 24,25 – 34,35 – 44,45 – 54,55 – 64,65+"


There are a number of methods to load data to such a control. In a later article, I will cover the combobox control in detail with each method explored. The data is selected using the ComboBoxSelectItem function.


The remaining controls are loaded using the same methods as just described. When we are done, the set_state function is called, which sets the state of all controls that change state depending on selection context. We discuss that next. 


User Interaction


For more complex, well-designed dialogs, user interaction is interpreted on the fly and the controls should adapt to the context. For our dialog we have three operational contexts defined:


–  ‘Gender’ allows the user to enter a self-identified value.


–  ‘Cable Box’ allows a ‘DVR Capable’ checkbox to be checked (Satellite probably should, too)


–  Media has an ‘Other’ checkbox with an optional description.


Ideally these controls should become available as the context of the data entered demands them. This is possible through the action procedure:


void es_action(int c_id, int code) { ... }


As the user interacts with the dialog, numerous calls are made to the action procedure. It is important to note that hundreds or even thousands of calls may be made to the action procedure during the life of a dialog. Each control type sends actions/messages specific to that control and its settings. For example, by default, button controls send a single message on click, but if the BS_NOTIFY style is set, then messages are sent for focus changes, highlights, and more. For an edit control, Legato will always send notifications focus change, content change, etc. The code indicates the specific actions as defined for the control.


The codes (c_code parameter) meanings can overlap. For example, button notification BN_SETFOCUS and CBN_EDITUPDATE both have the value 6. The meaning of this value depends on the control class as identified by the c_id parameter.


Let’s look closer at our action procedure:


void es_action(int c_id, int c_code) {                                  // Control Actions

    if ((c_id == ES_FEMALE) || (c_id == ES_MALE) ||                     // Gender
        (c_id == ES_SELF_ID)) {
      if (CheckboxGetState(ES_SELF_ID) == FALSE) {
        EditSetText(ES_SELF_ID_TEXT, "");
        }
      set_state();
      return ;
      }
    
    if (c_id == ES_AYS_CABLE) {                                         // Cable Can have DVR
      if (CheckboxGetState(ES_AYS_CABLE) == FALSE) {
        CheckboxSetState(ES_AYS_DVR, FALSE);
        }
      set_state();
      return ;
      }

    if (c_id == ES_OM_OTHER) {                                          // Media Other
      if (CheckboxGetState(ES_OM_OTHER) == FALSE) {
        EditSetText(ES_OM_OTHER_TEXT, "");
        }
      set_state();
      return ;
      }
    }

The first action condition concerns changes to the ‘gender’ controls. Three ids are watched. Any change to these will cause two things to happen: the self id (ES_SELF_ID_TEXT) text is cleared and the state of the dialog is updated. Note that we only need check the state of ES_SELF_ID to see if that control is unchecked. Because our radio buttons were created with the BS_AUTORADIOBUTTON when one is checked the others automatically uncheck. If we did not use this style we would need to handle that behavior ourselves.


The second action checks the ‘cable’ checkbox, which can clear the ‘DVR’ checkbox and update the dialog state.


Finally, the last action of interest is ‘Media Other’, which also clears the associated text control.


All other actions (messages) are ignored.


Updating the Dialog State


We have a simple routine that updates all controls in the dialog at once. Yes, this is a little inefficient but most computers have tens of millions of CPU cycles available, so for ease of coding and debugging, we will do it all at once every time something relevant changes. This also makes it easier to change how controls interact since it is all in one place. The function:


void set_state() {                                                      // Set Control State

    if (CheckboxGetState(ES_SELF_ID)) {                                 // Gender Controls
      ControlEnable(ES_SELF_ID_TITLE);
      ControlEnable(ES_SELF_ID_TEXT);
      }
    else {
      ControlDisable(ES_SELF_ID_TITLE);
      ControlDisable(ES_SELF_ID_TEXT);
      }
    
    if (CheckboxGetState(ES_AYS_CABLE)) {                               // Cable DVR
      ControlEnable(ES_AYS_DVR);
      }
    else {
      ControlDisable(ES_AYS_DVR);
      }

    if (CheckboxGetState(ES_OM_OTHER)) {                                // Media Other
      ControlEnable(ES_OM_OTHER_TEXT);
      }
    else {
      ControlDisable(ES_OM_OTHER_TEXT);
      }
    }

The function checks the state of each conditional context as I described them above and enables or disables controls based on user selection. In a real script, this routine could become very complex. It can hide and show controls, swap out entire sets of overlapping controls, and more. Because of it’s complexity it should only be called when a relevant change is made, including the loading of the dialog.


A function that sets the state also aids in validation because you can prevent the user from entering conflicting data.


Collecting and Validating Data


The last procedure we will cover is validation. The validate procedure is called when the user presses OK or the DialogPostOk function is called. Two primary tasks usually occur here: gathering user input and validating it for correctness. Let us look at the first section of our validation:


int es_validate() {                                                     // Get Controls (Validate)

    string              s, a[];
    int                 flag;
                                                                        // About You

    a["Name"] = EditGetText(ES_NAME);
    if (a["Name"] == "") {
      MessageBox('x', "Please enter your first name.");
      return ERROR_SOFT | ES_NAME;
      }

    s = "";
    if (CheckboxGetState(ES_FEMALE)) { s = "Female"; }
    if (CheckboxGetState(ES_MALE)) { s = "Male"; }
    if (CheckboxGetState(ES_SELF_ID)) { 
      s = EditGetText(ES_SELF_ID_TEXT);
      if (s == "") {
        MessageBox('x', "Please enter your self-identified gender.");
        return ERROR_SOFT | ES_SELF_ID_TEXT;
        }
      }      
    if (s == "") {
      MessageBox('x', "Please select or enter a gender.");
      return ERROR_SOFT | ES_FEMALE;
      }
    a["Gender"] = s;
    
    a["Age Group"] = ComboBoxGetSelectString(ES_AGE_GROUP);
    if (a["Age Group"] == "") {
      MessageBox('x', "Please enter your first name.");
      return ERROR_SOFT | ES_AGE_GROUP;
      }

First, notice we are placing the data into a local array a to later be copied into our global data variable. This is important. Without an alternate version of our working data, if the user presses OK but the validation does not complete because of an error, we will have destroyed the contents of data. If the user then decides to cancel, we have now corrupted our original information. Pressing Cancel should always leave original information unaltered. The last thing we’ll do in our validation procedure is copy the array a over data.


We start by capturing the ‘About You’ group. The first field captured is the ‘First Name’. It is required so we test the data. The EditGetText function trims any spaces so we don’t have to worry about a field full of spaces. The EditGetText function also has some validation built in. Here is the prototype:


string = EditGetText ( int id, [string name], [int flags], [int size] );


By adding a name, the function can report errors back to the user. Setting bits within flags allows for a variety of tests to be performed:



 Definition Bits Description 
 General     
  EGT_FLAG_REQUIRED 0x80000000 Field Is Required 
  EGT_FLAG_DO_NOT_TRIM 0x40000000 Don’t Trim Spaces 
 Field Types     
  EGT_FLAG_TYPE_MASK 0x0F000000 Mask for Type 
  EGT_FLAG_STRING 0x00000000 General String (no test) 
  EGT_FLAG_NUMERIC 0x01000000 Numeric Field 
  EGT_FLAG_CURRENCY 0x02000000 Currency Field 
  EGT_FLAG_HEX 0x07000000 Hex Field (0x00000000) 
 Numeric Options     
  EGT_FLAG_NO_ZERO 0x10000000 Zero Not Allowed 
  EGT_FLAG_UPPER_LIMIT 0x20000000 Upper Limit on Value (embed or void) 
  EGT_FLAG_NEGATIVE 0x40000000 Negative Allowed 
  EGT_FLAG_UPPER_LIMIT_MASK 0x00FFFFFF Upper Limit Value Mask 
 String Options     
  EGT_FLAG_FILE_MUST_EXIST  0x00000001 Check File for Existing File  
  EGT_FLAG_FILE_CAN_OPEN_READ  0x00000002 Check File Can Open as Read (tests for exists as well) 
  EGT_FLAG_FILE_CAN_OPEN_WRITE  0x00000004 Check File Can Open as Write  
  EGT_FLAG_FILE_QUERY_OVERWRITE  0x00000008 Query Overwrite  

 


For this demo, we will just check the string ourselves. On failure, we display a message box and return an error code with the control ID. This prevents the dialog from closing and sets the keyboard focus on the offending field.


The second part of ‘About You’ is the ‘Gender’ section. In this example, I use a little program trick by having the variable s be both the selection and the ‘as’ data. If s comes up empty, they did not enter anything. If not, I can store the data.


The last item is the ‘Age Group’ where I capture the selected string from the combobox using the ComboBoxGetSelectString function. If nothing has been selected, the returned value is an empty string.


Each of the following sections is a series of check boxes. Since we are requiring that the user check at least one box in each group, a we will use the flag to store whether a box was checked and the value will be stored in the array.


    flag = FALSE;
    if (CheckboxGetState(ES_AYS_TERRESTRIAL)) { 
      a["Service Terrestrial"] = "True"; 
      flag = TRUE;
      }
    if (CheckboxGetState(ES_AYS_STREAMING)) {
      a["Service Streaming"] = "True"; 
      flag = TRUE;
      }
    if (CheckboxGetState(ES_AYS_CABLE)) { 
      a["Service Cable"] = "True"; 
      flag = TRUE;
      }
    if (CheckboxGetState(ES_AYS_SATELLITE)) {
      a["Service Satellite"] = "True"; 
      flag = TRUE;
      }
    if (CheckboxGetState(ES_AYS_DVR)) {
      a["Service DVR"] = "True"; 
      }
    if (flag == FALSE) {
      MessageBox('x', "Please select one or more boxes under 'About Your Service'.");
      return ERROR_SOFT | ES_AYS_TERRESTRIAL;
      }

At the end of the group, if flag has not been set, we report an error and stop. After repeating this process, we save the data and exit:


    data = a;
    return ERROR_NONE;
    }

Cross validation for additional requirements or conflicts can also be performed after all the data has been collected.


Validation in property sheets can sometimes get a little more complicated. This is particularly true when data from one page can conflict with data on another page. In addition, if a page is never displayed, Legato (Windows) does not load or validate the page. Again, conflicts are difficult to detect in this situation. There are options to cope with these complexities which I will cover in a later article.


Debugging Techniques


Unfortunately, the IDE does not allow debug stepping inside of a dialog procedure. The best bet is to use debug trace, either on the entire file or using the trace log option in the IDE.


Legato Execution Trace Dump: Survey.ls
---- -- :: ====== SCRIPT ENTRY ======
   107  1 :: s1 = GetTempFileFolder() + FILE_NAME;
   108  1 :: s2 = FileToString(s1);
   109  1 :: data = ParametersToArray(s2);
   111  1 :: if (DialogBox("EntertainSurveyDialog", "es_") == ERROR_NONE) {
   148  2 :: DialogSetPageColor("white");
   150  2 :: EditSetText(ES_NAME, data["Name"]);
   251  2 :: (c_id == ES_SELF_ID)) {
   259  2 :: if (c_id == ES_AYS_CABLE) {
   267  2 :: if (c_id == ES_OM_OTHER) {
   274  2 :: }
^  251  2 :: (c_id == ES_SELF_ID)) {
   259  2 :: if (c_id == ES_AYS_CABLE) {
   267  2 :: if (c_id == ES_OM_OTHER) {
   274  2 :: }

Strategically placed message boxes can also help to inspect the program. However, use caution when placing message boxes (or displaying message boxes as part of the program) within the action procedure, For example, let’s say a message box was added which displays the action code for a control getting focus. You can get stuck in a loop with the message box being displayed and the control regaining focus every time you press OK on the message box. At that point, you are pretty much cooked and will have to terminate the application.


Another option is to add messages to the default log to examine what is happening:


void es_action(int c_id, int c_code) {

    AddMessage("Action %3s - Code %d", c_id, c_code);

    ...

Just firing up the dialog and pressing Cancel will yield this in the log:


Action 201 - Code 1024
Action 201 - Code 768
Action 226 - Code 1024
Action 226 - Code 768
Action 201 - Code 256
Action 201 - Code 512

A basic translation of the IDs and codes:


Action 201 - Code 1024      First Name  -- EN_UPDATE
Action 201 - Code 768       First Name  -- EN_CHANGE
Action 226 - Code 1024      Other Media -- EN_UPDATE
Action 226 - Code 768       Other Media -- EN_CHANGE
Action 201 - Code 256       First Name  -- EN_SETFOCUS
Action 201 - Code 512       First Name  -- EN_KILLFOCUS

Six actions occurred during open and cancel. Remember, the codes are specific to the controls and are also defined in the SDK.


Conclusion


I find that, depending on the application, I may spend far more time building a dialog interface than writing code to perform the actual functions associated with the dialog. A well designed interface makes for a better user experience. In later articles I will cover the details of specific common controls and some of the other dialog procedures.


Hopefully this information will help you build a more effective user interface and reduce development time.


 


 


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

Novaworks’ Legato Resources

Legato Script Developers LinkedIn Group

Primer: An Introduction to Legato 



Posted by
Scott Theis
in Development at 18:08
Trackbacks
Trackback specific URI for this entry

No Trackbacks

Comments
Display comments as (Linear | Threaded)
No comments
The author does not allow comments to this entry

Quicksearch

Categories

  • XML Accounting
  • XML AICPA News
  • XML FASB News
  • XML GASB News
  • XML IASB News
  • XML Development
  • XML Events
  • XML FERC
  • XML eForms News
  • XML FERC Filing Help
  • XML Filing Technology
  • XML Information Technology
  • XML Investor Education
  • XML MSRB
  • XML EMMA News
  • XML FDTA
  • XML MSRB Filing Help
  • XML Novaworks News
  • XML GoFiler Online Updates
  • XML GoFiler Updates
  • XML XBRLworks Updates
  • XML SEC
  • XML Corporation Finance
  • XML DERA
  • XML EDGAR News
  • XML Investment Management
  • XML SEC Filing Help
  • XML XBRL
  • XML Data Quality Committee
  • XML GRIP Taxonomy
  • XML IFRS Taxonomy
  • XML US GAAP Taxonomy

Calendar

Back May '25 Forward
Mo Tu We Th Fr Sa Su
Sunday, May 18. 2025
      1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31  

Feeds

  • XML
Sign Up Now
Get SEC news articles and blog posts delivered monthly to your inbox!
Based on the s9y Bulletproof template framework

Compliance

  • FERC
  • EDGAR
  • EMMA

Software

  • GoFiler Suite
  • SEC Exhibit Explorer
  • SEC Extractor
  • XBRLworks
  • Legato Scripting

Company

  • About Novaworks
  • News
  • Site Map
  • Support

Follow Us:

  • LinkedIn
  • YouTube
  • RSS
  • Newsletter
  • © 2024 Novaworks, LLC
  • Privacy
  • Terms of Use
  • Trademarks and Patents
  • Contact Us