The Help Manager and Online Documentation

From EDM2
Jump to: navigation, search

By Larry Salomon Jr.

Introduction

More times than I care to remember, I have been asked "How do I add online help to my applications?" or "How can I write online books?" While the latter takes less time to answer, I often find myself talking for 45 minutes or so on everything from what a GML language is to the sequence of events that happen when a user presses the F1 key.

Finally today I got tired of repeating myself (even as much as I love to talk) so I decided to write the article that has been promised for so long. Hopefully after reading this, you should be able to do the following:

  • Briefly explain what a GML language is and what markup is
  • List the three components needed when adding context-sensitive help to a PM application and what each component's purpose is
  • Describe the sequence of events that occur when a user presses F1
  • Detail the minimum markup required to create a valid online help file and to create a valid online book file
  • Be able to write the markup for headings, paragraphs, lists (all five types), emphasis, hypertext links, and graphics.
  • Explain some of the limitations of the IPFC compiler

The Basics

GML is an acronym for generalized markup language and describes a group of languages which perform operations on blocks of text. Typically, these languages are used for output formatting but are not limited to this arena. The language consists of control words (called tags) interspersed within blocks of text; these tags have the form

:tag [attribute[=value] [attribute[=value] [...]]].[text]

Figure 1: GML tag syntax

The attributes shown above vary from tag to tag and may not exist at all. Likewise, each attribute may or may not accept a value; consult the "Information Presentation Facility" reference guide which comes with the 2.x toolkit for a complete list of the attributes for each tag.

Frequently, a tag is used to mark the beginning of a change in formatting and has a corresponding end tag to signify the end of that change. The end tag often is the same as the begin tag prefixed with an "e". Thus, you use :hp2. to begin emphasis level 2 and :ehp2. to end it.

The term markup is used to describe a combination of tags and text in a GML file.

Application components

There are three help-related components to any PM application, listed below.

  • Source code - calls to and from the Help Manager from your application
  • Resources - relationships between focus windows and help panels
  • Panel definitions - GML source describing the help panels' appearance

Source code

This consists of the various calls to Help Manager functions from within your PM application. The bare minimum exists in your main() function and creates an instance of the Help Manager:

#define INCL_WINHELP
  :

INT main(USHORT usArgs,PCHAR apchArgs[])
{
     :
   HELPINIT hiHelp;

   habAnchor=WinInitialize(0);
   hmqQueue=WinCreateMsgQueue(habAnchor,0);
     :
     : /* Create frame window */
     :
   if (hwndFrame!=NULLHANDLE) {
        :
        : /* Initialize HELPINIT structure */
        :
      hwndHelp=WinCreateHelpInstance(habAnchor,&hiHelp);
      WinAssociateHelpInstance(hwndHelp,hwndFrame);
        :
        : /* Message loop in here somewhere */
        :
      WinDestroyHelpInstance(hwndHelp);
      WinDestroyWindow(hwndFrame);
   } /* endif */

   WinDestroyMsgQueue(hmqQueue);
   WinTerminate(habAnchor);
}

Figure 2: Help Manager initialization

The HELPINIT structure is a rather large conglomeration of configurable items.

typedef struct _HELPINIT {
   ULONG cb;
   ULONG ulReturnCode;
   PSZ pszTutorialName;
   PHELPTABLE phtHelpTable;
   HMODULE hmodHelpTableModule;
   HMODULE hmodAccelActionBarModule;
   ULONG idAccelTable;
   ULONG idActionBar;
   PSZ pszHelpWindowTitle;
   ULONG fShowPanelId;
   PSZ pszHelpLibraryName;
} HELPINIT, *PHELPINIT;

Figure 3: HELPINIT structure

cb
Specifies the size of the structure in bytes
ulReturnCode
On exit, contains any error codes returned from the Help Manager
pszTutorialName
Points to the string specifying the default tutorial name. If NULL, there is no default tutorial or it is specified in the panel definition file.
phtHelpTable
Points to the HELPTABLE to use, or specifies the resource id of the HELPTABLE to be loaded from the resource file. If this specifies the resource id, it is contained in the low word and the high word must be 0xFFFF.
hmodHelpTableModule
Specifies the handle of the module from whence the HELPTABLE is to be loaded.
idAccelTable
Specifies the resource id of the accelerator table to be used. A value of 0 specifies that the default is to be used.
idActionBar
Specifies the resource id of the action bar to be used. A value of 0 specifies that the default is to be used.
hmodAccelActionBarModule
Specifies the handle of the module from whence the accelerator table and action bar are to be loaded.
pszHelpWindowTitle
Points to the help window title text.
fShowPanelId
Specifies whether or not to show the panel identifier (if present). Valid values are CMIC_SHOW_PANEL_ID and CMIC_HIDE_PANEL_ID.
pszHelpLibraryName
Points to the filename containing the compiler panel definitions.

It needs to be noted that even though a valid window handle is returned from WinCreateHelpInstance(), an error might have occurred whose value is specified in the ulReturnCode field of the HELPINIT structure.

Messages

There will be times when you will want to send messages to the Help Manager and when messages will be received. The four most frequent messages sent to the Help Manager are listed below:

HM_HELP_INDEX
Causes the help index to be displayed
HM_EXT_HELP
Causes the extended (general) help panel defined for the active window to be displayed (described more later)
HM_DISPLAY_HELP
Causes the panel specified by name (in mpParm1) or resource id (in mpParm2) to be displayed. Specifying NULL and 0 for these values cause the "Using Help" panel to be displayed.
HM_KEYS_HELP
Causes the keys help panel to be displayed. Since the active key set is dependent on the current state of the application, this cannot be statically defined in the resource tables. Instead, the Help Manager responds by sending your application an HM_QUERY_KEYS_HELP message to get the resource id of the keys help panel.

The following three messages sent to your application are probably the most widely used:

HM_ERROR
Sent whenever an error occurs between the time F1 is pressed and the help operation ends. The error code is specified in mpParm1
HM_HELPSUBITEM_UNDEFINED
Sent whenever help was requested but no entry in the HELPSUBTABLE was found.
HM_INFORM
Sent whenever a link with the inform attribute is encountered.

Resources

To make the connection between a window and a help panel, two new resource types were added to PM's resource file definition - HELPTABLEs and HELPSUBTABLEs. Together, they specify an array of variable length lists that map a window resource id to a help panel resource id.

HELPTABLE RES_CLIENT
{
   HELPITEM RES_CLIENT, SUBHELP_CLIENT, GENHELP_CLIENT
   HELPITEM RES_DIALOG1, SUBHELP_DIALOG1, GENHELP_DIALOG1
   HELPITEM RES_DIALOG2, SUBHELP_DIALOG2, GENHELP_DIALOG2
     :
   HELPITEM RES_DIALOGn, SUBHELP_DIALOGn, GENHELP_DIALOGn
}

HELPSUBTABLE SUBHELP_CLIENT
{
   HELPSUBITEM WID_WINDOW1, HID_PANEL1
   HELPSUBITEM WID_WINDOW2, HID_PANEL2
     :
   HELPSUBITEM WID_WINDOWn, HID_PANELn
}

HELPSUBTABLE SUBHELP_DIALOG1
{
   HELPSUBITEM WID_WINDOW1, HID_PANEL1
   HELPSUBITEM WID_WINDOW2, HID_PANEL2
     :
   HELPSUBITEM WID_WINDOWn, HID_PANELn
}

HELPSUBTABLE SUBHELP_DIALOG2
{
   HELPSUBITEM WID_WINDOW1, HID_PANEL1
   HELPSUBITEM WID_WINDOW2, HID_PANEL2
     :
   HELPSUBITEM WID_WINDOWn, HID_PANELn
}

HELPSUBTABLE SUBHELP_DIALOG3
{
   HELPSUBITEM WID_WINDOW1, HID_PANEL1
   HELPSUBITEM WID_WINDOW2, HID_PANEL2
     :
   HELPSUBITEM WID_WINDOWn, HID_PANELn
}

Figure 4: Help Manager resource structures

Each HELPITEM specifies the resource id of an active window, the id of the HELPSUBTABLE associated with this window, and the resource id of the General Help panel associated with this window.

Each HELPSUBITEM specifies a focus window resource id (WID_*) and a corresponding help panel resource id (HID_*).

What are the rules that the Help Manager uses to get from F1 to a help panel id? To answer that question, we need to know the sequence of events that occur when a user presses F1. Below, we assume that the application is help-enabled.

  1. The user presses F1
  2. The Help Manager queries the active window resource id (ID1) and the focus window id (ID2).
  3. ID1 is used in a lookup of the HELPTABLE to determine the HELPSUBTABLE to use.
  4. ID2 is used in a lookup of the HELPSUBTABLE to find the resource id of the help panel to display.
  5. The Help Manager displays the panel.

There are two obvious error conditions: 1) there is no HELPSUBTABLE for the active window and 2) there is no HELPSUBITEM for the focus window. The former is resolved by examining each window in the parent window chain until a window that does have a HELPSUBTABLE is found and then the process continues as normal. If this still yields nothing, the owner window chain is searched and then if nothing still, an error message is sent to the active window. The latter is resolved by first sending an HM_HELPSUBITEM_UNDEFINED message to attempt to alleviate the situation. If the application returns FALSE the general help panel specified on the HELPITEM statement is displayed.

Panel definitions

This is, by far, the most time-consuming portion of help-enabling. Not only do you have to write the text to be displayed, but you must also be aware of formatting options and what effect they have on the output. At a minimum, you must have the following to create a valid online help file:

:userdoc.
:h1.Heading 1
:p.Paragraph 1
:euserdoc.

Figure 5: Minimum GML markup

The tags used above are described below:

:userdoc.
Specifies the beginning of a user document.
:h1.
Specifies a heading level 1.
:p.
Specifies a new paragraph.
:euserdoc.
Specifies the end of the user document.

Online book writers must also specify a :title.Document title after the :userdoc. tag.

As you can imagine, this file doesn't do much. In fact, it does nothing since as you can see nowhere are an help panel resource ids specified (even though you don't know how to specify them). For that matter, what defines a panel?

A panel is a block of formatted text beginning with a heading level and ending with the beginning of the next panel displayed in the table of contents or the end of the document body. (the back matter of a document can contain an index.) Heading levels are specifies after the h and can be in the range 1-9; by default, only heading levels 1-3 are displayed in the table of contents, but this is configurable using the :docprof. tag. A heading may also have attributes; these are the res, id, name, and hide attributes.

:hn [res=value][id=value][name=value][hide].

Figure 6: Heading tag syntax

res=value
This specifies a numeric resource id for the panel and is used in the HELPSUBITEM definitions and can be used to specify the target of a hypertext link.
id=value
This specifies an alphanumeric id that can be used to specify the target of a hypertext link.
name=value
This specifies an alphanumeric id that can be referenced by an application.
hide
This specifies that, regardless of the heading level, the panel should not show up in the table of contents. This is useful for hypertext links.

Headings must have data associated with them, i.e. you cannot have an :h1. tag immediately followed by an :h2. tag. Also, heading levels other than 1 that are ordinally higher than their predecessors must have the next cardinal value. Thus, the first example is valid while the second is not:

:h1.Heading 1
:p.Blah blah blah
:h2.Heading 2
:p.Blah blah blah
:h1.Heading 1
:p.Blah blah blah

:h1.Heading 1
:p.Blah blah blah
:h3.Heading 3
:p.Blah blah blah
:h1.Heading 1
:p.Blah blah blah

Figure 7: Heading examples

This does not apply to headings that have a lower value than their predecessors.

Paragraphs

The most frequently used tag is probably the :p. tag, used to begin a new paragraph. It should be noted that some constructs implictly begin on a new paragraph, so this is not needed. A paragraph is denoted by ending the current line, inserting a blank line, and continuing on the next line. The current indentation level (modified by lists, etc.) is not changed.

:p.

Figure 8: Paragraph tag syntax

A word here should be mentioned about symbols. What happens when you want to put a colon (:) in your panels? How is the compiler going to be able to differentiate between a colon as part of the text or as the beginning of a tag. GML defines a syntax for symbols such that they begin with an ampersand (&) and end with a period with a symbol name in the middle. Thus, a colon is &colon., and ampersand is &amp., etc.. Some other commonly used symbols appear below:

&lbrk. Left bracket ([) &rbrk. Right bracket (]) &lbrc. Left brace ({) &rbrc. Right brace (}) &vbar. Vertical bar (|) &apos. Apostrophe (') &bsl. Backslash (\) &odq. Open double quote (") &cdq. Close double quote (") &osq. Open single quote (`) &csq. Close single quote (')

You should use the symbol syntax instead of the actual characters whenever possible to ease the process of translating your IPF source to other languages, should that happen, since the compiler defines the code point to which each symbol is mapped according to the codepage in effect.

Lists

IPF (Information Presentation Facility - the language definition) defines five types of lists - simple, unordered, ordered, definition, and parameter. All lists consist of a begin tag, one or more list items, and an end tag. Definition and parameter list items are unique in that they consist of two parts.

The begin tags/end tags are :sl./:esl., :ul./:eul., :ol./:eol., :dl./:edl., and :parml./:eparml. for simple, unordered, ordered, definition, and parameter lists respectively. List items for the first three types are specified using the :li. tag. Definition list terms are specified using the :dt. tag, and the corresponding definitions using the :dd. tag. Parameter list items are specifies using the :pt. tag and - like the definition lists - each item has a corresponding definition specified using the :pd. tag.

:sl [compact].
:li.text
:esl.

:ul [compact].
:li.text
:eul.

:ol [compact].
:li.text
:eol.

:dl [tsize=value][compact][break={ all | none | fit }].
[:dthd.text]
[:ddhd.text]
:dt.text
:dd.text
:edl.

:parml [tsize=value][compact][break={ all | none | fit }].
:pt.text
:pd.text
:eparml.

Figure 9: List tags syntax

Below are some examples of lists.

:sl.
:li.Simple list item 1
:li.Simple list item 2
:li.Simple list item 3
:esl.

:ul.
:li.Unordered list item 1
:li.Unordered list item 2
:li.Unordered list item 3
:eul.

:ol.
:li.Ordered list item 1
:li.Ordered list item 2
:li.Ordered list item 3
:eol.

:dl.
:dt.Term 1
:dd.Definition 1
:dt.Term 2
:dd.Definition 2
:dt.Term 3
:dd.Definition 3
:edl.

:parml.
:pt.Parameter 1
:pd.Definition 1
:pt.Parameter 2
:pd.Definition 2
:pt.Parameter 3
:pd.Definition 3
:eparml.

Figure 10. Examples of list markup

The above are formatted as:

Simple list item 1

Simple list item 2

Simple list item 3

o Unordered list item 1

o Unordered list item 2

o Unordered list item 3

1.  Ordered list item 1

2.  Ordered list item 2

3.  Ordered list item 3

Term 1   Definition 1

Term 2   Definition 2

Term 3   Definition 3

Parameter 1
  Definition 1

Parameter 2
  Definition 2

Parameter 3
  Definition 3

All lists accept the attribute compact which specifies that items should not be separated by blank lines. Definition and parameter lists also accept the attributes tsize=value and break=[all | fit | none]. tsize specifies the width of the term column in units of the average character width of the current font. break specifies what is to be done when the term exceeds the column width; the default is none and starts the definition after the term preceeded by a space. all specifies that all definitions begin on a new line indented by the term column width. fit specifies that definitions whose terms exceed the column width begin on a new line as in all.

Emphasis

Emphasis markups consist of a begin and end tag and have the form :hpn. There are different emphasis levels, ranging from 1 to 9, which is specified as n.

:hpn.

Figure 11. Emphases tag syntax

Level Meaning
1 Italic
2 Bold
3 Italicized bold
4 Alternate color 1
5 Underlined
6 Underlined italic
7 Underlined bold
8 Alternate color 2
9 Alternate color 3

Emphasis markup has no attributes.

Hypertext

Ever since online documentation became all-the-rage, one of the greatest advantages that it touted was the elimination of the phrase "For more information, see page...". Hypertext - as it was termed - is the ability to jump from one point to another by performing some action (usually a click of the mouse) on a hot-link; these hot-links are usually a word or phrase that has more, related information associated with it that the user will supposedly want to read eventually but without cluttering up the topic already being read.

Hypertext in IPF markup is accomplished using the :link. tag and its corresponding end tag.

:link reftype={ fn | hd | launch | inform } [res=value][refid=value]
   [object='value'][data='value'][x=value y=value cx=value cy=value].
:elink.

Figure 12. Hypertext tag syntax

The type of the link destination is specified by the reftype parameter. It can have one of the following values:

fn
Footnote. The footnote must have an id attribute which is specified in the refid parameter.
hd
Panel. The panel must have either an id or a resource id specified using the id and res attributes, respectively. This identifier is specifies in the refid and res attributes of the link tag.
launch
Application. The application, whose full path, name, and extension must be specified in the object attribute is executed. Any command line parameters may be specified in the data attribute.
inform
Message. This is applicable to online help only. The active window is sent an HM_INFORM message with the value of the res attribute passed in mpParm1.

Hypertext links can be used to allow access to panels displayed elsewhere or not displayed at all; for example, suppose that you are on heading level 3 and you have some indirectly related information that you want to avoid cluttering the panel with. You cannot make it a heading level 4 because it won't show up in the table of contents. Linking makes a lot of sense here; make the target panel heading hidden and level 1 (to avoid nonsensical error messages from the compiler) and then link it to allow the user to read the information only if desired.

Graphics

Graphics can be included in your documents also; both OS/2 bitmaps and metafiles are supported. I have found it useful to use a screen capture program to export a bitmap of my application to a file, annotate it with a graphical editor, and then include it in the online help to label the items of interest. The tag that is used is the :artwork.. tag.

:artwork name='filename' [runin][linkfile='filename']
   [align={ left | center | right }]

Figure 12. Hypertext tag syntax

name specifies the filename containing the bitmap or metafile. runin specifies that the graphic does not force an newline before and after the graphic. align specifies the justification of the graphic. linkfile is for graphical linking (called hypergraphics) and will not be discussed.

The Extra Mile

Okay, so you've completely enabled your application to use online help...or have you? Actually, with the exception of rare applications, there is one more area that needs to be covered and that is message box help.

Message boxes are modal dialogs that contain information for the user - error messages, usually. However, there is only so much that you can say in a message box; so, there is a style that you can specify on the call to WinMessageBox() that says to add a Help pushbutton. Fine, so you have a Help pushbutton. The user selects it. Nothing happens. What goes on under the covers?

Hooks

What goes on is that the message box receives the WM_HELP message and, because it can't determine the window that called WinMessageBox() (for more information on why this is so, read the documentation for WinLoadDlg() and pay attention to the part about determining the real owner of a dialog), it sends the message to the help hook.

What is a help hook? Well, a hook in general is a function that gets called by an application (or, in this case, the system) when specific types of events occur; thus, a help hook is a function that receives notification of help-related events.

BOOL EXPENTRY helpHook(HAB habAnchor,
                       SHORT sMode,
                       SHORT sTopic,
                       SHORT sSubTopic,
                       PRECTL prclPosition);

Figure 13. Help hook function prototype

habAnchor
Specifies the anchor block of the thread to receive the message
sMode
Specifies the context of the help request. The values and the contexts are:
HLPM_FRAME
When the parent of the focus window is an FID_CLIENT window. sTopic is the frame window id, sSubTopic is the focus window id, and prclPosition points to the screen coordinates of the focus window.
HLPM_WINDOW
When the parent of the focus window is not an FID_CLIENT window. sTopic is the focus window's parent id, sSubTopic and prclPosition have the same meaning as HLPM_FRAME.
HLPM_MENU
When the application is in menu mode. sTopic contains the active action bar item id, sSubTopic contains the id of the menu item with the cursor, or -1 if the action bar has the cursor.

In message boxes, the Help pushbutton is not defined with the BS_NOPOINTERFOCUS style, so it has the focus after it is selected. According to the rules, the help hook should get HLPM_WINDOW for the mode and the various identifiers in the other parameters. However, this is not the case; the help hook does indeed receive HLPM_WINDOW as the mode, but the sTopic parameter is the number specified as the fifth parameter to WinMessageBox() (the help button identifier). sSubTopic always contains the value 1. I have not verified if prclPosition points to the screen coordinates of the focus window.

Before we can utilitize this information, we need to install the help hook using the WinSetHook() function. (Don't forget to uninstall it before the application exits using the WinReleaseHook() function.) Since the Help Manager works by installing its own help hook, and since WinSetHook() puts the hook at the beginning of the hook chain, you need to remember two things: 1) install your hook after calling WinCreateHelpInstance(), and 2) always return FALSE to insure that the next hook in the chain is called.

As an ending note, someone sent me a while back, detailed instructions on a short cut which - if I remember correctly - eliminated the need for help hooks to provide message box help. Unfortunately, I lost these notes.

Epilogue

Now that we have all of the necessary information, we can start developing our online help and documents. To compile your IPF source, the Developer's Toolkit contains the IPFC compiler. It takes an IPF source file as input and compiles it to a HLP binary file in the same directory. For online documents, you need to specify the /INF switch which instead produces an INF binary file in the same directory.

You will probably notice some limitations with the compiler.

  • There is no symbolic substition a la the C preprocessor
  • Numbers can only be expressed in decimal
  • All artwork must be in the same directory as the source

The first one got so frustrating that I wrote my own IPFC preprocessor which processes C-style include files and allows you to substitute the #define macros in your IPF source as though it were a symbol. This preprocessor is included, as well as the accompanying User's Guide (in INF format, of course). The latter two problems could also be resolved by adding functionality to the preprocessor, but those features were never implemented.

Summary

Whew! A lot of information was presented here. Hopefully, you should be able to reread the objectives of this article and know the answers to the implied questions therein. The importance of online help cannot be understressed because as computer systems and applications become more complex, the more difficult it becomes to remember every feature that is provided. Online documentation is also important in that it provides a good vehicle for information dissemination and saves trees.