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 stringIds allowing context sensitive help to be shown when requested. All was working on C++ controlIds mapped to HTMLHelp stringIds 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 stringIds 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:

and create a dictionary mapping strings 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.

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.dllStgOpenStorage, 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 stringIds 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:

publicvoid Load(
string sChmPath,//path to the .chm file
Stream sAliasIDH,//this is a stream object to the .h resource
// used for the mapping
// (Assembly.GetExecutingAssembly().GetManifestResourceStream(
//"WinHelpTest.XPHelpMap.h"))
string sCSStreamName,//name of the IStream in .chm
// holding the context sensitive help
string sTTStreamName//name of the IStream in .chm holding the tooltip text
);
//loads the two dictionaries from the .chm file

and:

publicvoid AttachForm(
Control ctrlBase,//( can be null - the base control is frm )control
//for the bottom of the hierarchy when constructing HTMLHelp string key
Form frm,//the form for which we want to apply the .chm help
bool putHelpButton,//should we show the Help Button in the window Title bar
IsControlUsedDlgt IsCtrlUsed, //( can be null - control is used
//for help ) supplied by the form - callback that
//can specify if control has help associated in .chm ( can exclude controls )
GetControlNameDlgt GetCtrlName,//( can be null - control is assigned
//the automatically generated HTMLHelp stringId ) supplied
//by the form - callback that can specify if control
//have HTMLHelp string name different than the automatically
//generated for the context sensitive string name
IsCSHelpDlgt IsCSHelp,//(can be null - if form default help action
//is show topic , if control default help action is context
//sensitive help ) supplied by the form - callback that can
//specify if control have context sensitive help or show topic
IsControlUsedDlgt IsCtrlTTUsed,//( can be null - control
//is used for tooltip ) supplied by the form - callback
//that can specify if control has tooltip
//associated in .chm ( can exclude controls )
GetControlNameDlgt GetCtrlTTName //( can be null - control is assigned
//the automatically generated HTMLHelp stringId for tooltip )
//supplied by the form - callback that can specify
//if control have HTMLHelp string name different
//than the automatically generated for the tooltip string name
);
//goes through all the suncontrols of the form recursively and tries
//to associate context sensitive help and tooltip with them according
//to that if control is used and what is the HTMLHelp string name of the control

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 #defines of the string ids.

Comments and Discussions

Nice article but I have some remarks/suggestions (based on borland delphi programming I did years ago on this topic).

1) You can use the Tag property of the controls to store the numbers. Then you would only have to write some reflection based routine that dumps forms and controls on it that have numeric Tag values attached into a *.h file to be compiled into the chm. It
saves a lot of typos and testing.

2) The code inside ShowContextHelp() fails on windows x64 with memory protection errors. On Windows x64 structure alignment is different and must be defined properly in order to work. Adding Pack=1 should solve this.

3) There are more commands like HH_DISPLAY_TOPIC that are usefule like: HH_DISPLAY_INDEX/HH_DISPLAY_TOPIC/HH_DISPLAY_SEARCH/HH_HELP_CONTEXT and
HH_CLOSE_ALL.

4) Help on Help (ie viewing viewhlp.chm from the workshop) is also usefull to support.

5) The import of HtmlHelp seems wrong for x64 (related to 2). You cannot cast 8 byte IntPtr to a 4 byte data type. So data, caller and return type should all (imho) be IntPtr so they adjust from 4 to 8 bytes. Unfortunatly this still does not produce a popup on x64 but it does stops the code from crashing (and return 0 as a error).

About 3, it's just that you only define a single HH_* constant. I would expect the full complement.

About 4, a decent help menu has also a help on help menuitem showing the chm i mentioned. This is not much more that a helper method that uses htmlhelp to shows that help file if present on disk/path. The file is normally part of the htmlhelp workshop and gives help on how to use htmlhelp.

I have made not much progress on the x64 stuff and did't find any useful reference yet. Important is that IntPtr and Hwnd (that is an IntPtr) are 4 bytes in x86 and 8 bytes on x64. So the import of hemlhelp and the casting cannot be correct. Changing the signature prevents crashing but no popup in x64 mode.

Btw the Help.ShowPopup Method shows a nice popup (and would be useful if you can extract the text to be shown from the htmlhelp file).

The article at http://support.microsoft.com/kb/317406 has sample code attached that just uses an overloaded import with a ref HH_POPUP parameter. I did not get that working but it seems a pretty simply way to get a IntPtr. This file also contains a full complement of constants.

You seem be living in the past...
Every modern software I recently downloaded has only plain html (mini-site) help, either downloadable or Internet based only.
Come on, google something and see for yourself!