TN028: Context-Sensitive Help Support
This note describes the rules for assigning Help contexts IDs (that is, topic numbers) and other help issues in MFC. Context sensitive help support requires the help compiler that is available in Visual C++.
In addition to implementing context-sensitive help using WinHelp, MFC also supports using HTML Help. For more information on this support and programming with HTML Help, see HTML Help: Context-Sensitive Help for Your Programs.
There are two types of context-sensitive help implemented in Windows applications. The first, referred to as "F1 Help" involves launching WinHelp with the appropriate context based on the currently active object. The second is SHIFT+F1 mode. In this mode, the mouse cursor changes to the help cursor (a combination arrow + question mark), and the user proceeds to click the object on which they want help. At that point, WinHelp is launched to give help for the object on which the user clicked.
The Microsoft Foundation Classes implement both of these forms of help. In addition, the framework supports two simple help commands, Help Index and Using Help.
0x00000000 - 0x0000FFFF : user defined 0x00010000 - 0x0001FFFF : commands (menus/command buttons) 0x00010000 + ID_ (note: 0x18000-> 0x1FFFF is practical range since command IDs are >=0x8000) 0x00020000 - 0x0002FFFF : windows and dialogs 0x00020000 + IDR_ (note: 0x20000-> 0x27FFF is practical range since IDRs are <= 0x7FFF) 0x00030000 - 0x0003FFFF : error messages (based on error string ID) 0x00030000 + IDP_ 0x00040000 - 0x0004FFFF : special purpose (non-client areas) 0x00040000 + HitTest area 0x00050000 - 0x0005FFFF : controls (those that are not commands) 0x00040000 + IDW_
These rules are hard-coded into the default implementation of the Microsoft Foundation classes. They can be overridden by providing different implementations of the various Help-related member functions.
There are two simple Help commands that are implemented by the Microsoft Foundation Classes:
ID_HELP_INDEX which is implemented by CWinApp::OnHelpIndex
ID_HELP_USING which is implemented by CWinApp::OnHelpUsing
These two commands simply show the Help index for the application and show the user help on using the WinHelp program, respectively.
This is the first form of context-sensitive Help (usually referred to as F1 Help). The user presses F1 to get help on the task (the active window or menu item). No special "help mode" is involved.
The F1 key is usually translated to a command with an ID of ID_HELP by an accelerator placed into the main window's accelerator table. The ID_HELP command may also be generated by a button with an ID of ID_HELP on the main window or dialog box. Also, when a menu or a dialog box is active and the user presses F1, the keystroke is hard-coded to translate into an ID_HELP command.
However the ID_HELP command is generated, it is routed as a normal command until it reaches a command handler (for more information on the Microsoft Foundation classes command-routing architecture, refer to Technical Note 21.) If the application is Help enabled, the ID_HELP command will be handled by the CWinApp::OnHelp function. Since the default command routing is not adequate for determining the most specific context the command is instead always routed to the application object and then undergoes custom routing for Help.
CWinApp::OnHelp attempts to launch WinHelp in the following order:
Checks for an active AfxMessageBox call with a Help ID. If a message box is currently active, WinHelp is launched with the context appropriate to that message box.
If no message box is active, CWinApp::OnHelp sends a WM_COMMANDHELP (a message private to the Microsoft Foundation classes) to the active window. If that window does not respond by launching WinHelp, the same message is then sent to the parent of that window, until the message is processed or the current window is a top-level window (and therefore does not have a parent window).
If the message remains unprocessed, then the default Help is invoked. This is done by sending a ID_DEFAULT_HELP command to the main window. This command is generally mapped to CWinApp::OnHelpIndex.
To globally override the ID base values (0x10000 for commands, 0x20000 for resources such as dialogs, and so on), the application should override CWinApp::WinHelp.
To override this functionality and the way that a Help context is determined, an application should handle the WM_COMMANDHELP message (see below). You may wish to provide more specific Help routing than the framework provides, as it only goes as deep as the current MDI child window. You may also want to provide more specific help for a particular window or dialog, perhaps based on the current internal state of that object or the active control within the dialog.
afx_msg LRESULT CWnd::OnCommandHelp(WPARAM, LPARAM lParam)
WM_COMMANDHELP is an MFC private Windows message that is received by the active window when Help is requested. When the window receives this message, it may call CWinApp::WinHelp with context that matches the window's internal state.
contains the currently available Help context. lParam is zero if no Help context has been determined yet. An implementation of OnCommandHelp can use the context ID in lParam to determine a "better" context or can just pass it to CWinApp::WinHelp.
is not used and will be zero.
If the OnCommandHelp function calls CWinApp::WinHelp, it should return TRUE. Returning TRUE stops the routing of this command to other classes (base classes) and to other windows.
This is the second form of context-sensitive Help. Generally, this mode is entered by pressing SHIFT+F1 or via the menu/toolbar. It is implemented as a command (ID_CONTEXT_HELP). The message filter hook is not used to translate this command while a modal dialog box or menu is active, therefore this command is only available to the user when the application is executing the main message pump (CWinApp::Run).
After entering this mode, the Help mouse cursor is displayed over all areas of the application, even if the application would normally display its own cursor for that area (such as the sizing border around the window). The user is able to use the mouse or keyboard to select a command. Instead of executing the command, Help on that command is displayed. Also, the user can click a visible object on the screen, such as a button on the toolbar, and Help will be displayed for that object. This mode of Help is provided by CWinApp::OnContextHelp.
During the execution of this loop, all keyboard input is inactive, except for keys that access the menu. Also, command translation is still performed via PreTranslateMessage to allow the user to press an accelerator key and receive help on that command.
If there are particular translations or actions taking place in the PreTranslateMessage function that shouldn't take place during SHIFT+F1 Help mode, you should check the m_bHelpMode member of CWinApp before performing those operations. The CDialog implementation of PreTranslateMessage checks this before calling IsDialogMessage, for example. This disables "dialog navigation" keys on modeless dialogs during SHIFT+F1 mode. In addition, CWinApp::OnIdle is still called during this loop.
If the user chooses a command from the menu, it is handled as help on that command (through WM_COMMANDHELP, see below). If the user clicks a visible area of the applications window, a determination is made as to whether it is a nonclient click or a client click. OnContextHelp handles mapping of nonclient clicks to client clicks automatically. If it is a client click, it then sends a WM_HELPHITTEST to the window that was clicked. If that window returns a nonzero value, that value is used as the context for help. If it returns zero, OnContextHelp tries the parent window (and failing that, its parent, and so on). If a Help context cannot be determined, the default is to send a ID_DEFAULT_HELP command to the main window, which is then (usually) mapped to CWinApp::OnHelpIndex.
afx_msg LRESULT CWnd::OnHelpHitTest(WPARAM, LPARAM lParam)
WM_HELPHITTEST is an MFC private windows message that is received by the active window clicked during SHIFT+F1 Help mode. When the Window receives this message, it returns a DWORD Help ID for use by WinHelp.
contains the X-axis device coordinate where the mouse was clicked relative to the client area of the window.
contains the Y-axis coordinate.
is not used and will be zero. If the return value is nonzero, WinHelp is called with that context. If the return value is zero, the parent window is queried for help.
In many cases, you can leverage hit-testing code you may already have. See the implementation of CToolBar::OnHelpHitTest for an example of handling the WM_HELPHITTEST message (the code leverages the hit-test code used on buttons and tooltips in CControlBar).
The MFC Application Wizard creates the necessary files to build a Help file (.cnt and .hpj files). It also includes a number of prebuilt .rtf files that are accepted by the Microsoft Help Compiler. Many of the topics are complete, but some may need to be modified for your specific application.
Automatic creation of a "help mapping" file is supported by a utility called MAKEHM. The MAKEHM utility can translate an application's RESOURCE.H file to a Help mapping file. For example:
#define IDD_MY_DIALOG 2000 #define ID_MY_COMMAND 150
will be translated into:
HIDD_MY_DIALOG 0x207d0 HID_MY_COMMAND 0x10096
This format is compatible with the Help compiler's facility, which maps context IDs (the numbers on the right side) with topic names (the symbols on the left side).
The source code for MAKEHM is available in the MFC Programming Utilities sample MAKEHM.
The best way to add Help to your application is to check the "Context-sensitive Help" option on the Advanced Features page of the MFC Application Wizard before creating your application. That way the MFC Application Wizard automatically adds the necessary message map entries to your CWinApp-derived class to support Help.
Help on Message Boxes (sometimes called alerts) is supported through the AfxMessageBox function, a wrapper for the MessageBox Windows API.
There are two versions of AfxMessageBox, one for use with a string ID and another for use with a pointer to string (LPCSTR):
int AFXAPI AfxMessageBox(LPCSTR lpszText, UINT nType, UINT nIDHelp); int AFXAPI AfxMessageBox(UINT nIDPrompt, UINT nType, UINT nIDHelp);
In both cases, there is an optional Help ID.
In the first case, the default for nIDHelp is 0, which indicates no Help for this message box. If the user presses F1 while such as message box is active, the user will not receive Help (even if the application supports Help). If this is not desirable, a Help ID should be provided for nIDHelp.
In the second case, the default value for nIDHelp is -1, which indicates the Help ID is the same as nIDPrompt. Help will work only if the application is Help-enabled, of course). You should provide 0 for nIDHelp if you wish that the message box have no help support. Should you want the message to be Help enabled, but desire a different help ID than nIDPrompt, simply provide a positive value for nIDHelp different from that of nIDPrompt.