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:
- The call can complete successfully, and the return value is the handle of the help instance.
- The function can partially complete, returning a help instance handle and specifying an error code in the ulReturnCode field.
- 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.
|
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 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.
- The user presses F1.
- The help instance determines the ID of the window that it is currently associated with.
- The HELPITEM for the given window ID is referenced, and the appropriate HELPSUBTABLE is determined.
- 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.
- The help panel definition is retrieved from the compiled help file.
- 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
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_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 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 |
&.
|
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:
- Hooks are "performance-eaters," so help-related activity will execute faster using HELPTABLEs.
- Using HELPTABLEs is less confusing which makes them easier to code.
Disadvantages:
- Each message box requires a HELPITEM in the HELPTABLE. For large
applications, your HELPTABLE can quickly get unwieldy using HELPTABLEs.
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:
- 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.
- Add a HELPSUBTABLE whose identifier is the same as that of the second parameter of HELPITEM created in step 1.
- 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