Automatic operation of checkbox and radio buttons is easy, but what if you want to extend the functionality or do something out of the ordinary? This blog discusses how to manage the control more closely via button notifications. I will also update the checkbox script from Part One of this series to use notifications to add a narrative description to the dialog.
Friday, April 26. 2019
LDC #133: Checkbox and Radio Button Controls Part Three
Get the Message
Each type of control sends its own set of messages or notifications. Most controls will send the common messages such as set and kill focus or click. Messages are processed either from the action or notify procedure or both, depending on the specific control. Unfortunately, many control classes share similar notifications; however, the underlying id of the messages are not consistent, so it is important to make sure that the appropriate defined code is being used. In other words, don’t mistake BN_SETFOCUS for EN_SETFOCUS. They indicate the same condition regarding the control but have very different values (6 and 256, respectively). Many control classes have a “notify” style, such as BS_NOTIFY for button controls. Without this style bit being set, the control will only send a notification on click.
Button Messages
In our example, we are assuming that every notification is a click. If BS_NOTIFY is set, then a number of actions may 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. |
It is critical, once the BS_NOTIFY bit is set, that the action code be checked for the BN_CLICKED notification. Also, do not show message boxes, even for debugging, while processing focus messages. The program will get stuck in an unbreakable loop: the dialog gets focus, the message box kills that focus, and hitting ok on the message box sets focus again, and so forth.
Example 1 — Make a Checkbox Act Like a Radio Button
Our first example is a simple “make-work” program to illustrate direct management of checkboxes. The dialog appears as follows:
Three checkboxes are present with a little message log. The “First” button is set up as a tri-state checkbox while “Second” and “Third” are conventional checkboxes. For illustrative purposes, Second and Third are programmed to act like radio buttons.
Looking at the resources, we see a couple of style keywords that are different than our previous examples:
ManualCheckboxExample01Dlg DIALOGEX 0, 0, 190, 76 EXSTYLE WS_EX_DLGMODALFRAME STYLE DS_MODALFRAME | DS_3DLOOK | WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU CAPTION "Check Box Like Radio Button" FONT 8, "MS Shell Dlg" { CONTROL "'Radio' Check Box", -1, "static", SS_LEFT | WS_CHILD | WS_VISIBLE, 6, 6, 60, 8, 0 CONTROL "", -1, "static", SS_ETCHEDFRAME | WS_CHILD | WS_VISIBLE, 70, 11, 112, 1 CONTROL "&First", CB_FIRST, "button", BS_3STATE | BS_NOTIFY | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 14, 18, 60, 12 CONTROL "&Second", CB_SECOND, "button", BS_CHECKBOX | BS_NOTIFY | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 74, 18, 60, 12, 0 CONTROL "&Third", CB_THIRD, "button", BS_CHECKBOX | BS_NOTIFY | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 134, 18, 60, 12, 0 CONTROL "", CB_MESSAGE, "static", SS_LEFTNOWORDWRAP | WS_CHILD | WS_VISIBLE, 12, 36, 170, 8 CONTROL "", -1, "static", SS_ETCHEDFRAME | WS_CHILD | WS_VISIBLE, 6, 50, 176, 1 CONTROL "OK", IDOK, "button", BS_DEFPUSHBUTTON | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 77, 56, 50, 14, 0 CONTROL "Cancel", IDCANCEL, "button", BS_PUSHBUTTON | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 130, 56, 50, 14, 0 }
As shown, BS_3STATE is the style that will make the “First” button behave in a tri-state as unchecked, checked and undefined:
As can be seen, the undefined state also disables the Second and Third checkboxes. The automatic style of tri-state is the BS_AUTO3STATE style. The BS_NOTIFY style is added to show all notifications in the message area, an area that would not be present in a user interface. In the next example, the BS_NOTIFY style will become important to the behavior of the user interface.
Here is the code:
// Resource #beginresource #define CB_FIRST 201 #define CB_SECOND 202 #define CB_THIRD 203 #define CB_MESSAGE 204 ManualCheckboxExample01Dlg DIALOGEX 0, 0, 190, 76 EXSTYLE WS_EX_DLGMODALFRAME STYLE DS_MODALFRAME | DS_3DLOOK | WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU CAPTION "Check Box Like Radio Button" FONT 8, "MS Shell Dlg" { CONTROL "'Radio' Check Box", -1, "static", SS_LEFT | WS_CHILD | WS_VISIBLE, 6, 6, 60, 8, 0 CONTROL "", -1, "static", SS_ETCHEDFRAME | WS_CHILD | WS_VISIBLE, 70, 11, 112, 1 CONTROL "&First", CB_FIRST, "button", BS_3STATE | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 14, 18, 60, 12 CONTROL "&Second", CB_SECOND, "button", BS_CHECKBOX | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 74, 18, 60, 12, 0 CONTROL "&Third", CB_THIRD, "button", BS_CHECKBOX | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 134, 18, 60, 12, 0 CONTROL "", CB_MESSAGE, "static", SS_LEFTNOWORDWRAP | WS_CHILD | WS_VISIBLE, 12, 36, 170, 8 CONTROL "", -1, "static", SS_ETCHEDFRAME | WS_CHILD | WS_VISIBLE, 6, 50, 176, 1 CONTROL "OK", IDOK, "button", BS_DEFPUSHBUTTON | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 77, 56, 50, 14, 0 CONTROL "Cancel", IDCANCEL, "button", BS_PUSHBUTTON | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 130, 56, 50, 14, 0 } #endresource int main() { DialogBox("ManualCheckboxExample01Dlg", "cb_"); return ERROR_NONE; } void cb_action(int c_id, int c_ac) { string s1, s2; int cv; // Poorman's message snooper if ((c_id == CB_FIRST) || (c_id == CB_SECOND) || (c_id == CB_THIRD)) { s2 = EditGetText(CB_MESSAGE); switch (c_ac) { case BN_CLICKED: s1 = "BN_CLICKED"; break; case BN_HILITE: s1 = "BN_HILITE"; break; case BN_UNHILITE: s1 = "BN_UNHILITE"; break; case BN_DISABLE: s1 = "BN_DISABLE"; break; case BN_DOUBLECLICKED: s1 = "BN_DOUBLECLICKED"; break; case BN_PUSHED: s1 = "BN_PUSHED"; break; case BN_UNPUSHED: s1 = "BN_UNPUSHED"; break; case BN_DBLCLK: s1 = "BN_DBLCLK"; break; case BN_SETFOCUS: s1 = "BN_SETFOCUS"; break; case BN_KILLFOCUS: s1 = "BN_KILLFOCUS"; break; } s2 = s1 + " " +s2; s2 = GetStringSegment(s2, 0, 100); EditSetText(CB_MESSAGE, s2); } // First Checkbox Action if ((c_id == CB_FIRST) && (c_ac == BN_CLICKED)) { cv = CheckboxGetState(c_id); cv++; if (cv == 3) { cv = 0; } CheckboxSetState(c_id, cv); if (cv == 2) { CheckboxSetState(CB_SECOND, 0); CheckboxSetState(CB_THIRD, 0); ControlDisable(CB_SECOND); ControlDisable(CB_THIRD); } else { ControlEnable(CB_SECOND); ControlEnable(CB_THIRD); } } // Second Checkbox Action if ((c_id == CB_SECOND) && (c_ac == BN_CLICKED)) { CheckboxSetState(CB_SECOND, 1); CheckboxSetState(CB_THIRD, 0); } // Third Checkbox Action if ((c_id == CB_THIRD) && (c_ac == BN_CLICKED)) { CheckboxSetState(CB_THIRD, 1); CheckboxSetState(CB_SECOND, 0); } }
For this example, all the work is being done in the action procedure. The first part is something that would only be used for debugging or demonstrating the notifications coming from a control.
There are three processors: the “First” processor counts the clicks as the user moves through the checked, undefined, and back to the unchecked states. This processor also enables and disables the “Second” and “Third” checkboxes. The next two processors are the same for “Second” and “Third”, and they make them act like radio buttons.
When using BS_NOTIFY, it is important to filter the action as show below:
if ((c_id == CB_FIRST) && (c_ac == BN_CLICKED)) { cv = CheckboxGetState(c_id); cv++; if (cv == 3) { cv = 0; } CheckboxSetState(c_id, cv);
In this way, the ‘if’ statement will filter the control ID and the action as well. Below is the code to move through the three states. For two states, the cv variable, which contains the control state, could be XORed with 1:
cv ^= 1;
The “Second” and “Third” function by flipping the state of their counterpart:
if ((c_id == CB_SECOND) && (c_ac == BN_CLICKED)) { CheckboxSetState(CB_SECOND, 1); CheckboxSetState(CB_THIRD, 0); }
There are a few special precautions to take. First, mixing the auto click style with the manual click processing can cause confusion and odd behavior. Second, using the BS_NOTIFY and not filtering notifications such as focus change which are then mistaken for user clicks.
Improving our Part 1 Check Box Example
The second example uses the BS_NOTIFY style to improve the user interface by adding descriptive “hints” as a static control in the lower portion of the dialog. These hints change depending on which checkbox (and therefore which allergy) is selected. Some code has been added to the program described in Part One to create this modified interface:
When the user tabs or changes focus, a description is presented in a new area within the dialog.
Below is the modified script (added code is shown in blue):
// 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 #define CB_OTHER_DESCRIPTION 214 CheckboxExample01Dlg DIALOGEX 0, 0, 240, 160 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 | BS_NOTIFY | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 14, 18, 60, 12, 0 CONTROL "&Milk", CB_BOX_MILK, "button", BS_AUTOCHECKBOX | BS_NOTIFY | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 14, 30, 60, 12, 0 CONTROL "&Egg", CB_BOX_EGG, "button", BS_AUTOCHECKBOX | BS_NOTIFY | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 84, 30, 60, 12, 0 CONTROL "&Peanut", CB_BOX_PEANUT, "button", BS_AUTOCHECKBOX | BS_NOTIFY | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 154, 30, 60, 12, 0 CONTROL "&Tree Nut", CB_BOX_TREE_NUT, "button", BS_AUTOCHECKBOX | BS_NOTIFY | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 14, 42, 60, 12, 0 CONTROL "&Wheat", CB_BOX_WHEAT, "button", BS_AUTOCHECKBOX | BS_NOTIFY | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 84, 42, 60, 12, 0 CONTROL "&Soy", CB_BOX_SOY, "button", BS_AUTOCHECKBOX | BS_NOTIFY | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 154, 42, 60, 12, 0 CONTROL "&Fish", CB_BOX_FISH, "button", BS_AUTOCHECKBOX | BS_NOTIFY | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 14, 54, 60, 12, 0 CONTROL "S&hellfish", CB_BOX_SHELLFISH, "button", BS_AUTOCHECKBOX | BS_NOTIFY | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 84, 54, 60, 12, 0 CONTROL "Ses&ame", CB_BOX_SESAME, "button", BS_AUTOCHECKBOX | BS_NOTIFY | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 154, 54, 60, 12, 0 CONTROL "&Other", CB_BOX_OTHER, "button", BS_AUTOCHECKBOX | BS_NOTIFY | 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 "", CB_OTHER_DESCRIPTION, "static", WS_CHILD | WS_VISIBLE, 12, 102, 210, 30 CONTROL "", -1, "static", SS_ETCHEDFRAME | WS_CHILD | WS_VISIBLE, 6, 132, 226, 1 CONTROL "OK", IDOK, "button", BS_DEFPUSHBUTTON | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 127, 138, 50, 14, 0 CONTROL "Cancel", IDCANCEL, "button", BS_PUSHBUTTON | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 180, 138, 50, 14, 0 } #endresource // Dialog Data string cb_allergies; string cb_other_text; string hint[]; // Main Entry int main() { int rc; hint[rc++] = "Check if you have no known allergies."; hint[rc++] = "Cow's milk is the usual cause of milk allergy, but milk from sheep, goats, buffalo and other mammals also can cause a reaction."; hint[rc++] = "Eggs or foods that contain eggs."; hint[rc++] = "Peanuts are not the same as tree nuts. Peanuts grow underground and are part of a different plant family, the legumes. Other examples of legumes include beans, peas, lentils and soybeans."; hint[rc++] = "Tree nuts include almond, brazil, cashew, hazelnut, pistachio, and walnuts. Tree net allergies are not the same as peanuts, which are legumes, or seeds, such as sunflower or sesame."; hint[rc++] = "Wheat or foods that contain wheat."; hint[rc++] = "Soybeans are a member of the legume family. Beans, peas, lentils and peanuts are also legumes. Please list peanut separately."; hint[rc++] = "Tuna, salmon and halibut are the most common types in this group. Fish and shellfish are not related."; hint[rc++] = "Either group of shellfish: crustacea (such as shrimp, crab and lobster) and mollusks (such as clams, mussels, oysters and scallops). Crustacea cause most shellfish reactions."; hint[rc++] = "Sesame seeds or foods that contain sesame seeds."; hint[rc++] = "Check if an allergy is not listed and please describe in the associated text box."; 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) && (c_id <= CB_BOX_OTHER)) { if (c_ac == BN_SETFOCUS) { EditSetText(CB_OTHER_DESCRIPTION, hint[c_id - CB_BOX_NONE]); } if (c_ac == BN_KILLFOCUS) { EditSetText(CB_OTHER_DESCRIPTION, ""); } } if ((c_id == CB_BOX_NONE) && (c_ac == BN_CLICKED)) { 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) && (c_ac == BN_CLICKED)) { 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)) { if (c_ac == BN_CLICKED) { 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; }
Each of the checkboxes has the BS_NOTIFY style added in order to capture the set and kill focus messages. Any window or control that can accept keyword input receives keyboard focus from the operating system and will send set and kill focus messages.
An array of hints is created (thanks to the internet for these descriptions!), which is referenced during the control focus change in the action procedure:
if ((c_id >= CB_BOX_NONE) && (c_id <= CB_BOX_OTHER)) { if (c_ac == BN_SETFOCUS) { EditSetText(CB_OTHER_DESCRIPTION, hint[c_id - CB_BOX_NONE]); } if (c_ac == BN_KILLFOCUS) { EditSetText(CB_OTHER_DESCRIPTION, ""); } }
As shown, the control id is used as the index to the hints list, which is set as the description. The kill focus message is processed to clear the description. Without this code, when the other message box or OK and Cancel buttons receive focus, the description from the last checkbox will still be on the dialog. Also, one might note that the added code is at the start of the action proceeding the other operations. This makes sure that the focus is processed for the controls in which we have interest.
Watching Messages
As shown in the first example, we had a little monitor dumping messages to a window to keep track of what messages are being sent and received. This can be very useful for debugging. I have used more a more sophisticated log that I append to the side of dialogs:
This drives message data to a side log to show the message interaction. Developing a script to achieve something like this is a good exercise in both using dialog controls and receiving and outputting notifications.
Conclusion
Many controls send messages which can be used to improve the user experience, such as those we have explored here in reference to checkboxes. Understanding the dynamic of messaging is critical to smooth implementation. By interpreting the messages from checkbox controls, more sophisticated dialog behavior can be developed. Also, the debug process can be aided by dumping data to logs or creating a rolling window to watch what it happening in real-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
Legato Script Developers LinkedIn Group
Primer: An Introduction to Legato