HTML 控件





3.00/5 (18投票s)
2007 年 11 月 28 日
3分钟阅读

65979

1659
一个简单而强大的基于 HTML 的 GUI 库

引言
本文将帮助人们构建简单但功能强大、轻量级、灵活且外观酷炫的用户界面控件。 主要思想是使用 Internet Explorer 引擎来显示用户界面。 因此,可以使用 HTML、CSS 和 JavaScript 组合提供的丰富功能。 HTML 文档的 JavaScript 可以调用 C++ 函数,反之亦然。 附加的示例演示了一个简单的基于 HTML 的对话框,该对话框具有文档 JavaScript 和 C++ 后端之间的交互。
该库可以作为“黑盒”使用,不需要任何关于 Windows 编程的特定知识。 但是,为了理解细节,一些 COM 背景知识会很有帮助。
Using the Code
最简单的对话框可以通过以下代码显示
HtmlControl dlg("hello_world.html");
dlg.DoModal();
hello_world.html 是一个放置在资源中的文件,编译器将其插入到应用程序可执行文件中。 资源文件可以手动创建,例如
main.html HTML "main.html"
HtmlControl.js JS "HtmlControl.js"
此资源包含两个文件:main.html 和 HtmlControl.js。 为了简单起见,我通常给资源项名称与文件名相同。 可以使用以下方式从文档访问 HtmlControl.js
< script src="JS/HtmlControl.js"> </script>
为了从 HTML 文档 JavaScript 中调用 C++ 函数,您需要从 HtmlControl
继承一个类并声明 JavaScript 调用的处理程序。
class TestDialog : public HtmlControl
{
public:
TestDialog() : HtmlControl(L"test.html") {
CONNECT_JS_CALL_SIMPLE_HANDLER(Foo, TestDialog::OnFoo);
}
const char* OnFoo(int argc, const char* argv[]) { return "ok"; }
};
换句话说,TestDialog
声明了一个 JavaScript 调用处理程序 TestDialog::OnFoo
。 该处理程序通过 JavaScript 使用以下方式调用
var retval = external.Foo("param1", "param2", "param3", ...)
C++ 处理程序接收一个参数数组 argv
(数组大小为 argc
)。 为了简单起见,我定义了 CONNECT_JS_CALL_SIMPLE_HANDLER
宏,它将所有参数和返回值转换为字符串 (char*
)。 CONNECT_JS_CALL_HANDLER
使您可以完全控制从 JavaScript 发送的参数。
背景
现在,让我们尝试理解一些重要的实现细节。
宿主窗口和初始化
基本上,选择宿主窗口实现时没有限制。 它可以是一个简单的 Win32 窗口、基于 MFC 或 WTL 的窗口。 我决定使用 ATL 手动创建宿主窗口。 这减少了应用程序占用空间并消除了对其他库的不必要依赖。
在 HtmlControl::DoModal
中,创建一个无边框和无标题栏的窗口,并启动一个窗口消息循环。 当消息循环接收到 WM_CREATE
窗口消息时,将执行所有初始化。 在此消息中,将创建浏览器控件并将其导航到控件 HTML 页面。 此外,HtmlControl
注册为外部调度程序 (SetExternalDispatch
)。
JavaScript - C++ 通信
HTML 文档 JavaScript 和 C++ 后端之间的通信基于 COM 技术。 被调用者是一个 COM 对象,必须实现 IDispatch
接口。 此接口有用于通信的两种方法
HRESULT GetIDsOfNames(REFIID riid,
LPOLESTR *rgszNames,
UINT cNames,
LCID lcid,
DISPID *rgDispId);
HRESULT Invoke(DISPID dispIdMember,
REFIID riid,
LCID lcid,
WORD wFlags,
DISPPARAMS *pDispParams,
VARIANT *pVarResult,
EXCEPINFO *pExcepInfo,
UINT *puArgErr);
当调用者调用某个函数时,被调用者实例应返回此函数的唯一 ID。 GetIDsOfNames
执行此操作。 它接收函数名称 gszNames
(您可能已经注意到,它可以接收多个名称)并返回其 ID rgDispId
。 紧随其后,使用 dispIdMember
参数等于 GetIDsOfNames
返回的 ID 调用 Invoke
。 除了函数 ID 之外,Invoke
还接收所有参数 pDispParams
,并且可以通过 pVarResult
返回一些值。
在提供的源代码中,您可以找到这种技术的简单而灵活的实现。 此外,为了简单起见,我编写了几个宏来隐藏实现的细节。
键盘事件
WM_KEYDOWN
和 WM_KEYUP
消息存在一个微妙的问题。 宿主窗口会占用它们,因此 HTML 控件不会接收键盘事件。 解决方案很简单 - 在消息循环中,这些消息被重定向到 HTML 控件的窗口。