Using the OS/2 Help Manager

From EDM2
Jump to: navigation, search

Written by:

Larry Salomon Jr. (aka 'Q')
OS/2 Applications and Tools
IBM T.J. Watson Research Center
Yorktown Heights, NY

This seminar may be distributed in any form, providing that no modifications are made to the text, unless done so by the author.

This, and other seminars, can be received via anonymous ftp from the following sites:

Site                    IP Address      Directory
----                    ----------      ---------
cs.unc.edu              129.109.136.138 /pub/os2.seminars
network.ucsd.edu        128.54.16.3     /pub/os2.seminars
Abstract
the OS/2 Help Manager provides a very useful function to applications - online help. This seminar will attempt to demonstrate how to enable your applications to use the Help Manager.
Disclaimer
I do not claim to know everything about this subject. I simply know enough about this subject to warrant the writing of this seminar with the intent of making it easier on those who want to know more.
Audience
this seminar is directed at C programmers with good PM programming experience. Function prototypes will be taken from the OS/2 1.3 toolkit.
Contact
any questions, comments, etc. can be sent directly to me at one of the addresses listed above (in the order of preference and highest rate of successful delivery).

So You Want to Use the Help Manager?

One of the many very useful features provided by OS/2 is the Help Manager. Using it, an application can provide field and context sensitive help online for the user to get out of those "sticky situations" that they always seem to find themselves in.

However, enabling your application to use the Help Manager is not a five-minute task, by any means. Ironically, the only documentation (that I have ever found) on doing this is in the Programming Guide, which comes with the Technical Reference documentation.

In your application, there are three sections that need to be modified or created before "you are in business":

  • Code
  • Resource definitions
  • Help panel definitions

Before I discuss these further, it should be noted that help is usually associated with frame windows. Dialogs that are displayed as a result of some user action on the frame normally use the help instance associated with the frame that owns the dialog.

Code

The coding aspect of providing online help can be further split into two parts: initialization/termination and message handling. Initialization can be done at any time, and consists of a HELPINIT structure and the WinCreateHelpInstance/WinAssociateHelpInstance APIs.

       typedef struct {
          USHORT cb;
          ULONG ulReturnCode;
          PSZ pszTutorialName;
          PHELPTABLE phtHelpTable;
          HMODULE hmodHelpTableModule;
          HMODULE hmodAccelActionBarModule;
          USHORT idAccelTable;
          USHORT idActionBar;
          PSZ pszHelpWindowTitle;
          USHORT usShowPanelId;
          PSZ pszHelpLibraryName;
       } HELPINIT;

cb
the size of the HELPINIT structure
ulReturnCode
initialized by WinCreateHelpInstance to indicate any errors that have occured but did not prevent creation of the instantiation.
pszTutorialName
points to a string containing the default tutorial name. This should be NULL if your application does not have a tutorial.
phtHelpTable
points to the help table. If (and we will do it this way) the help table is defined in a resource file, the high word should be 0xFFFF and the low word should contain the resource id.
hmodHelpTableModule
the module handle of the DLL to be used when retrieving resources or NULL if the .EXE file contains the resources.
hmodAccelActionBarModule
the module handle of the DLL to be when loading the accelerator table and action bar to be used by the Help Manager or NULL if the .EXE file contains the resources.
idAccelTable
the resource id of the custom accelerator table or 0 if none exists.
idActionBar
the resource id of the custom action bar or 0 if none exists.
pszHelpWindowTitle 
points to a string to be used as the title for the online help window.
usShowPanelId
describes whether or not panel ids are to be shown. This can be CMIC_HIDE_PANEL_ID, CMIC_SHOW_PANEL_ID, or CMIC_TOGGLE_PANEL_ID, which do not show the panel id, show the panel id, or toggle the showing of the panel id, respectively.
pszHelpLibraryName
points to a string containing the name of the file containing the panel definitions.

After initializing the HELPINIT structure, you call the WinCreateHelpInstance API.

HWND APIENTRY WinCreateHelpInstance(HAB hab,
                                    PHELPINIT phinitHMInitStructure);

This returns the window handle of the Help Manager instance, to which you can send messages. Even though this might not be NULL, indicating success, you should still check the ulReturnCode field of the HELPINIT structure for any warnings that are returned.

After the frame window is created, you call the WinAssociateHelpInstance API. The need for this is described later.

BOOL APIENTRY WinAssociateHelpInstance(HWND hwndHelpInstance,
                                       HWND hwndApp);

Finally, after the frame is destroyed, you call the WinDestroyHelpInstance API.

BOOL APIENTRY WinDestroyHelpInstance(HWND hwndHelpInstance);

Message handling is simple, since it is done in the standard way. Although there are many messages defined, the few that you will typically use are listed below.

The following messages are sent to the Help Manager and take no parameters, unless otherwise specified:

HM_DISPLAY_HELP
tells the Help Manager to display a help panel.
PVOIDFROMMP(mp1) should point to a panel name or contain a panel id.
SHORT1FROMMP(mp2) describes what mp1 contains:
HM_RESOURCEID - SHORT1FROMMP(mp1) is the panel id
HM_PANELNAME - PVOIDFROMMP(mp1) points to the panel name
HM_EXT_HELP
tells the Help Manager to display the extended help panel for the window
HM_HELP_CONTENTS
tells the Help Manager to display the table of contents
HM_HELP_INDEX
tells the Help Manager to display the index
HM_KEYS_HELP
tells the Help Manager to display the keys help panel for the window

The following messages are sent by the Help Manager:

HM_ERROR
indicates an error occurred when viewing help. LONGFROMMP(mp1) indicates the HMERR_* constant describing the error.
HM_HELPSUBITEM_NOT_FOUND
indicates that a panel was not found when help was requested. SHORT1FROMMP(mp1) indicates a HLPM_* constant describing where help was requested from (a frame, menu, or a window); SHORT1FROMMP(mp2) indicates the id of the

active frame or dialog; SHORT2FROMMP(mp2) indicates the id of the window with the focus, or the menuitem selected when F1 was pressed. If FALSE is returned, the Help Manager displays the extended help panel, else no action is taken.

HM_QUERY_KEYS_HELP
asks the application for the id of the keys help panel. The application should return the id.

Additionally, WM_HELP is sent to the window whenever F1 is pressed. This should normally be passed on to the default procedure for processing. A caveat to this is when a pushbutton is used on a dialog. When selected by the user, the panel corresponding to the pushbutton will be displayed, which is usually an undesirable result. The workaround is to intercept the WM_HELP message and display the appropriate panel when the button is pushed. The WM_HELP parameters are exactly like those of WM_COMMAND:

       case WM_HELP:
          if (SHORT1FROMMP(mp1)==ID_HELP) {
             WinSendMsg(hwndHelpMgr,HM_EXT_HELP,0L,0L);
          } else {
             return WinDefDlgProc(hwnd,usMsg,mp1,mp2);
          } /* endif */
          break;

Resource Definitions

The resources that need to be defined are one or more trees of tables describing the relationships between help panels and windows in an application. There are two types of tables: HELPTABLEs and HELPSUBTABLEs; the former contains HELPITEMs while the latter contains HELPSUBITEMs.

HELPITEMs are comprised of three elements, in the following order: the main window id, the associated HELPSUBTABLE id, and the extended help panel id.

HELPSUBITEMs are comprised of two elements, in the following order: the child window id and the help panel id. A child window can be either a control or a menuitem.

An example of this structure is shown below:

HELPTABLE WND_IMAGE
{
  HELPITEM WND_IMAGE, SUBHLP_IMAGE, EXTHLP_IMAGE
  HELPITEM DLG_INFO, SUBHLP_INFO, EXTHLP_INFO
}

HELPSUBTABLE SUBHLP_IMAGE
{
  HELPSUBITEM M_FILE, HLP_M_FILE
  HELPSUBITEM M_FILE, HLP_M_FILE
  HELPSUBITEM MI_OPEN, HLP_MI_OPEN
  HELPSUBITEM MI_SAVE, HLP_MI_SAVE
  HELPSUBITEM MI_CLOSE, HLP_MI_CLOSE
  HELPSUBITEM M_EDIT, HLP_M_EDIT
  HELPSUBITEM MI_CUT, HLP_MI_CUT
  HELPSUBITEM MI_COPY, HLP_MI_COPY
  HELPSUBITEM MI_PASTE, HLP_MI_PASTE
  HELPSUBITEM MI_DELETE, HLP_MI_DELETE
  HELPSUBITEM M_IMAGE, HLP_M_IMAGE
  HELPSUBITEM MI_PRINT, HLP_MI_PRINT
  HELPSUBITEM MI_INFO, HLP_MI_INFO
  HELPSUBITEM M_HELP, HLP_M_HELP
  HELPSUBITEM MI_HELPFORHELP, HLP_MI_HELPFORHELP
  HELPSUBITEM MI_EXTHELP, HLP_MI_EXTHELP
  HELPSUBITEM MI_KEYSHELP, HLP_MI_KEYSHELP
  HELPSUBITEM MI_HELPINDEX, HLP_MI_HELPINDEX
}

HELPSUBTABLE SUBHLP_INFO
{
  HELPSUBITEM PB_CANCEL, HLP_PB_CANCEL
  HELPSUBITEM PB_HELP, HLP_PB_HELP
}

Help Panel Definitions

Help panels are defined via a GML language that is compiled using the "Information Presentation Facility Compiler" (IPFC). The tags, described in the Programming Guide, allow you to display text, display graphics, create hyperlinks, display footnotes, and a variety of other things. Because the language is fairly complex, a rigorous treatment is beyond the scope of this discussion. A brief description of a .IPF file is in order, however.

The GML language is based upon "tags" - usually a beginning tag and an ending tag - which describe something. In a not-so-abstract way, this means that you use tags to create everything from panels to bitmap inclusions, and everything in between. Tags begin with a colon and end with a period; additionally, they can have attributes associated with them.

An .IPF file begins and ends with a :userdoc. and :euserdoc. tag. Panels are created using the :h1. and :h2. tags ('h' is for "heading"), the difference between the two being that (by default) :h1. panels are visible in the table of contents, while :h2. panels are not. Headers can have a resource id attribute and a name attribute. For example:

:h1 res=256 id='MYPANEL'.A Heading
:p.This is a help panel

...creates a help panel with resource id=256 and having the name "MYPANEL". The title of the panel comes immediately after the ending period.

Paragraphs are started with the :p. tag, and have no ending tag. Finally, emphasis is done via the :hpn. tag, where 'n' is an emphasis level from 1-9. Experiment with this value to see what it produces. The emphasis tag has an ending tag :ehpn. where n matches the value on the beginning tag.

If it isn't already clear, the resource ids of the panels should match those in the HELPSUBTABLEs. This introduces a limitation of the IPFC compiler - there is no way to specify constants for the resource ids. You could use the C preprocessor, but you could encounter problems. For example:

:h1 res=EXTHELP_MAIN.Extended help for MyApp.

When the Microsoft C preprocessor substitutes 256 for "EXTHELP_MAIN", it sees a ".E" immediately following and expects a floating point number in exponential notation ("256.Ex"). When it sees an 'x' instead of a '+' or '-', it flags it as an error (a workaround to this is to give the panel a dummy name. Thus, you get ":h1 res=EXTHELP_MAIN id='DUMMY1'.Extended help for MyApp").

A nice application to write would be a IPFC preprocessor that scans the IPF file substituting values for the constants representing them that are defined in a C #include file. But I digress...

"But Why Daddy?"

The need for the WinAssociateHelpInstance call is not obvious, so why do we need to call it? The answer to this lies in the manner in which the Help Manager determines which panel to display when F1 is pressed.

When F1 is pressed, the Help Manager checks to see if the active window is associated with a particular Help Manager instance. If not, the parent chain is referenced until the first window with an associated help manager instance is found (stopping at the window who's parent is the desktop). If no window in the parent chain is found, the owner chain is likewise searched and an HM_ERROR is sent to the active window if no window was found there either.

If an associated window is found, its help subtable is determined and the id of the window/control with the focus is looked up in the subtable. If no match is found, a HM_HELPSUBITEM_NOT_FOUND is sent to the application and is acted on accordingly, or else the panel for the window/control is displayed.

To sum up, you should call WinAssociateHelpInstance whenever:

1) you create the main frame window or
2) whenever you display a dialog box due to a user action.

For dialog boxes, you should disassociate the help instance in the dialog procedure's processing of the WM_DESTROY message, by calling WinAssociateHelpInstance with the frame window that owns the dialog.

Summary

In summary, the following things need to be done:

  • Create a help instance
  • Associate the help instance after creating the main window
  • In the window procedure
    • Trap the WM_COMMANDS for the "Help for help...",
    • "Extended help...", "Keys help...", and "Help index..." and send the appropriate HM_* message to display the requested panel
  • For every dialog
    • Associate the help instance with the dialog in WM_INITDLG
    • Disassociate the help instance in WM_DESTROY
    • Trap WM_HELP for buttons with BS_HELP style.
  • Create the HELPTABLEs and HELPSUBTABLEs in the .RC file
  • Create the help panels.

The code you will write is rather common, and will usually not need to be modified when using it is various applications, so you can simply copy the code when writing new applications.