Chapter 29 | [ Contents ]

### Help Manager

Beginning with OS/2 1.2, IBM introduced an addition to the Presentation Manager interface (touted as the "Help Manager") that allowed an application to add both general help and field help online. (With 1.3, IBM published the previously undocumented method for creating online books, which are viewed using the system-supplied utility VIEW.EXE). It should be noted, however, that while this capability is very appealing, it is by no means added to an application quickly; in fact, well-written online help can take on the average of 1 day/3000 lines of code to complete for the text alone. (This figure is based on personal experience.) The upside of this is that, for most Presentation Manager applications, programmers do not have to think about his designing the programs; online help can be added at any time, providing that the source code to the application is available.

#### Application Components

There are at least three parts to the help component of any application: the source code, the HELPTABLEs, and the definitions of the help panels. The source code is obviously part of the application source, and includes the corresponding Win* calls and HM_ messages sent to and received from the Help Manager. The HELPTABLEs (and HELPSUBTABLEs) are part of the resource file, and they define the relationships between the various windows and the corresponding help panels. Finally, the help panel definitions describe the look as well as the text of the help panels and are written using a general markup language (GML)-like language (SCRIPT and Bookmaster users will recognize the help panel definition language as a subset of the Bookmaster macros they are familiar with). Let us take a closer look at each of these three parts in more detail.

#### The Application Source

The source code is usually the smallest component of the three, only because it typically consists of an initialization section and the processing of a few messages. The initialization section normally goes in the main routine after the main window is created and follows the next which is the typical initialization code used in a Presentation Manager application to create a help instance.
`#define HELP_CLIENT 256
HELPINIT hiInit;
CHAR achHelpTitle[256];
HAB habAnchor;
HWND hwndHelp;
HWND hwndFrame;
: // WinInitialize, etc. goes here
// We need to initialize the HELPINIT structure before calling
// WinCreateHelpInstance. See the online technical reference
// for an explanation of the individual fields.
hiInit.cb = sizeof(HELPINIT);
hiInit.ulReturnCode = 0L;
hiInit.pszTutorialName = NULL;
// By specifying 0xFFFF in the high word of phtHelpTable, we are
// indicating that the help table is in the resource tables with
// the id specified in the low word.
hiInit.phtHelpTable = (PHELPTABLE)MAKEULONG(HELP_CLIENT,0xFFFF);
hiInit.hmodHelpTableModule = NULLHANDLE;
hiInit.hmodAccelActionBarModule = NULLHANDLE;
hiInit.idAccelTable = 0;
hiInit.idActionBar = 0;
hiInit.pszHelpWindowTitle = achHelpTitle;
hiInit.fShowPanelId = CMIC_HIDE_PANEL_ID;
hiInit.pszHelpLibraryName = "MYAPPL.HLP";
hwndHelp = WinCreateHelpInstance(habAnchor,&hiInit);
if ((hwndHelp != NULLHANDLE) && (hiInit.ulReturnCode != 0)) {
WinDestroyHelpInstance(hwndHelp);
hwndHelp = NULLHANDLE;
} /* endif */
:
: // Message loop goes here
:
if (hwndHelp != (HWND)NULL) {
WinDestroyHelpInstance(hwndHelp);
hwndHelp = NULLHANDLE;
} /* endif */
` As with the relationship between window classes and window instances, there exists a help manager class of which you create an instance by calling WinCreateHelpInstance. This function can have one of three outcomes:

1. The call can complete successfully, and the return value is the handle of the help instance.
2. The function can partially complete, returning a help instance handle and specifying an error code in the ulReturnCode field.
3. The function can fail returning NULL. Because of the subtle difference between (1) and (2), it is not sufficient to simply check the return value.

If the help instance is successfully created, it becomes the recipient of any messages that you send and the originator of any messages that are sent to the active window.
Since a help instance is associated with a "root" window and all of its descendants, you need to indicate what the root window is. This is done using the WinAssociateHelpInstance function.
`(BOOL)WinAssociateHelpInstance(HWND hwndHelp, // Help instance
HWND hwndWindow); // "Root" window
`
Specifing a non-NULL value for hwndHelp indicates that this is the active window that should be used when determining which help panel to display. Specifying NULL for this parameter removes the current association between the help instance and the window specified. We will see how this is used shortly.

| Gotcha! Note that the call to WinAssociateHelpInstance will not work if you call it within the WM_CREATE message of the window with which it is associated. WinAssociateHelpInstance needs a valid window handle, and when the WM_CREATE message is received, the window handle is not yet valid.

#### Messages

The next piece of source code that you will use in most of your applications deals with the "Help" pull-down menu and "Help" push-buttons (obviously, if your application does not contain an action bar or any dialogs, you need not read this). According, to IBM's guidelines on developing a application user interface, there should exist on the action bar a pull-down titled "Help" that contains the following four items:

- "Using help..."
- "General help..."
- "Keys help..."
- "Help index..."

There can also be an optional fifth item - labeled "Product information..." - that displays an "About" box when selected. Fortunately, the Help Manager has four messages that can be sent to it to process these four menu items. Each of them take no parameters and are listed in Table 29.1:

Table 29.1 Help Manager Display Messages
Message | Description
HM_DISPLAY_HELP | Displays help on using online help.
HM_EXT_HELP | Displays the "extended" help for the current window.
HM_KEYS_HELP | Displays the keys help for the current window.
HM_HELP_INDEX | Displays the help index.

Except for HM_KEYS_HELP, all that needs to be done is send the appropriate message to the help instance. Sending HM_KEYS_HELP results in the help instance sending the window a HM_QUERY_KEYS_HELP message back to determine which "keys help" panel to display. The panel resource ID should be returned by the programmer in response to this message.
The behavior of a "Help" push-button is left somewhat up to the programmer. The official IBM response is that it should display field help - a panel that describes what the purpose is of the control containing the cursor. We follow this strategy in our applications; it results in the displaying of the extended help for the frame or dialog. To display this help for the frame, the programer should define the push button with the BS_NOPOINTERFOCUS style to avoid receiving the input focus, and should send the help instance a HM_DISPLAY_HELP message (this time with either the panel resource ID or the panel name in mpParm1 and either HM_RESOURCEID or HM_PANELNAME in mpParm2) to display the help panel for the current control with the focus. To display this help for the dialog, the programmer simply needs to send an HM_EXT_HELP message to the help instance.

#### The Help Tables

The help tables define the relationship between the control windows and the help panels to be displayed when the user requests help. Visualizing the help tables as a two-dimensional array of help panel Ids may make undestanding what they are easier. The first index into this array is the ID of the window that has been associated with a help instance via WinAssociateHelpInstance, and the second index is either a menu item ID or an ID of a child window that can receive the input focus. To understand how the help tables are used, we need to understand the sequence of events beginning with the user pressing F1 and the displaying of the help panel.

1. The user presses F1.
2. The help instance determines the ID of the window that it is currently associated with.
3. The HELPITEM for the given window ID is referenced, and the appropriate HELPSUBTABLE is determined.
4. The menu item ID (or the ID of the window with the focus) is used to look up in the HELPSUBTABLE the ID of the help panel to display.
5. The help panel definition is retrieved from the compiled help file.
6. The help panel is displayed.

There are obviously many places where errors can occur; the most frequent one is when the menu item ID/child window ID is not in the HELPSUBTABLE. When this occurs, the owner window-chain is searched (steps 3 - 6). If it is still not found, the parent window chain is also searched. If the ID has not been found after both searches, the current window is sent a HM_HELPSUBITEM_NOT_FOUND message, giving it the opportunity to remedy the situation (via a HM_DISPLAY_HELP message). The default action is to display the extended help for the current window.
When the ID is found in a HELPSUBTABLE but the panel definition does not exist, or when any other error occurs (with the exception of HELPSUBITEM not found described above and when the extended help panel cannot be determined), the application is sent an HM_ERROR message. This message contains an error code in the first parameter that describes the condition causing the error. The typical response to receiving this is to display a message and then disable the help manager by calling WinDestroyHelpInstance.
Given this logical view of the help tables, let us look at a sample definition in a resource file.

#### Sample HELPTABLE

The tables below describe the online help panels that correspond to the child windows and menuitems in the application and its associated dialogs.

     HELPTABLE HELP_CLIENT
     {
             HELPITEM HELP_CLIENT, SUBHELP_CLIENT, EXTHELP_CLIENT
             HELPITEM DLG_OPEN, SUBHELP_OPEN, EXTHELP_OPEN
             HELPITEM DLG_PRODUCTINFO,
                     SUBHELP_PRODUCTINFO, EXTHELP_PRODUCTINFO
     }

     HELPSUBTABLE SUBHELP_CLIENT
     {
             HELPSUBITEM M_FILE,  HELP_M_FILE
             HELPSUBITEM MI_NEW,  HELP_MI_NEW
             HELPSUBITEM MI_OPEN, HELP_MI_OPEN
             HELPSUBITEM MI_SAVE, HELP_MI_SAVE
             HELPSUBITEM MI_CLOSE,HELP_MI_CLOSE
             HELPSUBITEM MI_EXIT, HELP_MI_EXIT
             HELPSUBITEM M_HELP,  HELP_M_HELP
             HELPSUBITEM MI_USINGHELP,   HELP_MI_USINGHELP
             HELPSUBITEM MI_GENERALHELP, HELP_MI_GENERALHELP
             HELPSUBITEM MI_KEYSHELP,    HELP_MI_KEYSHELP
             HELPSUBITEM MI_HELPINDEX,   HELP_MI_HELPINDEX
             HELPSUBITEM MI_PRODINFO,    HELP_MI_PRODINFO
     }
     HELPSUBTABLE SUBHELP_SETOPEN
     {
             HELPSUBITEM DOPEN_EF_FILENAME, HELP_DOPEN_EF_FILENAME
             HELPSUBITEM DLG_PB_OK,         HELP_DLG_PB_OK
             HELPSUBITEM DLG_PB_CANCEL,     HELP_DLG_PB_CANCEL
             HELPSUBITEM DLG_PB_HELP,       HELP_DLG_PB_HELP
     }

     HELPSUBTABLE SUBHELP_PRODINFO
     {
             HELPSUBITEM DLG_PB_CANCEL,  HELP_DLG_PB_CANCEL
             HELPSUBITEM DLG_PB_HELP,    HELP_DLG_PB_HELP
     }

As is clear from the sample, our application has two dialogs with online help. Their resource identifiers are DLG_OPEN and DLG_PRODUCTINFO, and that there are 12 child windows or menu items that belong to the client window. In each of the HELPSUBITEMS, the window ID is on the left and the corresponding help panel resource ID is on the right.

| Gotcha! If the resource ID specified in the WinCreateStdWindow call is different from that used as the resource ID of the HELPTABLE, the first parameter to the HELPITEM that refers to the main window should be the same as the HELPTABLE resource ID and not the ID for the frame resources.

#### Message Boxes

When your application needs to give the user some information, one of the way it can do so is by using the WinMessageBox function. This displays a window that contains application-specified title and text, as well as an optional icon to the left and one or more predefined push-buttons (e.g., "OK", "Yes", "Abort", etc.).

             (USHORT)WinMessageBox(HWND   hwndParent, // parent window
                                   HWND   hwndOwner,  // owning window
                                   PSZ    pszMessage, // pointer to the text
                                   PSZ    pszTitle,   // pointer to the title
                                   USHORT usHelpId,   // help topic id
                                   ULONG  ulStyle);   // message box style

hwndParent defines the bounding area of the message box; typically, this is HWND_DESKTOP. hwndOwner specifies the window that "owns" the message box; this window is disabled while the message box is displayed and is reactivated when the call returns. pszMessage and pszTitle point to the message box text and title, respectively. usHelpId is used when MB_HELP is specified in ulStyle (see below), and ulStyle is a combination of MB_* constants. This function returns a constant that specifies the push-button selected on the message box (e.g., MBID_OK, MBID_NO, MBID_RETRY, etc.)
As might be imagine, only so much can be said in a small dialog box. Often, what fits is enough for most users to figure out what the programmer is trying to say. However, it would be nice to provide another level of detail for those who would like more information (i.e., online help). The constant MB_HELP specifies that a "Help" push-button is requested; this is the only button that does not cause the function to return. Unfortunately, since a message box doesn't have to have an application window as the owner (HWND_DESKTOP will work fine for hwndOwner; this could be used in, for example, a program that simply calls WinMessageBox with the command line for the message for CMD files), it cannot simply send the owner a message saying that the help button was pressed. The system, therefore, provides two ways to display help for message boxes: using a help hook and using HELPTABLEs. We will look at the latter method later in the chapter.

#### Fishing, Anyone ?

A "hook" is a function that PM calls whenever a certain event occurs. In a preverted way, we could look at it as subclassing the entire system, but instead of intercepting messages before the intended recipient receives them, the application intercepts "events". These events range from the "code page changed" event to the "DLL has been loaded with WinLoadLibrary" event and cover 16 different items. There is, of course, a "help requested" event as well, and it is this event that we are interested in.
Hooks are installed with WinSetHook and are released with WinReleaseHook Both take the same parameters:
below:

            (BOOL)WinSetHook(HAB habAnchor,     // HAB of the calling thread
    		         HMQ hmqQueue,      // HMQ of the calling thread,
    				            // HMQ_CURRENT for current thread or NULL for
    			                    // system-wide hook
    			 USHORT usHookType, // HK_* constant
    		         PFN pfnHookProc,   // pointer to the hook procedure
    		         HMODULE hmodProc); // HMODULE containing pfnHookProc

habAncor is the handle to the anchor block of the calling thread. hmqQueue is the handle of the queue for which events are to be monitored. If this is HULLHANDLE, events for the entire system are monitored; however, the hook function - since it will be called by different processes - must reside in a DLL so that PM can load the function when needed. usHookType is one of the HK_ constants specifying the event to be monitored. pfnHookProc is a pointer to the event monitoring function (the "hook"). hmodProc is a handle to the DLL containing the hook function or NULLHANDLE if hmqQueue is not NULLHANDLE and the hook function resides in the exutable.
Each of the procedures for the different hook types take different parameters and return different values. Since we're interested in the HK_HELP hook, here is the prototype of the hook function:

            (BOOL)pfnHookProc(HAB habAnchor,    // HAB of the calling thread
    		          SHORT sMode,      // HLPM_* constant
    		          USHORT usTopic,   // Topic number
    			  USHORT usSubTopic,// Subtopic number
    			  PRECTL prclPosition);

habAncor is the handle to the anchor block of the thread for which the event occured. sMode indicates the context in which help was requested and is a HLPM_ constant. usTopic and usSubTopic are dependent on the value of sMode.

Table 29.2 Hook Variables
sMode Is | usTopic Is | usSubTopic Is
HLPM_FRAME | Identifier of the active frame window | Identifier of the window with the focus
HLPM_MENU | Identifier of the pull-down menu or FID_MENU if the action bar selected | Identifier of the menu item or submenu item for which help was requested.
HLPM_WINDOW | Identifier of the message box | Not used

The help hook returns TRUE if the next hook in the help hook chain should not be called and FALSE if the next hook should be called. The typical function of the help hook when used in this context is to send the help instance a HM_DISPLAY_HELP message to display the specified help panel.

| Gotcha! Note that the documentation states that the help hook should be installed before creating the help instance. However, since WinSetHook installed the hook at the head of the hook chain, this information is backwards. For this procedure to work properly, the call to WinSetHook should be placed after the call to WinAssociateHelpInstance.

Given the information in the Gotcha, the following question comes up: Since WinAssociateHelpInstance is called only aftre frame window has been created successfully, how does an application provide message box help for the WM_CREATE message ? The answer is to call WinSetHook after creating the help instance, calling WinCreateStdWindow to create the frame window, and then releasing the hook, associating the help instance, and resetting the hook with WinReleaseHook, WinAssociateHelpInstance, and WinSetHook, respectively.

| Gotcha! The header files in the Toolkit indicate that the parameters for the help hook are a SHORT and two USHORTs for 16-bit applications and a LONG and two ULONGs for 32-bit application. This is incorrect. The parameters are always a SHORT and two USHORTs.

#### The Help Panels

Now that we've seen how easy the code and resource definitions are, it is time to tackle the most difficult (to do well) and time-consuming aspect of this development phase - writing the help panels. While the definition of the language is large, it is fairly easy to digest. We will look at only the rudiments of the language; the full language definition can be gleamed from the online document entitled "IPF Reference" that is included with the OS/2 Programmer's Toolkit.
The help file (whose file extension is usually ".IPF") is compiled by the "Information Presentation Facility Compiler" (a.k.a. IPFC) to produce a ".HLP" file that is read by the Help Manager when WinCreateHelpInstance is called. The source file contains a collection of "tags," which begin with a colon (:), followed by the tag name, an optional set of attributes, and finally a period (.). Some tags also require a matching "end tag" (e.g., a "begin list" and "end list" tag), which have no attributes and whose name usually matches the beginning tag name preceded by an e (e.g., ":sl." and ":esl."). Table 29.3 presents common tags and their meanings.

Table 29.3 Common IPF Tags
Tag | Meaning
:h1. through :h6. | Heading tag. Headings 1 - 3 also have an entry in the table of contents.
:p. | New paragraph.
:fn. :efn. | Footnote and ending tag.
:hp1. through :hp9. | Emphasis tag. This requires the matching ending tag (:ehp1. through :ehp9.).
:link. | Hypertext link.
:sl. :esl. | Simple list and ending tag.
:ul. :eul. | Unordered list and ending tag.
:ol. :eol. | Ordered list and ending tag.
:li. | List item. Used between the list tags to describe the items in the list.
:dl. :edl. | Definition list and ending tag. Whereas the other lists consist of a single element, definition lists consist of a "data term" and "data definition" (:dt. and :dd., respectively).
:dt. :dd. | Data term and data definition tags.
:dthd. :ddhd. | Data term heading and data definition heading tags. Also, there are a few special tags that are used only once in a help file.
:userdoc. :euserdoc. | Beginning and ending of the document.
:title. | The text to be placed in the title bar of the help panels.

While most of these tags have attributes, the ones you'll use most are the resource and ID attributes. The resource attribute allows you to assign a numerical value to a heading tag (e.g., ":h1 res=2048.Help panel"), and this is what the HELPSUBITEMs reference. The ID attribute allows you to assign a alphanumeric name for use in hypertext links (e.g., ":h2 id='MYPANEL'.Help panel"). The ID attribute can be used on both heading and footnote tags, while the resource attribute can only be used on heading tags. Heading IDs are referenced using the "refid" attribute of a hypertext link, while a footnote is referenced also using the "refid" attribute of a ":fnref" (footnote reference) tag.
In addition to the tags, certain symbols that are either translated into different values in other languages, not easily enterable using the keyboard, or are also used by IPF are defined. These are referenced by symbol name substitution, beginning with an ampersand (&), including the symbol name, and ending with a period (.).

Chapter 29 | [ Contents ]