HELP!
By: Gary White (dBVIPS)

Overview
Okay... You’ve sweated and slaved over your latest creation. You tested and re-tested. You scratched and scraped and clawed your way up the learning curve and completed your Visual dBASE application. You beta tested it on your co-workers, on your spouse and your children. You’ve designed and re-designed the user interface until it’s so instinctively intuitive that a half-stupid chimpanzee could easily determine the meaning of life and the origin of the universe. Your application is finally finished.

Help MenuOne small problem remains; your users aren’t half as smart as that half-stupid chimpanzee. What’s the solution? You’re going to have to provide on line help. Great! How do you do that? Well, writing the help file is beyond the scope of this article. However, it is possible to write a Windows help file using a word processor that can save a file in Rich Text Format (RTF). I know it’s possible because I’ve done it. With that experience, I feel that I’m qualified to tell you it is tedious and time consuming. That sort of tedium is exactly what computers are designed to relieve us of, so that we can spend our time on more creative pursuits. 

There are a huge number of inexpensive Windows help authoring programs available. You are absolutely wasting your time if you don’t take advantage of one of them. I use a program called HelpScribble. It makes writing a Windows help file child’s play. You can find information on HelpScribble and many others at http://www.helpmaster.com/hlp-standalone.htm.

Tips
As mentioned, this document is not designed to teach you how to write the help file, but I will offer a couple of tips. One of the more frequent questions I see relating to help files is "how do you include a screen shot?" It’s a simple matter to press Alt+PrintScrn to copy a bitmap of the active window to the Windows clipboard. You can then paste it into Windows Paint, or your favorite image editor. Once there, you can crop it and add annotations, if desired. Save it as a bitmap (.BMP). You may then use it in your help project.

When writing your help file, you will (or should) assign a keyword(s) to each topic, except possibly popup topics. These keywords are what appears in the help index. You can use more that one keyword to identify the same topic. Try to think of all the different things that a user might think of when trying to find the topic. While you, being an expert dBASE developer, might think that append is the obvious thing to look up when you want to know how to add a new record to a table, your user might be thinking more of new, or insert, or add, or something even different. The more keywords you can think of, the easier it will be for users to find the help they are looking for.

Enough said. I will now assume that you have written the most comprehensive, concise, and detailed help file in the history of help files. How do you then implement this functionality in your application? You’re really lucky, because Visual dBASE makes this very easy.

Basic Help Using the F1 Key
InspectorEach form has a property called "HelpFile" and this is a reference to the help file that you have written. If help is invoked by pressing the F1 key, it will open the appropriate help file. If the control which has focus when the F1 key is pressed does not have a HelpId assigned, the first topic of the help file is displayed unless another topic has been created as the contents page. If the control which has focus when the F1 key is pressed has a string assigned to the HelpId property, the help file’s index will be searched for that string. If the string is found in the index, the corresponding topic will be displayed. If more than one instance of the string is found in the index, a dialog with the various topics will be displayed, allowing the user to choose the topic to display. If the string is not found, the index will be displayed in the help finder dialog with the nearest match displayed in the index.

Help By Using Menu Commands
If you only want to implement help via the F1 key, that’s all you need to do. It gets only slightly more complex if you want to implement help via a menu call. If you don’t need context sensitive help, you can use the SET HELP TO command to specify your help file. Then, simply issue the HELP command to open the help file to the contents page. In order to open the help file and display a specific help topic you could use the
KEYBOARD command, however, that will still not give you the ability to open the Help on Help file, nor will it allow you to open the help dialog with a specific tab active.  I find that it’s easiest to use the Windows API for all these features. Don’t let this frighten you. It’s easy. The first thing you need to do is to "prototype" the function:


if type("WinHelp") # "FP"
   extern clogical WinHelp(chandle, cstring, cuint, cstring) user32 ;
      from "WinHelpA"
endif

All the above does is checks to see if the WinHelp API function has been defined and, if not, tells VdB what the function is and where to find it. For more information, look up the topic EXTERN in the VdB help file. You’d normally want to place this code in your application’s start-up code, or possibly in a form’s onOpen event handler code.

Let’s examine the function in just a bit of detail to understand what it is and how we’ll be calling it:

First, the EXTERN keyword tells VdB that we’re defining a function that resides in a DLL that does not belong to VdB.

The CLOGICAL keyword tells us that the external function will return a logical (TRUE/FALSE) value. The name we are going to use to refer to this external function is WinHelp. Note that this does not have to match anything in the DLL. We could, just as easily called the function Gibberish and neither VdB, nor the Windows API would have cared in the slightest, so long as the "from" part of the command correctly named the way the function is named in the DLL. Normally, it is easiest and clearest if you use the actual function name. However, as you will see in a moment, you may want to define the same function in two different ways. In this case, you’d need to name them differently. This is perfectly legal and will work correctly.

Next come the four parameters that the function accepts:

CHANDLE The window handle (hWnd) of the calling help

CSTRING The file name of the path & name of the help file

CUINT The type of help to invoke (more on this later)

CSTRING Additional data

Next, USER32 indicates the DLL, in which the function can be found.

Finally FROM "WinHelpA" is the name of the function in the DLL.

Now, if you’re sharp, you may have noticed that this prototype differs from what is shown in the Win32api.prg in your Include directory. It also differs from what is shown in the Win32 Programmer’s Reference. Those both show the last parameter as a DWORD. That data type is correct. However, we want to be able to specify a help topic using a string. If we use the CSTRING type in the prototype, what is actually passed to the function is a pointer to the string. This pointer is, in fact, a 32-bit number indicating the address of the string. This means that these are compatible data types and the result will be what we want. We can pass the string using the 32-bit pointer. If we prototype the function using the DWORD data type, VdB will raise an exception due to a type mismatch because it’s expecting a numeric value when we try to pass it a string.

The next thing we’ll do is define a few constants to represent the CUINT parameter to indicate what type of help we want to call:


#define HELP_FINDER 0x000B // opens the help dialog
#define HELP_COMMAND 0x0102 // sends the command in the extra data param
#define HELP_KEY 0x0101 // the keyword to look up is in extra data
#define HELP_PARTIALKEY 0x0105 // partial keyword in extra data
#define HELP_HELPONHELP 0x0004 // open the help on help file

This will allow us to call the function using named parameters that indicate more clearly the meaning of the function call.

In order to open the help file and display a topic, you simply call the WinHelp function with the form’s hWnd property, the help file name, the HELP_KEY constant and the keyword string that appears in the index and points to the topic we want:


Winhelp(form.hWnd, form.helpFile, HELP_KEY, "My Topic")

Now, if we want to open the help Contents/Index dialog, all we need to do (assuming you’ve taken my advice and set the form’s menuFile) is issue the command:


WinHelp(form.hWnd, form.helpFile, HELP_FINDER, 0)

The above, will open the help dialog, normally on the same tab that was last used. The Windows API, unfortunately, does not surface a method to specify a specific tab to be selected when the dialog opens. Fortunately, the Windows help engine includes built-in macros which allow you to do many different things. You can use some of those macros to specify the tab you want active when the dialog opens and the API allows you to send commands to the help engine to execute a macro. You don’t need to do anything special when creating the help file because the macros are built-in. Instead of using the HELP_FINDER constant, we’ll use the constant HELP_COMMAND which just means that we want to send a command (a macro name) to WinHelp. So, if you wanted to display the table of contents, you would (assuming you’ve already prototyped the API function) simply call it like this:


WinHelp(form.hWnd, form.helpFile, HELP_COMMAND, "Contents()")

If you wanted to open the dialog on the "Find" tab, you only need to change the name of the macro:


WinHelp(form.hWnd, form.helpFile, HELP_COMMAND, "Find()")

Opening the dialog on the Index tab is a little different. There is no "Index()" macro. The way you force the "Index" tab is to use the HELP_PARTIALKEY constant and set the final parameter (the string to search for) to an empty string:


WinHelp(form.hWnd, form.helpFile, HELP_PARTIALKEY, "")

There is one other function commonly displayed on a Help menu. That other item is "Help with Help". This is simply a help file, included with the operating system, that helps users find out how to use the help engine. This functionality is built into both the API and the help engine. I find it most convenient, since we’re using the macros already, to simply supply the appropriate macro name:


WinHelp(form.hWnd, form.helpFile, HELP_COMMAND, "HelpOn()")

The other method, should you prefer, would be to define one more constant and use that in the WinHelp API call:


WinHelp(form.hWnd, form.helpfile, HELP_HELPONHELP, "")

Where to Put the Code
My preference is to keep these functions in the form for which they would be called. So, you could easily incorporate them into your project’s base custom form, so it would include the following, for the support of implementing on line help:


#define HELP_FINDER 0x000B
#define HELP_COMMAND 0x0102
#define HELP_KEY 0x0101
#define HELP_PARTIALKEY 0x0105
#define HELP_HELPONHELP 0x0004

class BaseForm of form custom

   function HelpContents
      WinHelp(form.hWnd, form.helpfile, HELP_COMMAND, "Contents()")
      return

   function HelpFind
      WinHelp(form.hWnd, form.helpfile, HELP_COMMAND, "Find()")
      return

   function HelpIndex
      WinHelp(form.hWnd, form.helpFile, HELP_PARTIALKEY, "")
      return

   function HelpOnHelp
      WinHelp(form.hWnd, form.helpfile, HELP_COMMAND, "HelpOn()")
      return

   function HelpTopic(cTopic)
      WinHelp(form.hWnd, form.helpfile, HELP_KEY, cTopic)
      return

   function onOpen
      // prototype the API call to open Windows Help
      if type("WinHelp") # "FP"
         extern clogical WinHelp(chandle, cstring, cuint, cstring) user32;
            from "WinHelpA"
      endif
      return

endclass

Then, in your derived forms, simply set the helpId property of controls to a keyword that will find the appropriate topic in the index. Then, pressing the F1 key will open the help file to the topic associated with the currently active control. Implementing context sensitive help from a menu call is a simple matter of calling form.HelpTopic(form.activeControl.helpId). Or you could implement a Help with Help menu command with form.HelpOnHelp().

Your menu could then include, among other things:


this.HELP = new MENU(this)
with (this.HELP)
   text = "&Help"
endwith

this.HELP.CONTEXT = new MENU(this.HELP)
with (this.HELP.CONTEXT)
   onClick = {;form.HelpTopic(form.activeControl.helpId)}
   text = "Context Sensitive"
   shortCut = "F1"
endwith

this.HELP.CONTENTS_INDEX = new MENU(this.HELP)
with (this.HELP.CONTENTS_INDEX)
   onClick = {;form.helpContents()}
   text = "Contents " + '&' + '&' + " Index"
   shortCut = "Shift+F1"
endwith

this.HELP.SEP1 = new MENU(this.HELP)
with (this.HELP.SEP1)
   text = ""
   separator = true
endwith

this.HELP.HELPONHELP = new MENU(this.HELP)
with (this.HELP.HELPONHELP)
   onClick = {;form.helpOnHelp()}
   text = "Help with Help"
endwith

Now, assuming you have set the form’s helpFile property and the helpId of the various controls, when the user presses F1, or selects Context Sensitive from the Help menu, they get the same action. The help file is opened to the appropriate topic. If they select Contents/Index, or press Shift+F1, they’ll get the dialog allowing them to choose the topic they want. In other cases, you can specify a specific tab of the help dialog to display, or easily display a specific topic, from a pushbutton in a dialog, for example, by calling:


Form.HelpTopic("SomeTopic")

Summary
Your application can now easily include a professionally implemented, context sensitive help system. Your users will find your application easier to use, which will make you look better and reduce the time you need to spend providing phone support. Everybody wins!

 

Gary White
Some dBASE Stuff
http://www.apptools.com/dbase
1