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 2.0. Context sensitive help support requires the help compiler which is available in Visual C++ Professional.
Types of Help Supported
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 on the object they'd like help on. At that point, WinHelp is launched giving 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.
Help Files
The Microsoft Foundation classes assume a single Help file. That Help file must have the same name and path as the application (.EXE -> .HLP).
This is a public CWinApp member variable named m_pszHelpFilePath that the user can change if desired.
Help Context Ranges
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.
Simple “Help” Commands
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.
Context-Sensitive Help (F1 Help)
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 at hand (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. This is in fact done in the implementation of the ClassWizard and AppWizard applications themselves, for example, since both share a single Help file.
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. Or you may wish 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.
WM_COMMANDHELP
afx_msgLRESULTCWnd::OnCommandHelp(WPARAM, LPARAMlParam)
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.
lParam
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.
wParam
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.
Help Mode (Shift+F1 Help)
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 it's 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.
WM_HELPHITTEST
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.
LOWORD(lParam)
contains the X-axis device coordinate where the mouse was clicked relative to the client area of the window.
HIWORD(lParam)
contains the Y-axis coordinate.
wParam
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).
MFC AppWizard Support and MAKEHM
AppWizard 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 Utilties sample .
Adding Help Support After Running AppWizard
The best way to add Help to your application is to check the “Context Sensitive Help” option in AppWizard's Options dialog before creating your application. That way AppWizard automatically adds the necessary message map entries to your CWinApp-derived class to support Help.
If you have already created your application without Help support and now wish to add it, see Visual C++ Programmer's Guide.
Help on Message Boxes
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.