[ Next ] [ Previous ] Chapter 29
[ Contents ] [ Chapter 28: Presentation Manager Printing ]
 [ Chapter 30: Multithreading in Presentation Manager Applications ]

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

Specifying 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.
Ahtung! 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:
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 programmer 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 understanding 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.
Ahtung! 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 perverted 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:
        (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 NULLHANDLE, 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 executable.

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 occurred. 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_MENUIdentifier 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 after 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.
Table 29.4 Commonly Used Symbols
 Symbol
 Meaning
&amp.
Ampersand
&cdq.
Close double quote
&colon.
Colon
&csq.
Close single quote
&lbrk
Left bracket
&odq.
Open double quote
&osq.
Open single quote
&rbrk.
Right bracket
&vbar.
Vertical bar
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 (.).  Table 29.4 lists some  commonly used symbols.

 A help panel begins with a heading 1, 2, or 3 tag and ends with either the next heading 1, 2, or 3 tag or the end of the document.  Everything that is in between is shown when the panel is displayed.  A sample panel showing how some of the tags are used is shown below.


Sample Help Panel

 :h1 id='MYPANEL' res=1000.My help panel
:p.When adding online help to your PM application, the following
steps must be taken
:fnref refid='MYFN'.:
:ul compact.
:li.Initialization code must be written
:li.Messages must be processed
:li.Resources must be defined
:li.Help panels must be compiled
:eul.
:fn id='MYFN'.
Footnotes start on an implied paragraph.
:efn.
Note that at least five tags are required in a valid IPF file  -  :userdoc., :title.,  :h1., :p., and :euserdoc.,  in that order.

Putting It All Together

We now have enough information to write a simple application to illustrate the points made in this chapter.  The  application, HELP1, contains a few menu items and dialog boxes but otherwise does nothing.  Its sole purpose is to  allow the user to display the online help.

HELP1.C
HELP1.RC
HELP1.H
HELP1.MAK
HELP1.DEF
HELP1.IPF

Restrictions

You might have noticed by now that while the HELPSUBITEMs use manifest constants (those that have been  #define'd), the help panels must hard-code their resource ids.  This is a nasty problem that can only be solved by a  preprocessor that can accept C include files; there are a few good public domain ones that you can get for only the  cost of connecting to the Internet.  Though the right choice is to use preprocessor that built in your C/C++ compiler

Using HELP TABLEs for Message Box Help

As we stated earlier, there are two methods for providing help for message boxes. The second method uses HELPTABLEs defined in your .RC file to specify the appropriate help panels to display. This method has advantages and disadvantages:
Advantages:
Disadvantages:
The method is simple to implement and can be broken into the following steps, which must be done for every message box that your application displays:
  1. Add a HELPITEM in the HELPTABLE with the first parameter having the value of the identifier specified as the fifth parameter to WinMessageBox. The second parameter of the HELPITEM specifies a HELPSUBTABLE for the message box and the third specifies the resource ID of the "General help" panel for the message box.
  2. Add a HELPSUBTABLE whose identifier is the same as that of the second parameter of HELPITEM created in step 1.
  3. Add one HELPSUBITEM in the HELPSUBTABLE created in step 2 for each button displayed in the message box (specified using the MB_ constants). The first parameter of the HELPSUBITEM is the MBID_ constant corresponding to the message box button, and the second parameter is - as you would guess - the resource ID of the help panel corresponding to the message box button.
That's all you need to do. Let me reiterate, however, that this must be done for every message box your application displays.

Gotcha!

If a HELPSUBTABLE corresponding to a message box is empty, nothing will happen when the user presses F1 on that message box. No error message can be sent because, as we stated earlier, the message box does not necessarily know to whom the message should be sent.

HELP2.C
HELP2.RC
HELP2.H
HELP2.MAK
HELP2.DEF
HELP2.IPF

[ Next ] [ Previous ] Chapter 29
[ Contents ] [ Chapter 28: Presentation Manager Printing ]
 [ Chapter 30: Multithreading in Presentation Manager Applications ]