The Help Manager and Online DocumentationWritten by Larry Salomon, Jr. |
IntroductionMore 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:
The BasicsGML 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 componentsThere are three help-related components to any PM application, listed below.
Source codeThis 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
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. MessagesThere 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:
The following three messages sent to your application are probably the most widely used:
ResourcesTo 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.
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 definitionsThis 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:
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
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 blahFigure 7: Heading examples This does not apply to headings that have a lower value than their predecessors. ParagraphsThe 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 &., 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. ListsIPF (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. EmphasisEmphasis 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
Emphasis markup has no attributes. HypertextEver 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:
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. GraphicsGraphics 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 MileOkay, 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? HooksWhat 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
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. EpilogueNow 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.
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. SummaryWhew! 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. |