|Important||This document may not represent best practices for current development, links to downloads and other resources may no longer be valid. Current recommended version can be found here.|
TN028: Context-Sensitive Help Support
This note describes the rules for assigning Help contexts IDs 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, and the user proceeds to click on an object. At that point, WinHelp is launched to give help for the object that the user clicked on.
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.
The Microsoft Foundation classes assume a single Help file. That Help file must have the same name and path as the application. For example, if the executable is C:\MyApplication\MyHelp.exe the help file must be C:\MyApplication\MyHelp.hlp. You set the path through the m_pszHelpFilePath member variable of the CWinApp Class.
The default implementation of MFC requires a program to follow some rules about the assignment of Help context IDs. These rules are a range of IDs allocated to specific controls. You can override these rules by providing different implementations of the various Help-related member functions.
0x00000000 - 0x0000FFFF : user defined 0x00010000 - 0x0001FFFF : commands (menus/command buttons) 0x00010000 + ID_ (note: 0x18000-> 0x1FFFF is the practical range since command IDs are >=0x8000) 0x00020000 - 0x0002FFFF : windows and dialogs 0x00020000 + IDR_ (note: 0x20000-> 0x27FFF is the 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_
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
The first command shows the Help index for the application. The second shows the user help on using the WinHelp program.
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.
Regardless of how the ID_HELP command is generated, it is routed as a normal command until it reaches a command handler. For more information about the MFC command-routing architecture, refer to Technical Note 21. If the application has Help enabled, the ID_HELP command will be handled by CWinApp::OnHelp. The application object receives the help message and then routes the command appropriately. This is necessary since the default command routing is not adequate for determining the most specific context.
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.
Sends a WM_COMMANDHELP message to the active window. If that window does not respond by launching WinHelp, the same message is then sent to the ancestors of that window until the message is processed or the current window is a top-level window.
Sends a ID_DEFAULT_HELP command to the main window. This invokes the default Help. This command is generally mapped to CWinApp::OnHelpIndex.
To globally override the default ID base values (e.g. 0x10000 for commands and 0x20000 for resources such as dialogs), the application should override CWinApp::WinHelp.
To override this functionality and the way that a Help context is determined, you should handle the WM_COMMANDHELP message. 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 wParam, LPARAM lParam)
WM_COMMANDHELP is a private Windows MFC 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.
If the OnCommandHelp function calls CWinApp::WinHelp, it should return TRUE. Returning TRUE stops the routing of this command to other 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.
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.