65.9K
CodeProject 正在变化。 阅读更多。
Home

HTML 控件

starIconstarIconstarIconemptyStarIconemptyStarIcon

3.00/5 (18投票s)

2007 年 11 月 28 日

3分钟阅读

viewsIcon

65979

downloadIcon

1659

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

Screenshot - html_control.jpg

引言

本文将帮助人们构建简单但功能强大、轻量级、灵活且外观酷炫的用户界面控件。 主要思想是使用 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.htmlHtmlControl.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_KEYDOWNWM_KEYUP 消息存在一个微妙的问题。 宿主窗口会占用它们,因此 HTML 控件不会接收键盘事件。 解决方案很简单 - 在消息循环中,这些消息被重定向到 HTML 控件的窗口。

© . All rights reserved.