• 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, March 15. 2019

LDC #127: Checkbox and Radio Button Controls Part 1

For user selection controls within dialog boxes, there are three conventional options: checkboxes, radio buttons and list/combo boxes. Each has its application since each has its strengths and weaknesses. Sometimes developers make poor choices concerning which of the three to use on their on dialogs or web pages. Bad implementations may still work, but they provide a less intuitive user experience, and these uses may not be in the spirit for which the controls were designed. Hopefully this article will help improve your knowledge and therefore ability to choose a selection control through providing information on how each type works.


Options, Options, Options


So which type is the best? That depends on the situation and what each can do for the programmer and the end user. Let’s examine the differences:


Checkbox — Designed, like a paper form, to check or affirm a condition or option. Like a paper form, a checkbox connotes “Check All That Apply”, meaning more than one item can be checked. In addition, it can make sense to have a single checkbox to toggle an “on/off” or “yes/no” condition. Checkboxes are not initially programmatically interrelated, which means when you place them on a dialog, they operate independently, even if they are visually grouped together.


Radio Button — Designed, like a paper form, to check only one. For example, consider the options “Residence: Single Family Home, Duplex, Multi Unit Home, Townhome or Apartment”. The user is expected to check only one. This is an important distinction from a checkbox.


Combo Box — For Windows (and Legato). Combo boxes are a combination of a list box and an optional text box. They can be set up to allow only selections from a list. In that mode, combo boxes are similar to radio buttons. Other implementations, such as HTML or custom classes, may allow multiple selections, but the display scheme may or may not allow all selections to be seen.


List Box — List boxes can be setup to allow either single or multiple selections.


In reality, there are two implementation scenarios for user selection: allowing the user to pick as many items that apply as desired or permitting the user to select only one item. For the latter case, check boxes and list boxes are particularly good choices for a selection control. This blog will examine the checkbox and list box alternative. The example code shares the same data set to the two programs can be easily compared. Next week will cover the radio button and the combo box option.


Example Checkbox Implementation


For our checkbox example, I have written a short script that can query as user as to his or her allergies. While I am certain there are a lot more allergies, this gives an example of using the checkbox with common allergies.


Checkbox Example


Dialog with Checkboxes


Upon pressing OK, the selections are saved in an INI file and a message box displays the result:


Message box containing the checked values


This also provides a demonstration of the storage of information. Of course, there are many other options and techniques. Once you set selections and press OK, the next time the dialog loads, it will remember the previous selections.


The Code



                                                                        // Resource
#beginresource

#define CB_BOX_NONE                     201
#define CB_BOX_MILK                     202
#define CB_BOX_EGG                      203
#define CB_BOX_PEANUT                   204
#define CB_BOX_TREE_NUT                 205
#define CB_BOX_WHEAT                    206
#define CB_BOX_SOY                      207
#define CB_BOX_FISH                     208
#define CB_BOX_SHELLFISH                209
#define CB_BOX_SESAME                   210
#define CB_BOX_OTHER                    211
#define CB_OTHER_TITLE                  212
#define CB_OTHER_TEXT                   213

CheckboxExample01Dlg DIALOGEX 0, 0, 240, 130
EXSTYLE WS_EX_DLGMODALFRAME
STYLE DS_MODALFRAME | DS_3DLOOK | WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU
CAPTION "Check Box Example"
FONT 8, "MS Shell Dlg"
{
 CONTROL "Food Allergies:", -1, "static", SS_LEFT | WS_CHILD | WS_VISIBLE, 6, 6, 60, 8, 0
 CONTROL "", -1, "static", SS_ETCHEDFRAME | WS_CHILD | WS_VISIBLE, 56, 11, 176, 1
 CONTROL "&None", CB_BOX_NONE, "button", BS_AUTOCHECKBOX | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 14, 18, 60, 12, 0
 CONTROL "&Milk", CB_BOX_MILK, "button", BS_AUTOCHECKBOX | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 14, 30, 60, 12, 0
 CONTROL "&Egg", CB_BOX_EGG, "button", BS_AUTOCHECKBOX | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 84, 30, 60, 12, 0
 CONTROL "&Peanut", CB_BOX_PEANUT, "button", BS_AUTOCHECKBOX | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 154, 30, 60, 12, 0
 CONTROL "&Tree Nut", CB_BOX_TREE_NUT, "button", BS_AUTOCHECKBOX | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 14, 42, 60, 12, 0
 CONTROL "&Wheat", CB_BOX_WHEAT, "button", BS_AUTOCHECKBOX | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 84, 42, 60, 12, 0
 CONTROL "&Soy", CB_BOX_SOY, "button", BS_AUTOCHECKBOX | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 154, 42, 60, 12, 0
 CONTROL "&Fish", CB_BOX_FISH, "button", BS_AUTOCHECKBOX | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 14, 54, 60, 12, 0
 CONTROL "S&hellfish", CB_BOX_SHELLFISH, "button", BS_AUTOCHECKBOX | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 84, 54, 60, 12, 0
 CONTROL "Ses&ame", CB_BOX_SESAME, "button", BS_AUTOCHECKBOX | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 154, 54, 60, 12, 0
 CONTROL "&Other", CB_BOX_OTHER, "button", BS_AUTOCHECKBOX | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 14, 66, 40, 12, 0
 CONTROL "E&xplain:", CB_OTHER_TITLE, "static", WS_CHILD | WS_VISIBLE, 60, 68, 60, 12, 0
 CONTROL "", CB_OTHER_TEXT, "edit", ES_AUTOHSCROLL | WS_CHILD | WS_VISIBLE | WS_BORDER | WS_TABSTOP, 60, 82, 160, 12
 CONTROL "", -1, "static", SS_ETCHEDFRAME | WS_CHILD | WS_VISIBLE, 6, 102, 226, 1
 CONTROL "OK", IDOK, "button", BS_DEFPUSHBUTTON | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 127, 108, 50, 14, 0
 CONTROL "Cancel", IDCANCEL, "button", BS_PUSHBUTTON | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 180, 108, 50, 14, 0
}

#endresource

                                                                        // Dialog Data
    string              cb_allergies;
    string              cb_other_text;

                                                                        // Main Entry
int main() {
    int                 rc;

    cb_allergies = GetSetting("Dialog Examples", "Multiple Select Options", "Allergies");
    cb_other_text = GetSetting("Dialog Examples", "Multiple Select Options", "Allergies Other");

    rc = DialogBox("CheckboxExample01Dlg", "cb_");
    if (IsNotError(rc))  {
      PutSetting("Dialog Examples", "Multiple Select Options", "Allergies", cb_allergies);
      PutSetting("Dialog Examples", "Multiple Select Options", "Allergies Other", cb_other_text);
      MessageBox("Codes: %s\r\rOtherText: %s", cb_allergies, cb_other_text);
      }
    return rc;
    }

                                                                        // In Dialog Proc - Set State
void cb_set_state() {

    if (CheckboxGetState(CB_BOX_NONE)) {
      ControlDisable(CB_BOX_MILK);
      ControlDisable(CB_BOX_EGG);
      ControlDisable(CB_BOX_PEANUT);
      ControlDisable(CB_BOX_TREE_NUT);
      ControlDisable(CB_BOX_WHEAT);
      ControlDisable(CB_BOX_SOY);
      ControlDisable(CB_BOX_FISH);
      ControlDisable(CB_BOX_SHELLFISH);
      ControlDisable(CB_BOX_SESAME);
      ControlDisable(CB_BOX_OTHER);
      ControlDisable(CB_OTHER_TITLE);
      ControlDisable(CB_OTHER_TEXT);
      }
    else {
      ControlEnable(CB_BOX_MILK);
      ControlEnable(CB_BOX_EGG);
      ControlEnable(CB_BOX_PEANUT);
      ControlEnable(CB_BOX_TREE_NUT);
      ControlEnable(CB_BOX_WHEAT);
      ControlEnable(CB_BOX_SOY);
      ControlEnable(CB_BOX_FISH);
      ControlEnable(CB_BOX_SHELLFISH);
      ControlEnable(CB_BOX_SESAME);
      ControlEnable(CB_BOX_OTHER);
      }
    if (CheckboxGetState(CB_BOX_OTHER)) {
      ControlEnable(CB_OTHER_TITLE);
      ControlEnable(CB_OTHER_TEXT);
      }
    else {
      ControlDisable(CB_OTHER_TITLE);
      ControlDisable(CB_OTHER_TEXT);
      }
    }

                                                                        // In Dialog Proc - Load
int cb_load() {

    if (ScanString(cb_allergies, "None") >= 0) {
      CheckboxSetState(CB_BOX_NONE);
      }
    if (ScanString(cb_allergies, "Milk") >= 0) {
      CheckboxSetState(CB_BOX_MILK);
      }
    if (ScanString(cb_allergies, "Egg") >= 0) {
      CheckboxSetState(CB_BOX_EGG);
      }
    if (ScanString(cb_allergies, "Peanut") >= 0) {
      CheckboxSetState(CB_BOX_PEANUT);
      }
    if (ScanString(cb_allergies, "Tree Nut") >= 0) {
      CheckboxSetState(CB_BOX_TREE_NUT);
      }
    if (ScanString(cb_allergies, "Wheat") >= 0) {
      CheckboxSetState(CB_BOX_WHEAT);
      }
    if (ScanString(cb_allergies, "Soy") >= 0) {
      CheckboxSetState(CB_BOX_SOY);
      }
    if (ScanString(cb_allergies, "Fish") >= 0) {
      CheckboxSetState(CB_BOX_FISH);
      }
    if (ScanString(cb_allergies, "Shellfish") >= 0) {
      CheckboxSetState(CB_BOX_SHELLFISH);
      }
    if (ScanString(cb_allergies, "Sesame") >= 0) {
      CheckboxSetState(CB_BOX_SESAME);
      }
    if (ScanString(cb_allergies, "Other") >= 0) {
      CheckboxSetState(CB_BOX_OTHER);
      EditSetText(CB_OTHER_TEXT, cb_other_text);
      }

    cb_set_state();
    return ERROR_NONE;
    }

                                                                        // In Dialog Proc - Control Change
void cb_action(int c_id, int c_ac) {

    int                 state;

    if (c_id == CB_BOX_NONE) {
      state = CheckboxGetState(c_id);
      if (state != FALSE) {
        CheckboxSetState(CB_BOX_MILK, 0);
        CheckboxSetState(CB_BOX_EGG, 0);
        CheckboxSetState(CB_BOX_PEANUT, 0);
        CheckboxSetState(CB_BOX_TREE_NUT, 0);
        CheckboxSetState(CB_BOX_WHEAT, 0);
        CheckboxSetState(CB_BOX_SOY, 0);
        CheckboxSetState(CB_BOX_FISH, 0);
        CheckboxSetState(CB_BOX_SHELLFISH, 0);
        CheckboxSetState(CB_BOX_SESAME, 0);
        CheckboxSetState(CB_BOX_OTHER, 0);
        EditSetText(CB_OTHER_TEXT, "");
        }
      }

    if (c_id == CB_BOX_OTHER) {
      if (CheckboxGetState(c_id) == FALSE) {
        EditSetText(CB_OTHER_TEXT, "");
        }
      }

    if ((c_id == CB_BOX_NONE) ||
        (c_id == CB_BOX_MILK) ||
        (c_id == CB_BOX_EGG) ||
        (c_id == CB_BOX_PEANUT) ||
        (c_id == CB_BOX_TREE_NUT) ||
        (c_id == CB_BOX_WHEAT) ||
        (c_id == CB_BOX_SOY) ||
        (c_id == CB_BOX_FISH) ||
        (c_id == CB_BOX_SHELLFISH) ||
        (c_id == CB_BOX_SESAME) ||
        (c_id == CB_BOX_OTHER)) {
      cb_set_state();
      }
    }

                                                                        // In Dialog Proc - Pressed OK
int cb_validate() {

    string              s1, s2;

    if (CheckboxGetState(CB_BOX_NONE)) {
      s1 = AppendWithDelimiter(s1, "None");
      }
    if (CheckboxGetState(CB_BOX_MILK)) {
      s1 = AppendWithDelimiter(s1, "Milk");
      }
    if (CheckboxGetState(CB_BOX_EGG)) {
      s1 = AppendWithDelimiter(s1, "Egg");
      }
    if (CheckboxGetState(CB_BOX_PEANUT)) {
      s1 = AppendWithDelimiter(s1, "Peanut");
      }
    if (CheckboxGetState(CB_BOX_TREE_NUT)) {
      s1 = AppendWithDelimiter(s1, "Tree Nut");
      }
    if (CheckboxGetState(CB_BOX_WHEAT)) {
      s1 = AppendWithDelimiter(s1, "Wheat");
      }
    if (CheckboxGetState(CB_BOX_SOY)) {
      s1 = AppendWithDelimiter(s1, "Soy");
      }
    if (CheckboxGetState(CB_BOX_FISH)) {
      s1 = AppendWithDelimiter(s1, "Fish");
      }
    if (CheckboxGetState(CB_BOX_SHELLFISH)) {
      s1 = AppendWithDelimiter(s1, "Shellfish");
      }
    if (CheckboxGetState(CB_BOX_SESAME)) {
      s1 = AppendWithDelimiter(s1, "Sesame");
      }
    if (CheckboxGetState(CB_BOX_OTHER)) {
      s1 = AppendWithDelimiter(s1, "Other");
      s2 = EditGetText(CB_OTHER_TEXT);
      if (s2 == "") {
        MessageBox('X', "Please specify the nature of the other allergy.");
        return ERROR_SOFT | CB_OTHER_TEXT;
        }
      }

    if (s1 == "") {
      MessageBox('X', "Select at least one allergy or pick 'None'.");
      return ERROR_SOFT | CB_BOX_NONE;
      }

    cb_allergies = s1;
    cb_other_text = s2;
    return ERROR_NONE;
    }


The program is divided into seven sections: resource declaration, main entry, global data, control state management, load, control action, and validation.


Details on the checkbox CONTROL resource statement is covered below. The dialog itself is basic (a single page, not a property sheet style) dialog. One thing to notice is the volume of code associated with each allergy type. Each one requires a id declaration, control declaration, and associated code to manage it. This adds to the development time and, of course, maintenance if fields are added or removed. Given the number of controls in this example, this is not a big issue here, but obviously with many possible choices, it could become a problem.


The next part contains the global declarations. We have two: a string for a list of allergies and a string for the text pertaining to the “Other” field. Since aside from an error code, data cannot be passed to and from a dialog, global data is the method.


Entry is via the main() function. This function loads previous settings, launches the dialog. If all of its operations complete OK (ERROR_NONE), it then stores the result. Note that the global data is not altered by the dialog procedure unless the validation completes successfully. This is a good practice and makes for a clean cancel operation.


Then we have the dialog procedures. The first, the cb_set_state() function, is actually not an internally defined dialog procedure (i.e., it is not called directly by the internal dialog manager). It actually serves other procedures by setting the state of the controls based on the context of certain other controls. For example, checking ‘None’ should cause everything to become disabled. Uncheck this item, the function will enable most of the controls except for the ‘Other’ text selection, which in turn has its own logic.


The loader is pretty straight forward. It searches for tokens in the allergies string, using the ScanString SDK function, and sets checkboxes as required. This does allow bad data to filter in, such as having ‘None’ checked while having others checked. However, that should never happen under normal circumstances.


Response to control changes is a little more complex. An affirmative to ‘None’ causes all other controls to be cleared. Unchecking ‘Other’ clears the explanation. Any checkbox action results in a state check. Note that in the current resource declaration, the ONLY messages being send by checkboxes are state change messages. If notifications were allowed (BS_NOTIFY), then the action function MUST look at the control action type to respond. In this example, as is commonly implemented, the checkboxes are automatic, meaning that upon a user mouse click (or space bar) the button is toggled between checked and unchecked. This is the BS_AUTOCHECKBOX style. Other styles require the intervention of the action function to determine how to set the state upon a check change.


Finally, pressing the OK button calls the cb_validate() function. It gets the state of each checkbox and adds tokens to a temporary string s1. If ‘Other’ is checked, the ‘Other’ text edit control is tested for a value. The entire resultant string containing the checked allergies is examined to determine if it’s empty (in other words, to make sure the user selected something). Assuming all is okay, the dialog exits.


Checkbox API Functions


This is good place to look at the checkbox API functions. Let’s start with functions pertaining to a checkbox’s state:


int = CheckboxGetState ( int id );


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


The state parameter values (and returned value) are as follows:


 Return code Code Description 
 BST_CHECKED 0x00000001 Button is checked 
 BST_INDETERMINATE 0x00000002 Button is grayed, indicating an indeterminate state (applies only if the button has the BS_3STATE or BS_AUTO3STATE style). 
 BST_UNCHECKED 0x00000000 Button is cleared 


For two state checkboxes, the state of the checkbox can be used as a boolean value for conditional statements (i.e., if the checkbox is checked, the following code should be executed). For tri-state checkboxes, you must explicitly test its state against the various cases (checked, unchecked, and indeterminate).


The remaining functions are not used in the example code, but here they are for your edification:


string = CheckboxGetText ( int id );


int = CheckboxSetText ( int id, string text );


These two functions allow the legend (the text attached to the checkbox) to be read and/or set.


The checkbox’s highlight state can also be set:


int = CheckboxHighlight ( int id, boolean state );


Finally, an image can be added if either the BS_ICON or BS_BITMAP styles are set.


int = CheckboxSetImage ( int id, handle hResource | string name );


Checkbox Resource Details


The key to the resource declarations is the CONTROL statement. It operates with the checkbox class as it does for any other class. We are selecting the Windows SDK common control “button”. It is made into a checkbox by the use of the BS_AUTOCHECKBOX style.



    CheckboxExample01Dlg DIALOGEX 0, 0, 240, 130
    EXSTYLE WS_EX_DLGMODALFRAME
    STYLE DS_MODALFRAME | DS_3DLOOK | WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU
    CAPTION "Check Box Example"
    FONT 8, "MS Shell Dlg"
    {
     CONTROL "Food Allergies:", -1, "static", SS_LEFT | WS_CHILD | WS_VISIBLE, 6, 6, 60, 8, 0
     CONTROL "", -1, "static", SS_ETCHEDFRAME | WS_CHILD | WS_VISIBLE, 56, 11, 176, 1
     CONTROL "&None", CB_BOX_NONE, "button", BS_AUTOCHECKBOX | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 14, 18, 60, 12, 0
     CONTROL "&Milk", CB_BOX_MILK, "button", BS_AUTOCHECKBOX | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 14, 30, 60, 12, 0
     CONTROL "&Egg", CB_BOX_EGG, "button", BS_AUTOCHECKBOX | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 84, 30, 60, 12, 0
     CONTROL "&Peanut", CB_BOX_PEANUT, "button", BS_AUTOCHECKBOX | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 154, 30, 60, 12, 0
     CONTROL "&Tree Nut", CB_BOX_TREE_NUT, "button", BS_AUTOCHECKBOX | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 14, 42, 60, 12, 0
     CONTROL "&Wheat", CB_BOX_WHEAT, "button", BS_AUTOCHECKBOX | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 84, 42, 60, 12, 0
     CONTROL "&Soy", CB_BOX_SOY, "button", BS_AUTOCHECKBOX | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 154, 42, 60, 12, 0
     CONTROL "&Fish", CB_BOX_FISH, "button", BS_AUTOCHECKBOX | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 14, 54, 60, 12, 0
     CONTROL "S&hellfish", CB_BOX_SHELLFISH, "button", BS_AUTOCHECKBOX | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 84, 54, 60, 12, 0
     CONTROL "Ses&ame", CB_BOX_SESAME, "button", BS_AUTOCHECKBOX | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 154, 54, 60, 12, 0
     CONTROL "&Other", CB_BOX_OTHER, "button", BS_AUTOCHECKBOX | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 14, 66, 40, 12, 0
     CONTROL "E&xplain:", CB_OTHER_TITLE, "static", WS_CHILD | WS_VISIBLE, 60, 68, 60, 12, 0
     CONTROL "", CB_OTHER_TEXT, "edit", ES_AUTOHSCROLL | WS_CHILD | WS_VISIBLE | WS_BORDER | WS_TABSTOP, 60, 82, 160, 12
     CONTROL "", -1, "static", SS_ETCHEDFRAME | WS_CHILD | WS_VISIBLE, 6, 102, 226, 1
     CONTROL "OK", IDOK, "button", BS_DEFPUSHBUTTON | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 127, 108, 50, 14, 0
     CONTROL "Cancel", IDCANCEL, "button", BS_PUSHBUTTON | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 180, 108, 50, 14, 0
    }


Control declarations have the following form:


CONTROL text, id, class, style, x, y, width, height, [extended-style]


The text parameter is the legend (text) of the control. Note the use of ‘&‘ prefixes to create keyboard shortcuts. Controls are referenced in the code via the 16-bit id parameter. The main part of the control’s behavior is set by the style parameter which has two parts: basic Windows SDK window bits and bits specific to the class, WS_ and BS_ prefixes, respectively.


Checkbox specific button styles are as follows:


 Constant Description 
 BS_3STATE Creates a button that is the same as a checkbox, except that the box can be grayed as well as checked or cleared. Use the grayed state to show that the state of the checkbox is indeterminate. 
 BS_AUTO3STATE Creates a button that is the same as a three-state check box, except that the box changes its state when the user selects it. The state cycles through checked, indeterminate, and cleared. 
 BS_AUTOCHECKBOX Creates a button that is the same as a checkbox, except that the check state automatically toggles between checked and cleared each time the user selects the check box. 
 BS_BITMAP Specifies that the button displays a bitmap. 
 BS_CHECKBOX Creates a small, empty checkbox with text. By default, the text is displayed to the right of the check box. To display the text to the left of the check box, combine this flag with the BS_LEFTTEXT style (or with the equivalent BS_RIGHTBUTTON style). 
 BS_ICON Specifies that the button displays an icon. 
 BS_NOTIFY Enables a button to send BN_KILLFOCUS and BN_SETFOCUS notification codes to its parent window. Note that buttons send the BN_CLICKED notification code regardless of whether it has this style. To get BN_DBLCLK notification codes, the button must have the BS_RADIOBUTTON or BS_OWNERDRAW style. 
 BS_PUSHLIKE Makes a button (such as a checkbox, three-state checkbox, or radio button) look and act like a push button. The button looks raised when it isn’t pushed or checked and sunken when it is pushed or checked. 

The position parameters are in dialog units with 0,0 being in the upper left of the client area of the dialog page.


Notifications


In our example, we are assuming that every notification is a click. If BS_NOTIFY is set, then a number of actions will be dispatched:


 Define Code Description 
 BN_CLICKED  0 Sent when the user clicks a button. 
 BN_HILITE  2 Sent when the user selects a button. 
 BN_UNHILITE  3 Sent when the highlight should be removed from a button. This is used principally in owner drawn controls. 
 BN_DISABLE  4 Sent when a button is disabled. This is used principally in owner drawn controls. 
 BN_DOUBLECLICKED  5 Sent when the user double-clicks a button. This notification code is sent automatically for BS_USERBUTTON, BS_RADIOBUTTON, and BS_OWNERDRAW buttons. Other button types send BN_DOUBLECLICKED only if they have the BS_NOTIFY style. 
 BN_PUSHED  BN_HILITE Sent when the push state of a button is set to pushed. This is used principally in owner drawn controls. 
 BN_UNPUSHED  BN_UNHILITE Sent when the push state of a button is set to unpushed. This is used principally in owner drawn controls. 
 BN_DBLCLK  BN_DOUBLECLICKED Same as BN_DOUBLECLICKED. 
 BN_SETFOCUS  6 Sent when a button receives the keyboard focus. The button must have the BS_NOTIFY style to send this notification code. 
 BN_KILLFOCUS  7 Sent when a button loses the keyboard focus. The button must have the BS_NOTIFY style to send this notification code. 

 


List Box Implementation


I covered the operation of the list box control in LDC #84: Dialog Boxes Part III — List Boxes. There is more information on functions and styles in that article. In this section I will cover only those things that apply to the multiple item selection paradigm to make it comparable to checkbox functionality.


List Box Example


Let’s begin by looking at our list box example matching the functionality of the check box example:


Dialog with list box


The first thing you will notice is that as a stand along option, the list box method makes the dialog look lopsided. We will ignore that for the sake of comparison. In certain dialogs, this design makes sense (we used this method in an early product for EDGAR SROS selections). A list box with multiple select style, LBS_MULTIPLESEL, is employed:


Dialog with list box with multiple items selected


The Code


Like the checkbox example, the code is divided into seven sections: resource declaration, main entry, global data, control state management, load, control action, and validation. As you will see, there is a lot less code in this example.



                                                                        // Resource
#beginresource

#define LB_SELECTIONS                   201
#define LB_OTHER_TITLE                  212
#define LB_OTHER_TEXT                   213

ListBoxExample01Dlg DIALOGEX 0, 0, 240, 150
EXSTYLE WS_EX_DLGMODALFRAME
STYLE DS_MODALFRAME | DS_3DLOOK | WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU
CAPTION "List Box Example"
FONT 8, "MS Shell Dlg"
{
 CONTROL "Food &Allergies:", -1, "static", SS_LEFT | WS_CHILD | WS_VISIBLE, 6, 6, 60, 8, 0
 CONTROL "", -1, "static", SS_ETCHEDFRAME | WS_CHILD | WS_VISIBLE, 56, 11, 176, 1
 CONTROL "", LB_SELECTIONS, "listbox", LBS_NOTIFY | LBS_MULTIPLESEL | LBS_NOINTEGRALHEIGHT | WS_BORDER | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 14, 19, 40, 92
 CONTROL "Other E&xplain:", LB_OTHER_TITLE, "static", WS_CHILD | WS_VISIBLE, 65, 85, 60, 12, 0
 CONTROL "", LB_OTHER_TEXT, "edit", ES_AUTOHSCROLL | WS_CHILD | WS_VISIBLE | WS_BORDER | WS_TABSTOP, 65, 98, 160, 12
 CONTROL "", -1, "static", SS_ETCHEDFRAME | WS_CHILD | WS_VISIBLE, 6, 120, 226, 1
 CONTROL "OK", IDOK, "button", BS_DEFPUSHBUTTON | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 127, 126, 50, 14, 0
 CONTROL "Cancel", IDCANCEL, "button", BS_PUSHBUTTON | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 180, 126, 50, 14, 0
}

#endresource

                                                                        // Dialog Data
    string              lb_allergies;
    string              lb_other_text;

#define MAX_ALLERGY_ITEMS                       20

                                                                        // Main Entry
int main() {
    int                 rc;

    lb_allergies = GetSetting("Dialog Examples", "Multiple Select Options", "Allergies");
    lb_other_text = GetSetting("Dialog Examples", "Multiple Select Options", "Allergies Other");

    rc = DialogBox("ListBoxExample01Dlg", "lb_");
    if (IsNotError(rc))  {
      PutSetting("Dialog Examples", "Multiple Select Options", "Allergies", lb_allergies);
      PutSetting("Dialog Examples", "Multiple Select Options", "Allergies Other", lb_other_text);
      MessageBox("Codes: %s\r\rOtherText: %s", lb_allergies, lb_other_text);
      }
    return rc;
    }

                                                                        // In Dialog Proc - Set State
void lb_set_state() {

    string              list[MAX_ALLERGY_ITEMS];

    list = ListBoxGetSelectArray(LB_SELECTIONS);
    if (FindInList(list, "Other") >= 0) {
      ControlEnable(LB_OTHER_TITLE);
      ControlEnable(LB_OTHER_TEXT);
      }
    else {
      ControlDisable(LB_OTHER_TITLE);
      ControlDisable(LB_OTHER_TEXT);
      }
    }

                                                                        // In Dialog Proc - Load
int lb_load() {

    string              list[MAX_ALLERGY_ITEMS];
    int                 lx, ix;

    ListBoxLoadList(LB_SELECTIONS, "None, Milk, Egg, Peanut, Tree Nut, Wheat, Soy, Fish, Shellfish, Sesame, Other");

    if (lb_allergies != "") {
      list = ExplodeString(lb_allergies, ", ");
      while (list[ix] != "") {
        lx = ListBoxFindItem(LB_SELECTIONS, list[ix]);
        if (lx >= 0) {
          ListBoxSelectItem(LB_SELECTIONS, lx);
          }
        ix++;
        }
      }

    EditSetText(LB_OTHER_TEXT, lb_other_text);

    lb_set_state();
    return ERROR_NONE;
    }

                                                                        // In Dialog Proc - Control Change
void lb_action(int c_id, int c_ac) {

    if ((c_id == LB_SELECTIONS) && (c_ac == LBN_SELCHANGE)) {
      lb_set_state();
      }
    }

                                                                        // In Dialog Proc - Pressed OK
int lb_validate() {

    string              list[MAX_ALLERGY_ITEMS];
    string              s1, s2;

    list = ListBoxGetSelectArray(LB_SELECTIONS);
    
    if (ArrayGetAxisDepth(list) != 0) {
      s1 = ImplodeArray(list, ", ");
      }

    if (ScanString(s1, "Other") >= 0) {
      s2 = EditGetText(LB_OTHER_TEXT);
      if (s2 == "") {
        MessageBox('X', "Please specify the nature of the other allergy.");
        return ERROR_SOFT | LB_OTHER_TEXT;
        }
      }

    if (s1 == "") {
      MessageBox('X', "Please make an allergy selection or 'None'.");
      return ERROR_SOFT | LB_SELECTIONS;
      }

    if ((ScanString(s1, "None") >= 0) && (s1 != "None")) {
      MessageBox('X', "Other allergies cannot be selected with 'None'.");
      return ERROR_SOFT | LB_SELECTIONS;
      }

    lb_allergies = s1;
    lb_other_text = s2;
    return ERROR_NONE;
    }


Skipping to the state management, we are primarily interested enabling and disabling the ‘other’ items. Each time there is a change, the list selections are retrieved using the ListBoxGetSelectArray function, and we can simply look in the array for “Other”. In the affirmative, we enable the “Other” text field. Otherwise we disable it.


Since control action is closely related to state management, you will notice that there is significantly less code in this section. It simply looks for the control ID and the action code LBN_SELCHANGE. I am not sure why the developers at Microsoft decided this, but the style LBS_NOTIFY must be added in order to receive select notifications. Forgetting to add this style can lead to a great deal of frustration in trying to understand why a click action on a list box is not processed.


There is a functional difference between the checkbox and list box versions. In the checkbox version, checking “None” cleared all the other selections. I did not add that functionality in the control action for the list box. It would be simple; can you figure out how? Instead, the conflict of selecting “None” and other items is tested in validation.


Look at load. It is also significantly less voluminous. We begin by loading the template items in the list box using the ListBoxLoadList function. Rather than coding each item, the allergy string is exploded into an array using the ExplodeString function with a delimiter of “, “. Note that space actually counts as part of the delimiter. If a comma only is used, then the resulting items would have leading spaces. After the explosion, we loop through the list until it’s empty, finding each item in the list box and selecting it.


For validation, retrieving the user’s selection is super easy, again using the ListBoxGetSelectArray function and then the ImplodeArray function with the appropriate glue. As is the case, the remain of the code verifies that the user input is correct. We test for specific conditions: “Other”, to be certain we have the associated description; Empty, because the user must select something; and “None”, because has nothing else should be selected (remember the option of adding this as a state change?).


List Box API


Covering only what we are using (there are over 20 list box API functions), we will start with the ListBoxLoadList function: 


int = ListBoxLoadList ( int id, string data );


This is a very flexible function allowing for the data parameter to be delimited with commas, spaces or line endings.


int = ListBoxFindItem ( int id, string target );


The search operation in the ListBoxFindItem function returns the zero based index of the item or -1 on error for the target target item. It finds the first string in the list box that exactly matches the specified string. The search is not case-sensitive.


int = ListBoxSelectItem ( int id, int index, [boolean state] );


List boxes have two select modes: single and multiple. This function is used in multiple mode to set the state of an individual item.


string[] = ListBoxGetSelectArray ( int id );


This last function is very useful in that everything selected in the list box can be captured with one function.


List Box Resources


Our resources are fairly simple:



    ListBoxExample01Dlg DIALOGEX 0, 0, 240, 150
    EXSTYLE WS_EX_DLGMODALFRAME
    STYLE DS_MODALFRAME | DS_3DLOOK | WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU
    CAPTION "List Box Example"
    FONT 8, "MS Shell Dlg"
    {
     CONTROL "Food &Allergies:", -1, "static", SS_LEFT | WS_CHILD | WS_VISIBLE, 6, 6, 60, 8, 0
     CONTROL "", -1, "static", SS_ETCHEDFRAME | WS_CHILD | WS_VISIBLE, 56, 11, 176, 1
     CONTROL "", LB_SELECTIONS, "listbox", LBS_NOTIFY | LBS_MULTIPLESEL | LBS_NOINTEGRALHEIGHT | WS_BORDER | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 14, 19, 40, 92
     CONTROL "Other E&xplain:", LB_OTHER_TITLE, "static", WS_CHILD | WS_VISIBLE, 65, 85, 60, 12, 0
     CONTROL "", LB_OTHER_TEXT, "edit", ES_AUTOHSCROLL | WS_CHILD | WS_VISIBLE | WS_BORDER | WS_TABSTOP, 65, 98, 160, 12
     CONTROL "", -1, "static", SS_ETCHEDFRAME | WS_CHILD | WS_VISIBLE, 6, 120, 226, 1
     CONTROL "OK", IDOK, "button", BS_DEFPUSHBUTTON | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 127, 126, 50, 14, 0
     CONTROL "Cancel", IDCANCEL, "button", BS_PUSHBUTTON | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 180, 126, 50, 14, 0
    }


We are using LBS_NOTIFY, LBS_MULTIPLESEL and LBS_NOINTEGRALHEIGHT. The last property is not discussed here. No integral height is used to control the button of the list box. By default, list box height will snap to position so as to not cut off display of the that last item in the display area. However, it makes it very difficult to align other controls to the bottom of the box.


A side note on the quick keys. The list box quick key is Alt+A attached to the preceding static title “Food &Allergies:”. It can also be put into the title text of the list box control but will not be visible to the user.


Conclusion


For our example application, I think the checkbox option is the best user interface. That being said, let us compare the two:


 MetricCheckboxList Box 
 User ExperienceBestOk — Better in some environments 
 Lines of Code244131 
 ComplexityStraight ForwardAbstract 
 ExtensibilityPoorExcellent 
 Ability to ‘tristate’ controlsYesNo 


I did not cover some of the more sophisticated checkbox options like tri-state and icons. That is for another day. In addition, rather than using a list box, the Data Control class could be used. It allows for coloring, styling, checkboxes in list as well. That too is for another blog.


In the next blog, I will cover radio buttons and the ever-confusing concept of control grouping.


 


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 17:30
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