The Application Framework and WinHelp

You've seen WinHelp running as a stand-alone program. The application framework and WinHelp cooperate to give you context-sensitive help. Here are some of the main elements:

  1. You select the Context-Sensitive Help option when you run AppWizard.

  2. AppWizard generates a Help Topics item on your application's Help menu, and it creates one or more generic RTF files together with an HPJ file and a batch file that runs the Help Compiler.

  3. AppWizard inserts a keyboard accelerator for the F1 key, and it maps the F1 key and the Help Topics menu item to member functions in the main frame window object.

  4. When your program runs, it calls WinHelp when the user presses F1 or chooses the Help Topics menu item, passing a context ID that determines which help topic is displayed.

You now need to understand how WinHelp is called from another application and how your application generates context IDs for WinHelp.

Calling WinHelp

The CWinApp member function WinHelp activates WinHelp from within your application. If you look up WinHelp in the online documentation, you'll see a long list of actions that the optional second parameter controls. Ignore the second parameter and pretend that WinHelp has only one unsigned long integer parameter, dwData. This parameter corresponds to a help topic. Suppose that the SIMPLE help file is available and that your program contains the statement

AfxGetApp()->WinHelp(HID_TOPIC1);

When the statement is executed in response to the F1 key or some other event the Help Topic 1 screen appears, as it would if the user had clicked on Topic 1 in the Help Table Of Contents screen.

"Wait a minute," you say. "How does WinHelp know which help file to use?" The name of the help file matches the application name. If the executable program name is Simple.exe, the help file is named Simple.hlp.

You can force WinHelp to use a different help file by setting the CWinApp data member m_pszHelpFilePath.

"And how does WinHelp match the program constant HID_TOPIC1 to the help file's context ID?" you ask. The help project file must contain a MAP section that maps context IDs to numbers. If your application's resource.h file defines HID_TOPIC1 as 101, the Simple.hpj MAP section looks like this:

[MAP]
HID_TOPIC1        101

The program's #define constant name doesn't have to match the help context ID; only the numbers must match. Making the names correspond, however, is good practice.

Using Search Strings

For a text-based application, you might need help based on a keyword rather than a numeric context ID. In that case, use the WinHelp HELP_KEY or HELP_PARTIALKEY option as follows:

CString string("find this string");
AfxGetApp()->WinHelp((DWORD) (LPCSTR) string, HELP_KEY);

The double cast for string is necessary because the first WinHelp parameter is multipurpose; its meaning depends on the value of the second parameter.

Calling WinHelp from the Application's Menu

AppWizard generates a Help Topics option on the Help menu, and it maps that option to CWnd::OnHelpFinder in the main frame window, which calls WinHelp this way:

AfxGetApp()->WinHelp(0L, HELP_FINDER);

With this call, WinHelp displays the Help Table Of Contents screen, and the user can navigate the help file through jumps and searches.

If you want the old-style table of contents, call WinHelp this way instead:

AfxGetApp()->WinHelp(0L, HELP_INDEX);

And if you want a "help on help" item, make this call:

AfxGetApp()->WinHelp(0L, HELP_HELPONHELP);

Help Context Aliases

The ALIAS section of the HPJ file allows you to equate one context ID with another. Suppose your HPJ file contained the following statements:

[ALIAS]
HID_TOPIC1 = HID_GETTING_STARTED

[MAP]
HID_TOPIC1        101

Your RTF files could use HID_TOPIC1 and HID_GETTING_STARTED interchangeably. Both would be mapped to the help context 101 as generated by your application.

Determining the Help Context

You now have enough information to add a simple context-sensitive help system to an MFC program. You define F1 (the standard MFC library Help key) as a keyboard accelerator, and then you write a command handler that maps the program's help context to a WinHelp parameter. You could invent your own method for mapping the program state to a context ID, but why not take advantage of the system that's already built into the application framework?

The application framework determines the help context based on the ID of the active program element. These identified program elements include menu commands, frame windows, dialog windows, message boxes, and control bars. For example, a menu item might be identified as ID_EDIT_CLEAR_ALL. The main frame window usually has the IDR_MAINFRAME identifier. You might expect these identifiers to map directly to help context IDs. IDR_MAINFRAME, for example, would map to a help context ID of the same name. But what if a frame ID and a command ID had the same numeric value? Obviously, you need a way to prevent these overlaps.

The application framework solves the overlap problem by defining a new set of help #define constants that are derived from program element IDs. These help constants are the sum of the element ID and a base value, as shown in the following table.

Program Element Element ID Prefix Help Context ID Prefix Base (Hexadecimal)
Menu Item or toolbar button ID_, IDM_ HID_, HIDM_ 10000
Frame or dialog IDR_, IDD_ HIDR_, HIDD 20000
Error message box IDP_ HIDP_ 30000
Nonclient area H… 40000
Control bar IDW_ HIDW_ 50000
Dispatch error messages     60000

HID_EDIT_CLEAR_ALL (0x1E121) corresponds to ID_EDIT_CLEAR_ALL (0xE121), and HIDR_MAINFRAME (0x20080) corresponds to IDR_MAINFRAME (0x80).

F1 Help

Two separate context-sensitive help access methods are built into an MFC application and are available if you have selected the AppWizard Context-Sensitive Help option. The first is standard F1 help. The user presses F1; the program makes its best guess about the help context and then calls WinHelp. In this mode, it is possible to determine the currently selected menu item or the currently selected window (frame, view, dialog, or message box).

Shift-F1 Help

The second context-sensitive help mode is more powerful than the F1 mode. With Shift-F1 help, the program can identify the following help contexts:

Shift-F1 help doesn't work with modal dialogs or message boxes.

The user activates Shift-F1 help by pressing Shift-F1 or by clicking the Context Help toolbar button, shown here.

In either case, the mouse cursor changes to

On the next mouse click, the help topic appears, with the position of the mouse cursor determining the context.

Message Box Help—The AfxMessageBox Function

The global function AfxMessageBox displays application framework error messages. This function is similar to the CWnd::MessageBox member function except that it has a help context ID as a parameter. The application framework maps this ID to a WinHelp context ID and then calls WinHelp when the user presses F1. If you can use the AfxMessageBox help context parameter, be sure to use prompt IDs that begin with IDP_. In your RTF file, use help context IDs that begin with HIDP_.

There are two versions of AfxMessageBox. In the first version, the prompt string is specified by a character-array pointer parameter. In the second version, the prompt ID parameter specifies a string resource. If you use the second version, your executable program will be more efficient. Both AfxMessageBox versions take a style parameter that makes the message box display an exclamation point, a question mark, or another graphics symbol.

Generic Help

When context-sensitive help is enabled, AppWizard assembles a series of default help topics that are associated with standard MFC library program elements. Following are some of the standard topics:

These topics are contained in the files AfxCore.rtf and AfxPrint.rtf, which are copied, along with the associated bitmap files, to the application's \hlp subdirectory. Your job is to customize the generic help files.

AppWizard generates AfxPrint.rtf only if you specify the Printing And Print Preview option.