Chapter 16 Context Sensitive Help
Help is a very important feature for all types of applications. Since the user interface cannot be made intuitive enough to eliminate guessing when the user interacts with a program, we need to include context sensitive help to tell the user what exactly each command means. Although the help development is usually not a part of job for programmers, the persons who are in charge of application development should cooperate closely with the help developers to create high-quality applications.16.1 Context Sensitive Help for Menu Commands
Context Sensitive Help
Context sensitive help is supported by the up-to-date versions of MFC. It is more user friendly than the old style on-line help, and provides an easier way to let the user find out pertinent help topics. Generally, context sensitive help can be enabled when an SDI or MDI application is being generated by the Application Wizard (Figure 16-1).
After we've enabled the context sensitive help, a "question mark" button will appear on the mainframe tool bar (Figure 16-2). If we click on it, the mouse cursor will change to a question mark. If we use this cursor to click any menu command (or any part of the application window), the help window will pop up displaying the description of the item that was just clicked.
Context Sensitive Help for Menu Commands
By default, only the standard items such as caption bar, status bar and standard commands (File | Open, File | Save, File | print) will support context sensitive help. Menu commands (or mainframe tool bar commands) added by the programmer will not support context sensitive help automatically.
By default, contents of help are stored in a rich text format file generated by the Application Wizard: "AfxCore.rtf". We can find many footnotes within this file, each footnote corresponds to one help page. To implement the context sensitive help for a custom menu command, we need to add a new footnote to file "AfxCore.rtf", and link it to the command.
If the context sensitive help is enabled within the Application Wizard in step 4 (see Figure 1-1), the help project will be generated automatically. All the files used to build the help will be contained in a "hlp" directory under the project directory. For example, if we use the Application Wizard to generate an SDI application named "Help", the help project will be generated under "Help\hlp\" directory. There will be a batch file "Makehelp.bat" under the "Help" directory. Also, there are some other important files under "Help\hlp" directory that are used to build the help. The following table lists the usages of these files:
(Table omitted)
The help is compiled by a utility named "Microsoft Help Workshop". The executable file "Hcw.exe" can be found under Visual C directory "~DevStudio\Vc\Bin\". This utility can compile "*.hpj" file to generate a target help file.
By double clicking on the "*.hpj" file or "*.cnt" file (In "Explorer", they are described as "Help project file" and "Help contents file" respectively), we can compile the help project in the help workshop environment. Also, when we compile the application in Developer Studio, the help project will also be compiled. The help project file (".hpj) is similar to a make file when we execute C compilers, it contains information about how to generate the target help file. The help contents file (".cnt") contains the information of "help topics". If we execute Help | Help Topics command from the application, the help contents will be displayed in a "Help Topics" property sheet (Figure 16-3). All the descriptions about the commands and the application are included in "AfxCore.rtf" and "AfxPrint.rtf" files, we must edit them in order to add custom help descriptions.
Sample
Because the default help project already has many help items, without the knowledge of the help project, it is difficult for a programmer to add items for the newly added commands. Sample 16.1\Help is a standard SDI application generated by Application Wizard, which demonstrates how to add new help items and link them to the application commands to support context sensitive help.
New Commands
First, after the standard project is generated, four new commands are added to the application. These commands are implemented in both the mainframe menu and the tool bar, and their IDs are ID_HELPTEST_TESTA, ID_HELPTEST_TESTB, ID_HELPTEST_TESTC and ID_HELPTEST_TESTD respectively. In IDR_MAINFRAME menu, the new commands are Help Test | Test A, Help Test | Test B, Help Test | Test C, and Help Test | Test D. In the IDR_MAINFRAME tool bar, we also have four buttons corresponding to the four command IDs. The message handlers of these commands are all blank, because we just want to demonstrate how to implement context sensitive help for them.
Editing "AfxCore.rtf"
We must add four items to the help file in order to link a command to the help. In order to do this, we must modify file "AfxCore.rtf". The rich text format file can be edited by a word processor like Microsoft( Word (WordPad is not powerful enough for this purpose, because it does not support footnote editing). Within this file, each help item is managed as a footnote. If we want to add a new help item, we just need to add a new footnote.
To show how to add a footnote to the ".rtf" file, lets assume that we want to add a footnote for ID_HELPTEST_TESTA command.
Each footnote must be associated with a tag, so that it can be referenced from inside or outside the file. In Microsoft( Word, we can either add number-based tags or user-defined tags. To make the tags meaningful, usually we define tags by ourselves. The tags can be any string, usually they will have some relationship with the command IDs implemented in the application. For example, we can use "help_test_A" as the tag for the footnote that will be used to implement help for the command whose ID is ID_HELPTEST_TESTA.
To add a new tag within Microsoft( Word, first we need to move the cursor to the bottom of the file, then execute Insert | Break... command. From the popped up dialog box, we need to check the radio button labeled "Page Break". If we click "OK" button, a new page will be added to the file (Generally each footnote needs to use one page, so we need to add page break whenever a new footnote is added). Now execute Insert | Footnote command, and check the radio button labeled "Footnote" from the popped up dialog box (in "Insert" section). Then, check radio button labeled "Custom Mark" (in "Numbering" section) and input a '#' into the edit box beside it (Figure 16-4). Finally, click "OK" button. Now the client window of Word will split into two panes, the lower of which will show all footnote tags. The caret will be placed right after the '#' sign waiting for us to type in the footnote tag. We can type "help_test_A", then click on the upper pane. Then we can input any help description for command ID_HELPTEST_TESTA.
Tag "help_test_A" can be referenced either from other footnote pages or from the application. If we want to let the user jump from one help item to another (for example, when viewing help on "telp_test_A", the user may want to jump to footnote "help_test_B" by clicking on a link within the same page), we need to implement links by editing the ".rtf" file.
Suppose we have added four footnotes for the newly implemented commands: "help_test_A", "help_test_B", "help_test_C", and "help_test_D", and we want to add links to the rest of three commands within each footnote page. For example, help item "help_test_A" may be implemented as illustrated in Figure 16-5:
Under "See Also" statement, there are three links that will direct mouse clicking to footnotes "help_test_B", "help_test_C" and "help_test_D".
To create this type of links, we need to use special font format. We need to use double underline style to format the text that will be linked to a footnote (By doing this, the text formatted with double underline can respond to mouse clicking and bringing up another help item). Following the underlined text, we need to place the footnote tag using "Hidden" font style. To let the hidden text be displayed in the Word editor, we can execute command Tools | Options... and click tab View on the popped up property sheet. Then within "Nonprinting Characters" section, check "Hidden Text" check box.
Now we can make a link very easily. First we need to type in and format the text as illustrated in Figure 16-6:
To format text using double underline, we can select the text, then execute command Format | Font.... From the popped up property sheet, we can go to "Font" page and select "Double" form "Underline" combo box. By doing this, the selected text will be double underlined. To format text using "Hidden Text" style, we can first select the text, then execute command Format | Font..., go to "Font" page, make sure that "None" is selected from the "Underline" combo box, and check "Hidden" check box within "Effects" section.
ID Mapping
In order to support context sensitive help, we must link the footnotes to their corresponding command IDs. In our case, we need to link "help_test_A" to ID_HELPTEST_TESTA, "help_test_B" to ID_HELPTEST_TESTB... and so on.
This ID mappings are implemented in ".hm" file, so by opening file "Help.hm" with a text editor, we will see all the IDs of the help items supported in the sample. Generally help IDs are generated according to certain rules: it bases the help ID of a control (or a window) on its resource ID. By default, for a command whose ID starts with "ID_", MFC generates symbolic help ID by prefixing a character 'H' to the resource ID. For example, for command ID_HELPTEST_TESTA, the help ID generated by MFC is HID_HELPTEST_TESTA. For the actual ID value, MFC generates it by adding a fixed number to the corresponding resource ID value. For example, if the value of ID_HELPTEST_TESTA is 0x8004, the value of HID_HELPTEST_TESTA would be 0x18004 ¾ here a number of 0x1000 is added to the command resource ID.
By doing this, when the user executes a command in the help mode (When the mouse cursor changes to a question mark after the user clicks command ID_CONTEXT_HELP), the application will first find out the resource ID of the command being executed, add a fixed value, then pass the result to the help. By using this rule to generate ID for a help item, it is easier for us to implement context-sensitive help.
Although the ID mapping is customizable, which means we can prefix different character(s) to a resource ID for generating help ID (For example, the help ID of command ID_HELPTEST_TESTA could be HLID_HELPTEST_TESTA), and we can add any value to the resource ID to generate a help ID, it is more convenient if we stick to the rules of MFC. For example, if we add 0x20000 instead of 0x10000 to the resource ID to make a help ID, we also need to add some code to the program to customize its default behavior.
Another problem still remains: since we've already added footnotes and defined our own tags, the command IDs are still not directly linked to the footnote tags. For example, the help ID of command ID_HELPTEST_TESTA is HID_HELPTEST_TESTA, while the footnote tag implemented in the help file is "help_test_A". This can be solved by defining alias names in the help project file. By opening "*.HPJ" file, we will see an [ALIAS] session. Under this session, a help ID can be linked directly to a footnote tag. In our case, the footnote tags are "help_test_A", "help_test_B"..., and the help IDs automatically generated by MFC are HID_HELPTEST_TESTA, HID_HELPTEST_TESTB.... To link them together, we can add the following to alias session:
[ALIAS]
......
HID_HELPTEST_TESTA=help_test_A
HID_HELPTEST_TESTB=help_test_B
HID_HELPTEST_TESTC=help_test_C
HID_HELPTEST_TESTD=help_test_D
Obviousely, if we use the default help ID strings to implement footnote tags (i.e., use HID_HELPTEST_TESTA instead of help_test_A as the footnote tag), the ID mapping could be eleminated.
By doing this, when the user executes certain command in the help mode, the corresponding help page will be automatically brought up.
Help Topics Dialog Box
The footnotes can also be referenced from "Help Topics" dialog box. This dialog box is activated when the user executes command ID_HELP_FINDER. The contents contained in "Help Topics" dialog box are stored in ".cnt" file. To edit this file, we can open it using Help Workshop. In the Help Workshop, we can add two types of items: Heading and Tab Entry. A heading will not be linked to any footnote. Under each heading, we can add several tab entries, which need to be linked to footnotes. A new item can be added by clicking buttons labeled "Add Above...", "Add Below...". After that, a dialog box will pop up asking us to input the description text as well as the help ID (the footnote tag). The description of an item should be input into the edit box labeled "Title", and the footnote tag should be input into the edit box labeled "Topic ID" (Figure 16-7).
After making any change to the help project, we need to recompile the project in order to get the up-to-date help.
To make the help working, both ".hlp" and ".cnt" file must be copied to the directory that contains the application executable file.
16.2 Context Sensitive Help for Common Controls
Sample 16.2-1\Help and 16.2-2\Help are based on sample 16.1\Help, they demonstrate how to support context sensitive help in a dialog box.
Supporting Context Sensitive Help in Dialog Box
The previous section describes how to add context sensitive help for commands. We can also add this fancy feature for common controls contained in a dialog box. To implement context sensitive help for a dialog box, we can check "Context Help" check box under "More Styles" page of the "Dialog Properties" property sheet (Figure 16-8). After this type of dialog boxes are invoked, a question mark button will appear on their caption bars (Figure 16-9). This button has the same functionality with command ID_CONTEXT_HELP described in the previous section. By clicking on this button, we will enter help mode, with the mouse cursor becoming a question mark. As we click any controls contained in the dialog box with this cursor, the help will be activated and the corresponding help window will be brought up.
ID Naming Rules
Adding context sensitive help for common controls is more complicated than that of menu commands. First we must add footnotes for each common control contained in the dialog box, then we need to do the ID mappings.
By default, MFC will generate help IDs for the commands or controls whose resource IDs start from "ID_", "IDM_", "IDP_", "IDR_", "IDD_" and "IDW_" by prefixing a single character 'H' to them. Also, the values of these help IDs are generated by adding a fixed number to the corresponding resource IDs. This fixed number is different for different types of IDs. For example, for "ID_XXX" and "IDM_XXX" types of IDs, 0x10000 will be added to the resource ID; for "IDP_XXX" type IDs, 0x30000 will be added; for "IDR_XXX" and "IDD_XXX" types of IDs, 0x20000 will be added; for "IDW_XXX" type IDs, 0x50000 will be added.
By default, the IDs of the common controls in a dialog box all start with "IDC_" prefix, which is not included in the default mapping. This means we must generate help IDs by ourselves. Actually, this can be easily achieved. If we open file "Makehelp.bat" (in our case, this file should be located in "16.2\Help\" directory), we will see that the help IDs are generated through "Makehm" utility, which can be executed under DOS prompt.
"Makehm" has the following syntax:
Makehm argument 1, argument 2, argument 3, argument 4 >> "File name"
where
argument 1: prefix of resource ID
argument 2: prefix of help ID
argument 3: base number
argument 4: resource header file name
For example, if we want to generate symbolic help IDs for those IDs prefixed with "IDC_", and add 0x10000 to the resource IDs to generate actual help IDs, we can execute this command as follows (Here, the application resource header file name is "resource.h", and the ".hm" file name is "Help.hm", it is located under the directory of "Hlp"):
Makehm IDC_, HIDC_, 0x10000, resource.h >> "hlp\Help.hm"
If we include the above statement in file "Makehelp.bat", after executing it, we will see that all the common controls will have corresponding help IDs in file "Help.hm".
Enabling Context Sensitive Help for Common Controls
In sample 16.2-1\Help, a dialog box IDD_DIALOG is added to the project. The dialog box supports context sensitive help implementation. There are four controls included in the dialog box: edit box IDC_EDIT, radio button IDC_RADIO, combo box IDC_COMBO and a push button IDC_BUTTON. After adding the "Makehm" command to "Makehelp.bat" file and executing it, we will see the following help IDs in file "Help.hm":
// Common Controls (IDC_*)
HIDC_EDIT 0x103E8
HIDC_BUTTON 0x103E9
HIDC_RADIO 0x103EA
HIDC_COMBO 0x103EB
Please note that file "Makehm.exe" is located in directory "...DevStudio\Vc\Bin\". We must set path to this directory in order to execute it from DOS prompt. However, if we compile the help through Developer Studio or Help Workshop, there is no need to set the path. An alternate solution that can let us run the batch file from DOS prompt without setting path is to copy file "Makehm.exe" to the directory that contains file "Makehelp.bat".
In the sample application 16.2-1\Help, four new footnotes with tags "common_button", "common_combobox", "common_edit" and "common_radio" are added to file "AfxCore.rtf". Their alias names are specified in the help project file:
[ALIAS]
......
HIDC_BUTTON=common_button
HIDC_COMBO=common_combobox
HIDC_EDIT=common_edit
HIDC_RADIO=common_radio
This still does not finish context sensitive help implementation for the common controls. When the user uses question mark cursor to click a common control contained in the dialog box, by default, it is the ID of the dialog box, not the ID of the control that will be used to activate the help. To enable context help for each individual control, we need to call function CWnd::SetWindowContextHelpId(...) for it, which has the following format:
BOOL CWnd::SetWindowContextHelpId(DWORD dwContextHelpId);
Parameter dwContextHelpId is the help ID of the control. This function can be used to link a control's resource ID to its help ID.
To make things work, we must use the IDs generated by "Makehm" utility for each control. In the sample, a function CHelpDlg::SetContextHelpId() (CHelpDlg is the class used to implement the dialog box) is implemented to set help IDs for all the common controls contained in the dialog box:
(Code omitted)
First we call function CWnd::GetWindow(...) and use flag GW_CHILD to find out the first child window contained in the dialog box. Then we call this function repeatedly using GW_HWNDNEXT flag to find out all the other child windows. The loop will be stopped after the last child window is enumerated. For each child window (common control), we obtain its resource ID value by calling function CWnd::GetDlgCtrlID(), adding 0x10000 to it, and using this value to set help ID.
We must make sure that both utility "Makehm" and function CWnd::SetWindowContextHelpID(...) add the same value to the resource IDs to result in help IDs.
Function CWnd::OnHelpInfo(...)
We also need to override function CWnd::OnHelpInfo(...) in order to let the help jump to a special page when being activated. The following code fragment shows how this function is overridden in the sample:
(Code omitted)
When the user uses question mark cursor to click a control, by default, function CWnd::OnHelpInfo(...) will be called, this will not activate help for the common control being clicked. To customize this, we must check if the help ID corresponds to one of the common controls contained in the dialog box. If so, we need to activate the help by ourselves (and jump to the corresponding help page).
The information of the control (or window) will be passed through a HELPINFO type object. Especially, the help ID will be stored in dwContextId member of this structure. If we have called function CWnd:: SetWindowContextHelpId(...) for a control, the value of this ID will be the one we set there. So in our case, the resource ID can be obtained by subtracting 0x10000 from the help ID.
In the above function, we first check if the control is one of the four controls that support context sensitive help. If so, function CWinApp::WinHelp(...) is called to activate the help (The help ID is passed to the first parameter of this function). Otherwise, default implementation of this function will be called.
Function CWinApp::WinHelp(...) has two parameters:
virtual void CWinApp::WinHelp(DWORD dwData, UINT nCmd=HELP_CONTEXT);
The help will be activated in different styles according to the values of parameter nCmd. If we do not specify this parameter, the help will be implemented in default style, and jump to the footnote corresponding to the help ID specified by parameter dwData. If the help ID could not be found, the first footnote contained in the help will be displayed and an error message will pop up.
Displaying Help in a Pop up Window
In sample 16.2-2\Help, context help for the controls are displayed in different styles:
(Code omitted)
The edit box and radio button still use the default style, whose help window contains standard menu and buttons. The help windows of push button and combo box controls are implemented by pop up window, which is a smaller window with a yellowish background, and does not contain other controls (Figure 16-10). By passing different parameters to function CWinApp::WinHelp(...), we can activate "Help Topic" dialog box. Also, we can adjust its position and size of the help window before it is displayed,.
Summary
1) In order to implement context sensitive help for a command, we need to add a footnote to the "AfxCore.rtf" file, then link the command ID to the tag of the footnote.
2) Usually the help ID is generated by prefixing character(s) to the resource ID of the command, and the value of the help ID is generated by adding a fixed number to the value of the resource ID.
3) The resource ID can be linked to a footnote tag by specifying an alias name in ".hpj" file.
4) A help page can be referenced either from other help pages or from the application.
5) To support context sensitive help in the dialog box, we need to call function CWnd:: SetWindowContextHelpId(...) for every common control that will support context sensitive help, and override function CWnd::OnHelpInfo(...) to activate customized help window.
6) By default, function CWnd::OnHelpInfo(...) does not support context sensitive help for common controls. In this case, we can call CWinApp::WinHelp(...) to customize the default help implementation.
7) When calling function CWinApp::WinHelp(...), we can use various parameters to activate help windows in different styles. For example, using parameter HELP_CONTEXTPOPUP will invoke a pop up help window.