Introduction
This article will explain how to fully integrate HTMLHelp (.chm) with C# WinForm application. The standard .NET HelpProvider
can only show topics out of .chm file or show static context help. Here, we will show how to display context sensitive help and tooltips out of the .chm as well.
Background
Originally HTMLHelp
was designed to ship with C++ applications so a standard C++ application can make full use of a .chm file. Through HtmlHelp
API, it displays topics and can automatically assign controls to stringId
s allowing context sensitive help to be shown when requested. All was working on C++ controlId
s mapped to HTMLHelp stringId
s and shared .h files used from both HTMLHelp
compiler and C++ application to help the mapping. The C# use of .chm file in that respect is very limited. Through HelpProvider
, one can assign only topics or static text to be show when help is requested for the control. The controls don't have the C++ IDs any more that can be mapped to an HTMLHelp
stringId
s for automatic context help display. To solve the lack of context sensitive help, we can still use HtmlHelp
API through InteropServices
and hhctrl.ocx. We can use this function with HH_DISPLAY_TEXT_POPUP
and passing the marshaled HH_POPUP
structure from C# filled with the necessary information. The only problem is that it needs idString
which is the numeric id of the string
for the context help text in the HTMLHelp
file. In C++, these numeric IDs were coming from the shared .h file, but here we don't have it ...... well then, add it to the project as a resource and parse it ourselves. We can "Add as Link" this .h file which originally should be in the HTMLHelp
project and set its build action as "Embedded Resource". The .h file consists of defines like:
#define ButtonHelp 1001
#define ButtonHelp.TT 1002
#define MainForm.tbKW 1003
#define MainForm.tbKW.TT 1004
#define MainForm 1005
#define MainForm.TT 1006
#define MainForm.cbHC 1007
#define MainForm.tvHF 1008
so we can read the resource at runtime:
Assembly.GetExecutingAssembly().GetManifestResourceStream("WinHelpTest.XPHelpMap.h"))
and create a dictionary mapping string
s to numbers. But then still how to map these numbers to the controls in the C# application. Well, we can come up with some automatic recursive naming convention which constructs the HTMLHelp
name from gluing the names of the control and its parent back in the hierarchy.
String GetControlName(Control ctrl)
{
if (ctrl.Parent == null)
{
return ctrl.Name;
}
return GetControlName(ctrl.Parent) + "." + ctrl.Name;
}
For example, if we have a Form
named MainForm
and control on it named tbKW
, we can automatically workout that the stringId
matching this control is in the dictionary
under key "MainForm.tbKW
" . So if we construct the .h file defines with that in mind, everything will hook up when we use it with C#. The HtmlHelp
API is called on the control's HelpRequested
event, automatically filling up HH_POPUP
with the right stringId
taken from the .h dictionary with key the "recursive naming convention control name" - GetControlName(control)
.
To get the tooltips from the .chm file is another matter. It was not available even with C++ application so we have to use some trick to do it. The idea came from another CodeProject article Decompiling CHM (help) files with C# which explains how to browse through a content of a .chm file. Basically, all the files that are included in the HMLHelp
project are compiled as IStreams
in the .chm file which is kind of Compound Document File but not one you can open with Ole32.dll StgOpenStorage
, but for which we have to use undocumented interface ITStorage
(itss.dll) and its StgOpenStorage
. So basically, we can create a .txt file with the same syntax as the .txt file for the context sensitive help (even use the same one) and add the tooltips text there. Then, name the stringId
s with the same naming convention as the context sensitive help, but adding ".TT" at the end to separate them as tooltips. So when the application runs, we can read this stream
from the .chm storage and parse it and create dictionary of stringId
and tooltip text. Then, we can set C# ToolTip
object to be associated with each control and automatically assign the text for the control name.
Using the Code
The code that comes with the article is a C# WinForms project and a Library file which implements the .chm help interactions.
There is HTMLHelp
Workshop project as well which works with the C# app:
It demonstrates the described ideas and can give you a good start.
The WinForms application makes use of the .chm help file through the class library WinHtmlLib
through class WinHelpEx
. So first of all, create an object of this class:
WinHelpEx s_help = new WinHelpEx();
This object can be static
and used throughout the whole application or per form depending on the needs and the structure of the .chm file. If you define separate context sensitive .txt file and tooltip .txt file and .h map file for every form in the .chm file has to have object per form. If all the context sensitive help is in one file and tooltip text is in one file and map .h is only one file, you can use only one WinHelpEx static
object for the whole project.
WinHelpEx
has two main functions:
public void Load(
string sChmPath, Stream sAliasIDH, string sCSStreamName, string sTTStreamName);
and:
public void AttachForm(
Control ctrlBase, Form frm, bool putHelpButton, IsControlUsedDlgt IsCtrlUsed, GetControlNameDlgt GetCtrlName, IsCSHelpDlgt IsCSHelp, IsControlUsedDlgt IsCtrlTTUsed, GetControlNameDlgt GetCtrlTTName );
Please take a look at the attached example for more details or just ask.
Thanks.
Revision 1
The code was updated to work on Win7 64 bit as per wvd_vegt remarks in the message section.
Revision 2
The code was updated to output a .h file with the #define
s of the string
ids.
To unable it, uncomment these lines in Program.cs:
...
...
...
On that occasion, you don't need to call the load
function s_help.Load( ...... );