Visual Basic Explorer
Visual Basic Explorer
 Navigation
 Home


 Coding
 Source Code

 FAQ Center

 VB Tips

 Downloads

 ToolBox

 Tutorials

 VB Games

 VB News

 VB Award

 VB Forums



 Affiliates
 Planet Source Code

 Rent a Coder

 DirectX4VB


 Misc
 Search

 Feedback

 Advertise

 About


Need to hire
a VB coder?

Please support our sponsor:

 Home 
 Site Map 
 Forums 
 News 
 Feedback 

VB3, VB4 16/32, VB5, VB6
Level: Intermediate

Practical VB Help Connection Solutions
With a nod to C++ programmers

David E. Liske, 3910 Paragon Road, Tipton, Michigan, USA 48439
(517) 431-3813
delmar@tc3net.com
http://users.tc3net.com/delmar/

One of the biggest problems I've encountered in developing Visual Basic applications is finding complete, valid information on the basics of connecting to Help files. The information I've found has been quite confusing, and is conflicting in most instances. After quite a bit of research, and a whole lot of some seriously frustrating trial and error, I've settled on a group of functions which work quite well.

I use the HelpBreeze version 2.0 Help Compiler. Why? Well, it's one of those things that since it's the first one I ever used, it's the one I always go back to. (This is the same reason why I still use WordPerfect 6.0 for a lot of things.) HelpBreeze also supports Windows 95/98 emulation when running under Windows 3.x, which is a big plus when working on client/server systems where installed copies of Win16 and Win32 are about 50/50.

The method I use doesn't use the CommonDialog control found in Visual Basic. Rather, it's in the form of a single module, which contains the most commonly-used WinHelp DLL calling conventions for both 16 and 32-bit applications. A portion of this module also contains the path strings for any number of help files an application may need. When the module is used as a template, this particular function is the only area where changes will need to be made. The end result is the ability to call WinHelp using plain language, with a minimum of arguments to be passed, using identically-named subroutines for both 16 and 32 bit applications.

Getting Started - The Declarations Section

The syntax for calling WinHelp is the same for either the 16 or 32-bit DLL's:

BOOL WinHelp(HWND hwnd, LPCTSTR lpszHelpFile, UINT fuCommand, DWORD dwData)

This is actually the C++ version, copied from the Microsoft Help Authoring Guide. Let's boil it down a little so we can use it in Visual Basic. We know the function will return True if it succeeds and False if it doesn't. VB knows this as well, so we drop the BOOL keyword. Next, hwnd is the handle of the window requesting Help. Type this into a VB module window and it will change to hWnd automatically. The string lpszHelpFile is the path of the help file we're trying to use, fuCommand is the type of help we're requesting, and dwData can be any number of things depending on which fuCommand we're using.

WinHelp is contained in the file user.dll for win16 and user32.dll for win32. The calling conventions differ if you're developing for 16-bit Windows vs. 32-bit, so we'll use conditional compilation to cover both possibilities.

#If Win16 Then
  Declare Function WinHelp Lib "user" _
          (ByVal hWnd As Integer, _
           ByVal lpHelpFile As String, _
           ByVal wCommand As Integer, _
           ByVal dwData As Long) As Integer
  Declare Function WinHelpTopic Lib "user" Alias "WinHelp" _
          (ByVal hWnd As Integer, _
           ByVal lpHelpFile As String, _
           ByVal wCommand As Integer, _
           ByVal dwData As String) As Integer
#Else
  Declare Function WinHelp Lib "user32" Alias "WinHelpA" _
          (ByVal hWnd As Long, _
           ByVal lpHelpFile As String, _
           ByVal wCommand As Long, _
           ByVal dwData As Long) As Long
  Declare Function WinHelpTopic Lib "user32" Alias "WinHelpA" _
           (ByVal hWnd As Long, _
            ByVal lpHelpFile As String, _
            ByVal wCommand As Long, _
            ByVal dwData As String) As Long
#End If


Note that there are a couple of differences between the 16-bit and 32-bit functions. The 16-bit version uses data type integer for a couple of things while the 32-bit uses long. This is a minor difference compared to the 'A' suffix for WnHelp used in the 32-bit version.

Actually there are two possibilities for this suffix. The 'A' suffix specifies the ANSI coding for Windows 95/98 while a 'W' suffix is for the unicode of Windows NT. I found that Windows 95/98 could not access help without the 'A' suffix at all, which differs from the first DECLARE statement for 16-bit. The same may also be true for NT and its 'W' suffix but I haven't had the opportunity to find out.

Note there two different functions for using data vs. strings in the dwData section. In some instances we'll need strings to reference a keyword or topic so the WinHelpTopic function is necessary. I wouldn't have done this if the Variant or Any data types would have worked. Since they didn't, this was the next best thing.

This is all the functionality we need to get going. We could actually leave the rest of the module blank and call the DLL from anywhere using the WinHelp statement itself. But then we would have to deal with the syntax of the WinHelp statement every time, along with declaring the Help file path in every instance. Getting rid of this excess code is quite simple. Using it is even simpler.

Declaring The Help Files

We can use the Select…Case…End Select statement to return the paths of multiple Help files from anywhere in our application:

Public Function SetHelpStrings(ByVal intSelHelpFile As Integer) As String
  ' Set the string variable to
  ' include the application path
  Select Case intSelHelpFile
  Case 1
    SetHelpStrings = App.Path & "\Help\MyHelp.hlp"
  Case 2
    ' Place other Help file paths in successive Case statements
  End Select
End Function

All we need to do is specify which Case statement we want to go to and the correct Help file path comes back. We'll call this function from the subroutines we're about to discuss.

Displaying The Help Contents Tab

Displaying the Help Contents tab in the Help dialog box would appear to be a simple task. However, the documentation is laced with all kinds of "gotchas" that can cause you to wonder why you decided to attempt the procedure in the first place! For instance, the on-line help for VB5 under HelpCommand states that "cdlHelpContents (&H3) … displays the Help contents topic as defined by the Contents option in the [OPTION] section of the *.hpj file". This would lead one to believe that using HelpCommand.cdlHelpContents is all that is necessary to instantly display the *.cnt file in the Help dialog box. Unfortunately, this doesn't work as easily as it sounds. Even if you have a *.cnt file specified in the [OPTION] section of the *.hpj file, when you use cdlHelpContents the contents topic from the help file (generally the first topic in the file) is displayed in the Help window, and the Help dialog isn't displayed at all. The Contents tab can then be reached by clicking the Contents button. This is can be rather annoying.

There is a way to display the Contents tab of the Help dialog from a Visual Basic application the way it occurs in shrink-wrapped Windows 95/98/NT applications. We'll directly access the WinHelp API, avoiding the use of the CommonDialog control altogether.

The HELP_CONTENTS constant "&H3&", with which some programmers are familiar, is intended for use in C++. This function must also be declared prior to use in that language, which will confuse some C++ programmers making the transition to Visual Basic. In Visual Basic we can use a constant directly, without having to declare it, to accomplish the desired task.

Something I fought with for some time was a basic problem with the Help dialog. Using the HELP_PARTIALKEY command I would open the Index tab of the Help dialog, find a specific topic, close help, then attempt to open the Contents tab with HELP_CONTENTS only to see the Index tab instead. Something, somewhere, was remembering which tab of the Help dialog had been selected most recently.

Directly related to the *.hlp file is a hidden *.gid file. This is a configuration file which is created the first time a help file is opened or updated. I had noticed that recently compiled help files don't have a *.gid file, but it's there after the first time the help file is opened. Also, I disovered that if the user deletes a *.gid file, WinHelp creates a new one the next time the user opens the Help file. (Yes, I did this on purpose. Sometimes taking that kind of risk is the only way to find out how something works.) According to the Microsoft Help Authoring Guide the following information is contained in the *.gid file:

  • Binary representation of the contents (*.cnt) file, including jumps and commands, after it has been processed. Only topics that were found during processing are stored.
  • The filenames and titles of all Help files included in the contents file.
  • Keywords from other Help files (if :Index statements were used in the contents file).
  • List of which files have full-text search index (*.fts) files.
  • The size and location of Help windows and dialog boxes.

Notice the last statement. This should probably read "The size and location of Help windows and dialog boxes, including which tab was last selected in the Help dialog box".

WinHelp creates a *.gid file for each Help file that does not have a contents file, or for each contents file — even if that contents file is for a family of Help files.

This presents a whole series of questions. With all of Microsoft's talk of how the registry was to replace *.ini and other configuration files, why does the *.gid file even exist in Windows 95/98? Would it make the size of the system any larger to write this information to the registry? Sure, the *.gid file is binary which may make it a bit smaller. It also makes it much more difficult to change, which may be desirable from a security standpoint. But you can open a *.cnt (contents) file in Microsoft Word and edit it directly, which susequently changes the contents of the *.gid file. So at first glance the existence of the *gid file in Windows 95/98 doesn't seem to make sense.

For an indirect answer, The Microsoft Help Authoring Guide makes these statements: "If multiple users try to build a .gid file on a network server at the same time, only the first attempt will actually create the .gid file. Other users will see an animation window until the first attempt to create a .gid file succeeds." In a client/server application, where the Help file is located on the server, we want this type of information to be in a common location, not on everyone's machine, just like the Help file itself. Writing this kind of configuration to the registry just isn't practical when you look at the bigger picture. There are numerous other examples similar to this one for other network-based applications that don't use the registry to store configuration settings. A client-accessed, server-based registry may not be too far in the future…

Fortunately there's an easy way to open directly to the Contents tab. Undocumented in the VB Help files is the HELP_TAB constant of 15 (&HF). In fact, the Microsoft Help Authoring Guide states that this constant must be included in a C++ header file since it's not included in the C++ programmer's windows.h file. In Visual Basic this constant doesn't need to be declared, we can just use it. Used with the value of 0 in the dwData position, the Contents tab pops right up in Win32:

Public Sub ShowHelpContents(ByVal intHelpFile As Integer)
  #If Win16 Then
    ' Open the help file using the
    ' 16-bit HELP_CONTENTS constant (3)
    WinHelp hWnd, SetHelpStrings(intHelpFile), 3, 0
  #Else
    ' Open the help file using the
    ' 32-bit HELP_TAB constant (15)
    WinHelp hWnd, SetHelpStrings(intHelpFile), 15, 0
  #End If
End Sub

Notice that we still use the value of 3 for fuCommand in the 16-bit section. Unfortunately, there is no equivalent for the Contents tab in a system that doesn't know what a Contents tab is. We just have to put up with the Help file's Contents topic being displayed (normally the first topic in the file). One method I've used occasionally in a system that emulates Win32 help is to provide a graphic on the contents topic for a 16-bit Help file. I then have the Contents() macro start when this topic is displayed (this must be specified in the [CONFIG] section of the Help file's *.hpj project file). The result is a kind of "splash screen", where the graphic is displayed first (I generally design it so it duplicates the image on the application's own splash screen) and then the emulated Help dialog appears, with the Contents tab showing.

The code under the Help|Contents menu selection will look like this:

Private Sub mnuContents_Click()

  ShowHelpContents 1

End Sub

The argument specifies the Help file to be called. This value is then passed to the SetHelpStrings function to return the Help file path to the ShowHelpContents subroutine. This is all it takes to display the Contents tab from anywhere in your program.

Why not use the ShowHelp method of the CommonDialog control? It's definitely not as clean. The CommonDialog control translates the HelpCommand syntax and then accesses the WinHelp API as required. Also, since we can call the same function in one line, why have the overhead of using the control? Besides, in using the WinHelp API ourselves we can have a single module to do all the work for us instead of placing the CommonDialog control on every form we want to access our Help files from.

Displaying The Help Index Tab

Displaying the Index tab of the Help dialog box is even simpler. WinHelp gives us this function through the HELP_PARTIALKEY constant (261):

Public Sub ShowHelpIndex(ByVal intHelpFile As Integer)
  ' Open the help file using the
  ' HELP_PARTIALKEY constant (261)
  WinHelpTopic hWnd, SetHelpStrings(intHelpFile), 261, ""
End Sub

Here we're using the WinHelpTopic command, passing an empty string to the subroutine as our search criteria . If we were to pass a topic title as a string, WinHelp would open the topic. Passing a word or two without giving a complete topic title will open the Index tab of the Help dialog with the word(s) in the text box.

Some 32-bit programmers may be familiar with the HELP_FINDER constant of 11 (&HB). While this command does open the Help dialog to the Index tab, almost every time I tried it a different single Swedish character would appear in the text box! Also, using the HELP_TAB command with a different number in the dwData spot always opened to the Contents tab, so this isn't a solution either. HELP_PARTIALKEY,with an empty string, works quite well.

The code under the Help|Search for help on… menu selection will look like this:

Private Sub mnuSearch_Click()

  ShowHelpIndex 1

End Sub
Displaying A Particular Help Topic

There are two other methods besides HELP_PARTIALKEY for displaying any particular help topic from a command button, highlighted text, or a dialog box. One is to use a keyword, the other is to use a Context Integer. We'll first look at a situation using a command button.

You've developed a tabbed dialog box for the computerized cash register application you're writing for your uncle's deli, complete with the touch screen. (He saw one at the bar he goes to when your aunt's out of town at the monthly quilting bee conference and he wants one just like it.) Your uncle's not what they'd call "computer literate" so the help file you've written covers every possible question.

The dialog box you've developed for the financial portion has three tabs on an SSTab control. Also, on the background of the form, between the SSTab control and the bottom of the form itself, are four buttons; Help, Cancel, Apply, and OK.

The best way to treat the Help file portion of this is to write three separate topics, one for each of the tabs on the SSTab control. The Help button then goes to the correct topic by checking which tab is selected through the Select…Case command.

The Select...Case statement for the Help button on the tabbed dialog box passes the desired Help Topic keyword to a subroutine in the Help module:

Private Sub btnHelp_Click()
  Select Case tabCheckThisOut.Tab
  Case 0
    ShowHelpTopic 1, "DailyTotals"
  Case 1
    ShowHelpTopic 1, "Register Back-up"
  Case 2
    ShowHelpTopic 1, "Inventory"
  End Select
End Sub

It wouldn't be all that difficult to have the same button call different topics from entirely separate Help files depending on which tab on the SSTab control was selected.

The subroutine in the Help module accepts the arguments from the above subroutine and then uses the arguments to open the desired topic using the HELP_KEY constant "&H101":

Public Sub ShowHelpTopic(ByVal intHelpFile As Integer, strTopic As String)
  ' Open the help file using the
  ' HELP_KEY constant (&H101)
  WinHelpTopic hWnd, SetHelpStrings(intHelpFile), &H101, strTopic
End Sub

I used the hex constant &H101 in this subroutine since its decimal equivalent apparently did nothing. Double-checking the equivalent was no help at all.

This type of function can also be helpful for the very kind of keyword sensitive help we see in Visual Basic. For instance, you can allow the user to access a help topic for a highlighted keyword in a RichTextBox by passing the string you get from the SelText property of the control to the module that calls the Help file.

One situation that doesn't use the WinHelp declaration at all is that of displaying a help topic while a message box is being displayed. Take a look at the following code for a message box:

Msg = "Please rescan the barcode."
Style = vbOKOnly + vbExclamation
Title = "Barcode Scan Error"
Help = SetHelpStrings(1)
CTxt = 1009
Response = MsgBox(Msg, Style, Title, Help, CTxt)

Providing the path for the Help file from the SetHelpStrings function, and a specific Context Integer for a given topic, enables the F1 key while the message box is displayed. This way the user can get extra help on the situation that caused the message box to be displayed.

Going directly to a topic via its Context Integer takes very little code:

Public Sub ShowHelpContextID(ByVal intHelpFile As Integer, lngContextID As Long)
  ' Open the help file using the
  ' HELP_CONTEXT constant (1)
  WinHelp hWnd, SetHelpStrings(intHelpFile), 1, lngContextID
End Sub

Calling this subroutine involves specifying the Help file to be called and the Context Integer of the topic to be displayed:

Private Sub btnHelp_Click()

  ShowHelpTopic 1, 1000

End Sub

There's one "gotcha" when dealing with Context Integers. In order for an application to find a Context Integer, there must be an "include" file generated for your Help file. Include files come from the C/C++ language, so the default name for this file is cntxmap.h. For Visual Basic, the default name is cntxmap.txt. If you so desire, the Help compiler can generate both types, but you only need one or the other. You can change the label to whatever you want in the Help compiler. Strangely enough, the option to generate this file in HelpBreeze version 2.0 is turned off by default! Other Help compilers may work the same way, so be aware of this.

If you do have the compiler generate the cntxmap.txt file, make sure you distribute the file with your application. The compiler may also allow you to change the name of this file prior to compiling (don't change it afterwards or WinHelp won't be able to find it!) Keep track of what you've renamed it to so you don't accidentally delete it some time later.

Displaying A Particular Help Topic In A Secondary Window

Another use for the Context Integer is in displaying Help topics in a secondary window. Note that this subroutine also has the ContextID passed to it:

Public Sub ShowHelpSecondary(ByVal intHelpFile As Integer, lngContextID As Long)
  ' Open the help file in a secondary
  ' window named "subMain" using the
  ' HELP_CONTEXT constant (1)
  strHelpWindow = SetHelpStrings(intHelpFile) & ">subMain"
  WinHelp hWnd, strHelpWindow, 1, lngContextID
End Sub

The secondary windows are windows you design in the Help compiler. The "Example" windows in the VB help file are examples of secondary windows. The above subroutine allows you to call such a window from within your application. Calling this procedure is the same as calling ShowHelpContextID.

Conclusion

These are just some of the things you can accomplish through the WinHelp API. It's much cleaner than using the common dialog control, and runs faster as well. Change older common dialog code to the API, and you'll be surprised at the performance difference.





Home | About | What's New | Source Code | FAQ | Tips & Tricks | Downloads | ToolBox | Tutorials | Game Programming | VB Award | Search | VB Forums | Feedback | VBNews | Copyright & Disclaimer | Advertise | Privacy Policy |

Quick searches: Site Search | Advanced Site Search 

Copyright 2002 by Exhedra Solutions, Inc.
By using this site you agree to its terms and conditions
VB Explorer and VBExplorer.com are trademarks of Exhedra Solutions, Inc.