My Experience with Dr.Explain

by Victor Wheeler
 
×
Menu
  • How to Add Context Sensitive Help to Your VB.NET WinForms Application
 

How to Add Context Sensitive Help to Your VB.NET WinForms Application

(Using Dr.Explain)

 
Adding context-sensitive help to an application is one place where Dr.Explain shines, and doing this with the current version of Dr.Explain appears to have been simplified since its original help file was written.
 
In the software development industry, in order to get context-sensitive help to work, there are several options.  One of the cleaner options (because it permits you to change topic titles and file names without damaging the connections between the program and the help file) is generating numeric "Help IDs" -- one per topic.  These are simply integers.  The the application program can then use those IDs to get HTML Help to open the help file on a chosen topic, depending on the context of the application at the time help was requested.
 
From Dr.Explain's current help file, it appears that originally you could re-generate the Help IDs, which admittedly is a dangerous practice, since if you are not the programmer of the application, will require someone else to re-incorporate a new set of Help IDs into the application.  What if you are not only not the programmer, but the application is released and will not (or cannot) be updated?  If this happens and the Help IDs have changed, then you will have broken the links between the application and the help file.  It also appears that originally there was an option to only generate Help IDs that had not already been generated, leaving unchanged all Help IDs already in place.
 
However, it also appears (from what I'm seeing) that this whole design has now been simplified in Dr.Explain to simply generate a Help ID when the Topic is created, and never change it thereafter.  And that works very nicely, because generally, an application programmer does not care what the numbers are or whether they are in sequence, as long as the application is correctly connected into the Help File.
 
And finally, Dr.Explain DOES give the user the ability to change the numbers manually if needed.  And this is, in reality, all that I would ever need, and I'd probably never need to change them, since they are generated for me.
 
For Visual Basic.NET (this can be easily applied to other languages as well):
 
 

Step 1:

 
In Dr.Explain:  Project settings > CHM export > Help ID Management:
 
[x]  Export Help ID in CHM.
[x]  Create map file during CHM export [<filename>]
 
Click the [...] browse button to specify a map file name.  Note that the file need not already exist.  If you end the file name with ".vb", Dr.Explain generates a map file with valid VB.NET syntax, that compiles directly into the application with no errors.
 
 

Step 2:

 
Re-generate CHM help file and put it in the same directory as the application EXE.  (I like to put mine directly in Visual Studio's "...\bin\Release\" folder and then tell my Setup program to get it from there when it creates the installation files.  That way I can use a single file to test with under Visual Studio, and that same file serves the installation process.  Alternatively, if it needs to be in a source directory (e.g. for source-code control purposes), a 2nd copy can be maintained in the "...\bin\Release\" and possibly "...\bin\Debug" folder for use while running the code under Visual Studio.)
 
 

Step 3:

 
In each form (window) that will provide help, add a System.Windows.Forms.HelpProvider from the Toolbox. Set its HelpNamespace property to be just the name of the help file.  (Don't use the [...] browse button because it includes the full path to the help file, and that path isn't going to be the same on the end user's computer.)  For this project, my HelpProvier's HelpNamespace property is:
 
"HM_GP_Shifter_Users_Guide.chm".
 
(Don't include the quotation marks.)
 
See the comments in the bottom of the  frmMain_OnKeyDown() function below to see the role the System.Windows.Forms.HelpProvider performs other than simply providing this string.
 
 

Step 4:

 
Right click the application's Setup project and select Add > File... and select the CHM file from wherever you have placed it.  This tells the Setup project where to copy the help file from when it builds the installation package.
 
 

Step 5:

 
Teach your application to capture the F1 key from every part of the application.
 
To make this as easy as possible, you want all your code that captures when the user presses the F1 key in one place for each form (window) that will need to interpret the "context" the user is in when he/she presses the F1 key.  This is easy.
 
Set all forms that will provide help to KeyPreview = True. You can do this in the Load() event, or set it in the designer.
 
Add a subroutine that looks like this (you will use your own help IDs -- these are provided in the MAP file you designated above):
 
Private Sub frmMain_OnKeyDown(ByVal sender As Object, ByVal e As KeyEventArgs) Handles MyBase.KeyDown
Dim luiHelpId As UInteger = 0
Dim lboolOkToContinue As Boolean = True
 
e.Handled = False
 
If e.KeyCode = Keys.F1 Then
'--------------------------------------------------------------
' Note that the HelpTopicIds.IDH_TOPIC_OVERVIEW_TAB.ToString()
' function produces a string like this:
'    "IDH_TOPIC_OVERVIEW_TAB"
' whereas luiHelpId.ToString() produces a string like this:
'    "810"
' which is what we need.  So we run the value through an
' unsigned integer first.
'--------------------------------------------------------------
If tcMainTabControl.SelectedTab Is tabOverview Then
luiHelpId = HelpTopicIds.IDH_TOPIC_OVERVIEW_TAB
ElseIf tcMainTabControl.SelectedTab Is tabInput Then
luiHelpId = HelpTopicIds.IDH_TOPIC_INPUT_TAB
ElseIf tcMainTabControl.SelectedTab Is tabGears Then
luiHelpId = HelpTopicIds.IDH_TOPIC_GEARS_TAB
ElseIf tcMainTabControl.SelectedTab Is tabShiftType Then
luiHelpId = HelpTopicIds.IDH_TOPIC_SHIFT_TYPE_TAB
ElseIf tcMainTabControl.SelectedTab Is tabShift Then
luiHelpId = HelpTopicIds.IDH_TOPIC_SHIFT_TAB
ElseIf tcMainTabControl.SelectedTab Is tabBlip Then
luiHelpId = HelpTopicIds.IDH_TOPIC_BLIP_TAB
ElseIf tcMainTabControl.SelectedTab Is tabShiftBlipCommon Then
luiHelpId = HelpTopicIds.IDH_TOPIC_SHIFT_BLIP_COMMON_TAB
ElseIf tcMainTabControl.SelectedTab Is tabOutputs Then
luiHelpId = HelpTopicIds.IDH_TOPIC_OUTPUTS_TAB
ElseIf tcMainTabControl.SelectedTab Is tabLinData Then
luiHelpId = HelpTopicIds.IDH_TOPIC_LIN_DATA_TAB
ElseIf tcMainTabControl.SelectedTab Is tabFailsafes Then
luiHelpId = HelpTopicIds.IDH_TOPIC_FAILSAFES_TAB
ElseIf tcMainTabControl.SelectedTab Is tabLogging Then
luiHelpId = HelpTopicIds.IDH_TOPIC_LOGGING_TAB
ElseIf tcMainTabControl.SelectedTab Is tabRunTimeStats Then
luiHelpId = HelpTopicIds.IDH_TOPIC_STATS_TAB
ElseIf tcMainTabControl.SelectedTab Is tabFirmware Then
luiHelpId = HelpTopicIds.IDH_TOPIC_FIRMWARE_TAB
Else
lboolOkToContinue = False
End If
 
If lboolOkToContinue Then
Help.ShowHelp(Me, hpHelpProvider.HelpNamespace, HelpNavigator.TopicId, luiHelpId.ToString())
e.Handled = True
Else
' When 'e.Handled' remains false and this function exits, the default logic takes over.
' This is where the HelpProvider object comes into play:  it launches help on the help
' file in 'hpHelpProvider.HelpNamespace' in a default manner (e.g. to title page), as
' opposed to what we do above -- launching into a particular page via the 'luiHelpId' value.
'
' Note that the class' SetHelpNavigator() and SetHelpKeyword() methods can be used to alter
' this default behavior and direct the help displayed to other places in the .CHM file.
'
' See https://docs.microsoft.com/en-us/dotnet/api/system.windows.forms.helpprovider?view=netframework-4.8
' for more information about the HelpProvider class.
End If
End If
End Sub
 
This function then catches the F1 key everywhere when that window has keyboard focus, and acts on it. The If-elseif-elseif...End If structure detects the application's "context" to decide which part of the CHM help file to display.  The HelpTopicIds.IDH_TOPIC_... values are integers.  You will likely have an entirely different way of detecting your application's "context", and I would expect this to be different for every window of every application.  (And yes, of course there are more efficient ways to do the above.  This was quick and easy to code however, and okay since execution time was not an issue.)
 
Note that the call to
 
Help.ShowHelp(...)
 
requires that Help ID integer to be converted to its string representation, or else it won't work.  Yes, this is odd, but is what is required.  Note that we just use the hpHelpProvider.HelpNamespace property instead of hard-coding the help file name.
 
 

Step 6:

 
If you want to add a Help menu to whatever forms (windows) need it, add functions like the following for each such form:
 
Private Sub mnuHelp_Contents_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) _
Handles mnuHelp_Contents.Click
 
Help.ShowHelp(Me, hpHelpProvider.HelpNamespace, HelpNavigator.TableOfContents, "")
End Sub
 
Private Sub IndexToolStripMenuItem_Click(sender As System.Object, e As System.EventArgs) _
Handles IndexToolStripMenuItem.Click
 
Help.ShowHelpIndex(Me, hpHelpProvider.HelpNamespace)
End Sub
 
Private Sub SearchToolStripMenuItem_Click(sender As System.Object, e As System.EventArgs) _
Handles SearchToolStripMenuItem.Click
 
Help.ShowHelp(Me, hpHelpProvider.HelpNamespace, HelpNavigator.Find, "")
End Sub
 
 

Step 7:

 
Test.  The F1 key should launch into your help file anywhere in the application.  Additionally, if you provided a Help menu, all its options should launch into the intended locations in the help file (e.g. Table of Contents, Index, or Search display).
 
 

Notes:

 
a. Another way you can launch into a specific help file page is like this:
 
Help.ShowHelp(Me, hpHelpProvider.HelpNamespace, _
HelpNavigator.Topic, "step_1___load_hex_file.htm")
 
b. To launch into a specific help file page AND IN ADDITION to go a specific control is done like this:
 
Help.ShowHelp(Me, hpHelpProvider.HelpNamespace, _
HelpNavigator.Topic, "step_1___load_hex_file.htm#firmare_update_status_bar")
 
Note the #anchor_name at the end.  This works in the same way as the following in HTML file ANYNAME.HTM:
 
<A NAME="coffee">Coffee</A> is an example of...
 
and then in the same page in another location:
 
An example of this is <A HREF="#coffee">coffee</A>
 
or in another HTML page:
 
An example of this is <A HREF="ANYNAME.HTM#coffee">coffee</A>
 
Note that the string used in the NAME value is an arbitrary string, but must be unique within the HTML file (help topic), is case sensitive, and cannot contain spaces.
 
The online help was created with Dr.Explain