C# WinForms 应用程序与 HTMLHelp (.chm) 完全集成 - 帮助主题、上下文相关帮助和工具提示






4.67/5 (11投票s)
使用 HTMLHelp (.chm) 在 C# Winform 应用程序中显示帮助主题、上下文相关帮助和工具提示
引言
本文将介绍如何将 HTMLHelp (.chm) 与 C# WinForm 应用程序完全集成。标准的 .NET HelpProvider
只能显示 .chm 文件中的主题或显示静态上下文帮助。在这里,我们将展示如何从 .chm 中显示上下文相关帮助和工具提示。
背景
最初 HTMLHelp
是为 C++ 应用程序设计的,因此标准 C++ 应用程序可以充分利用 .chm 文件。通过 HtmlHelp
API,它可以显示主题并自动将控件分配给 stringId
,从而在需要时显示上下文相关帮助。所有这些都通过 C++ controlId
映射到 HTMLHelp stringId
并共享 .h 文件(由 HTMLHelp
编译器和 C++ 应用程序共享以帮助映射)来实现。C# 在这方面的 .chm 文件使用非常有限。通过 HelpProvider
,你只能将主题或静态文本分配给控件以在请求帮助时显示。控件不再具有 C++ ID,无法映射到 HTMLHelp
stringId
以进行自动上下文帮助显示。为了解决上下文相关帮助的不足,我们仍然可以通过 InteropServices
和 hhctrl.ocx 使用 HtmlHelp
API。我们可以使用此函数配合 HH_DISPLAY_TEXT_POPUP
并传递从 C# 填充了必要信息的 marshaled HH_POPUP
结构。唯一的问题是它需要 idString
,即 HTMLHelp
文件中上下文帮助文本 string
的数字 ID。在 C++ 中,这些数字 ID 来自共享的 .h 文件,但这里我们没有......嗯,那就把它作为资源添加到项目中,并自己解析。我们可以“添加为链接”这个 .h 文件(它最初应该在 HTMLHelp
项目中),并将其构建操作设置为“嵌入资源”。.h 文件由如下的 defines 组成:
#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
因此,我们可以在运行时读取资源
Assembly.GetExecutingAssembly().GetManifestResourceStream("WinHelpTest.XPHelpMap.h"))
并创建一个将 string
映射到数字的字典。但仍然如何将这些数字映射到 C# 应用程序中的控件?嗯,我们可以想出一个自动递归命名约定,通过将控件及其父项在层次结构中的名称连接起来来构建 HTMLHelp
名称。
String GetControlName(Control ctrl)
{
if (ctrl.Parent == null)
{
return ctrl.Name;
}
return GetControlName(ctrl.Parent) + "." + ctrl.Name;
}
例如,如果我们有一个名为 MainForm
的 Form
并且上面有一个名为 tbKW
的控件,我们可以自动推断出与此控件匹配的 stringId
在字典中键为“MainForm.tbKW
”。因此,如果我们考虑到这一点来构建 .h 文件 defines,那么在使用 C# 时一切都会连接起来。HtmlHelp
API 在控件的 HelpRequested
事件上调用,自动用正确的 stringId
填充 HH_POPUP
,该 stringId
从 .h 字典中获取,其键为“递归命名约定控件名称” - GetControlName(control)
。
从 .chm 文件中获取工具提示是另一回事。即使是 C++ 应用程序也无法获得,所以我们必须使用一些技巧来实现。这个想法来自另一篇 CodeProject 文章 使用 C# 反编译 CHM (帮助) 文件,其中解释了如何浏览 .chm 文件的内容。基本上,包含在 HMLHelp
项目中的所有文件都编译为 .chm 文件中的 IStreams
,这是一种复合文档文件,但不是你可以用 Ole32.dll StgOpenStorage
打开的文件,而是我们需要使用未公开的接口 ITStorage
(itss.dll) 及其 StgOpenStorage
。所以,基本上,我们可以创建一个具有与上下文相关帮助的 .txt 文件相同的语法的 .txt 文件(甚至可以使用同一个文件)并将工具提示文本添加进去。然后,使用与上下文相关帮助相同的命名约定命名 stringId
,但末尾添加“.TT”以将其作为工具提示分隔开。因此,当应用程序运行时,我们可以从 .chm 存储中读取此 stream
并进行解析,然后创建 stringId
和工具提示文本的字典。然后,我们可以将 C# ToolTip
对象设置为与每个控件关联,并自动为控件名称分配文本。
Using the Code
本文附带的代码是一个 C# WinForms 项目和一个实现了 .chm 帮助交互的库文件。
还有一个 HTMLHelp
Workshop 项目,可与 C# 应用一起使用

它演示了所述的思路,并可以为你提供一个好的起点。
WinForms 应用程序通过类库 WinHtmlLib
中的 WinHelpEx
类使用 .chm 帮助文件。所以,首先,创建一个这个类的对象
WinHelpEx s_help = new WinHelpEx();
这个对象可以是 static
的,并在整个应用程序中使用,或者根据 .chm 文件的需求和结构按窗体使用。如果为 .chm 文件中的每个窗体定义单独的上下文相关 .txt 文件和工具提示 .txt 文件以及 .h 映射文件,则每个窗体都必须有一个对象。如果所有上下文相关帮助都在一个文件中,工具提示文本在一个文件中,映射 .h 文件只有一个,那么你可以只为整个项目使用一个 WinHelpEx static
对象。
WinHelpEx
有两个主要函数
public void 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
和
public void 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
请查看附件示例了解更多详情,或者直接提问。
谢谢。
修订 1
代码已更新,根据消息部分 wvd_vegt 的评论,可在 Win7 64 位上运行。
修订 2
代码已更新,以输出一个包含 string
ID 的 #define
s 的 .h 文件。
要启用它,请取消注释 Program.cs 中的这些行
//FileStream fs = new FileStream(@"defines.h", FileMode.Append, FileAccess.Write);
//WinHelpEx.AttachOutput(fs);
...
...
...
//WinHelpEx.ReleaseOutput();
在此情况下,您无需调用 load
函数 s_help.Load( ...... );