HELP!
By: Gary White (dBVIPS)
Overview
Okay... Youve 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. Youve designed and re-designed the user interface until
its 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.
One small problem remains; your users
arent half as smart as that half-stupid chimpanzee. Whats the solution?
Youre 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 its possible because Ive done it. With that experience, I feel
that Im 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 dont take advantage of one of them. I use a program called HelpScribble.
It makes writing a Windows help file childs 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?" Its 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?
Youre really lucky, because Visual dBASE makes this very easy.
Basic Help Using the F1
Key
Each 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 files 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, thats all you need to do. It gets
only slightly more complex if you want to implement help via a menu call. If you
dont 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 its easiest to
use the Windows API for all these features. Dont let this frighten you. Its
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. Youd normally want to place this code in your
applications start-up code, or possibly in a forms onOpen event handler code.
Lets examine the function
in just a bit of detail to understand what it is and how well be calling it:
First, the EXTERN keyword tells VdB that were 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, youd 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 youre 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 Programmers
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 its expecting a numeric
value when we try to pass it a string.
The next thing well 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 forms 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 youve taken my
advice and set the forms 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 dont need to do anything special when creating the
help file because the macros are built-in. Instead of using the HELP_FINDER constant, well 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
youve 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 were 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 projects 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 forms 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, theyll 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!
|