在 MFC 应用程序中创建 Web 风格的 GUI






4.85/5 (37投票s)
2004 年 7 月 14 日
9分钟阅读

285491

11089
创建 Web 风格的 GUI 并从 MFC 代码中轻松管理它。基于 DHTML 创建对话框,从 DHTML 接收事件到 MFC,以及从 MFC 调用 JScript 函数。
引言
本文介绍了在 MFC 应用程序中创建 Web 风格 GUI 的方法。在“应用程序与 Web 风格 GUI”的公式下,我指的是用户界面或其一部分是基于 HTML 构建的。图示中有一个 Web 风格对话框的示例。
在尝试在应用程序中使用这种界面时,我遇到了以下问题
- 处理 Web 界面的事件(从 DHTML 获取事件到 MFC 代码)。
- 与 Web 元素的交互(从 MFC 代码修改 DHTML)。
为了解决这些问题,MSDN 库建议使用 DHTML COM 接口(另请参见处理 HTML 元素事件)。对我而言,这似乎是与 MFC 中 GUI 模型简单交互(事件映射和直接处理控件对象)的一个糟糕的替代方案。
本文解释了一些使 Web 界面的创建和应用程序与其的交互更加简单的工作方法。具体来说:
- 通过
OnBeforeNavigate2()
事件接收来自 Web 界面的事件。 - 通过 HTML 代码中的 HTML 脚本函数与 Web GUI 进行交互。
在本文中,我描述了 CHtmlDialog
、CHtmlScript
、CHtmlCtrl
类,用于在此文章中创建 Web 风格 GUI。包含 ChtmlDialog
类及其使用示例的存档已附加到文章中(使用前请阅读 *readme.txt*)。
Web 风格 GUI 实现示例
应用程序的 Web 风格 GUI 最初肯定是在 Microsoft 应用程序中使用的,这些应用程序是 Windows OS 的一部分。
Windows XP 帮助窗口中的 Web 界面。
Norton-AntiVirus 程序中的 Web 界面。
Web GUI 在 Windows XP“用户帐户”对话框中实现了感应式用户界面。
使用 CHtmlView 类显示 HTML
我们需要更改 HTML 代码,使应用程序看起来与 Internet Explorer 不同。
在 MFC 应用程序中显示 HTML 非常简单。您所要做的就是使用 CHtmlView
类。要尝试它,您需要使用“MFC AppWizard (exe)”启动一个新项目。选择“Single Document”类型并启用 Document View 体系结构支持。然后在 **View 类向导**的最后一页,将“CHtmlView”设置为基类。接下来,将 HTML 页面添加到应用程序资源中。
为了让我们的程序看起来更像一个应用程序而不是一个 Internet Explorer 窗口,有必要在 HTML 页面代码中做一些更改。
- 将 Windows 应用程序的标准背景颜色设置为背景颜色。
- 禁止显示 Internet Explorer 上下文菜单(除了 EditBox 字段上方的上下文菜单)。
- 禁止对 HTML 内容进行鼠标选择。仅允许在 EditBox 字段中的文本进行选择。
- 禁止鼠标光标在静态文本上更改。在 IE 中,放在文本上的光标会变成编辑光标 - “I”(如同 EditBox 字段中的光标),以便用户能够标记和复制 HTML 页面中的文本。在 Windows 应用程序中,文本光标通常仅出现在 EditBoxes 中。
以下 HTML 代码执行了这些更改。这些操作由于 DHTML 技术而成为可能。
…
<SCRIPT LANGUAGE="JScript">
// Forbid user’s mouse selecting for the content
// (allow text selection in EditBox only)
function onSelect1(){
if ( window.event.srcElement.tagName !="INPUT" ) {
window.event.returnValue = false;
window.event.cancelBubble = true;
}
}
// Forbid IE context menu
// (allow in EditBox only)
// (if the real context menu must be shown - Advanceв Hosting
// Interfaces must be used)
function onContextMenu(){
if ( window.event.srcElement.tagName !="INPUT" ) {
window.event.returnValue = false;
window.event.cancelBubble = true;
return false;
}
}
// Install Context Menu and Mark handlers on HTML loading.
//
function onLoad()
{
// forbid cursor change (except "INPUT"
// entry box and "A" hyperlink) for HTML text.
var Objs = document.all;
for (i=0; i< Objs.length; i++)
// "INPUT" entry box and "A" hyperlink
if (Objs(i).tagName!="INPUT" && Objs(i).tagName!="A")
Objs(i).style.cursor = "default";
// event handler – content selection
document.onselectstart = onSelect1;
// event handler – context menu
document.oncontextmenu = onContextMenu;
}
</SCRIPT>
<BODY onload="onLoad();"
leftmargin=0 topmargin=0 rightmargin=0 bottommargin=0
style = "background-color: buttonface;" >
// the HTML background color will be as in Windows Applications
…
这样,我们就创建了一个显示 HTML 作为其界面的应用程序。菜单、工具栏和状态栏仍然存在,但这很正常,因为我们不必放弃使用传统的控件,并以此获得一定的灵活性。
在这一步,我们遇到了 DHTML 技术,由于它,这里的一切才得以实现。DHTML 技术本身由于 DOM(文档对象模型)技术而成为可能。DOM 将 HTML 文档表示为对象 [MSDN Library]。
HTML 窗口事件处理
CHtmlView 中缺少什么?
典型的与界面元素交互的脚本假定接收来自它们的事件(例如按钮)和放置数据(例如,放入文本字段)。特别是在我们的情况下,有必要组织 HTML 窗口与 MFC 代码的交互。这并不复杂——其思想是通过 CHtmlView
类的 OnBeforeNavigate2
函数将事件从 HTML 传输到 MFC 代码 [Thomas Aust]。
在 HTML,或者更具体地说,动态 HTML 中,也存在事件的概念。DHTML 中事件模型的可能性非常大,例如,事件可以在其处理的某个阶段被阻止。
当 HTML 中发生事件时,它可以被转换为 window.navigate(%line%)
调用。MFC 代码将收到一个 OnBeforeNavigate2
调用,参数为 %line%
,作为此类事件。可以传输任何 HTML 参数到 %line%
,MFC 代码将能够处理用户的操作。传输事件“点击 OK 按钮”,同时传输 txtBox
中的文本。
.
.
.
<SCRIPT LANGUAGE="JScript">
function onBtnOk(){
var Txt = txtBox.value; // the line from TextBox
window.navigate("app:1005@" + Txt);
// "app:1005@" – this is the MFC code command prefix.
// Txt – data can be transmitted along with the event.
}
</SCRIPT>;
<BODY>
.
.
.
<input type=text style="width:50" id=txtBox >
<input type=BUTTON value="Ok" onClick="onBtnOk()" style="width:45%">
// the button has an event handler – the onBtnOk() script function
.
.
.
</BODY>
</HTML>
将处理事件的 MFC 代码
void CHtmlCtrl::OnBeforeNavigate2( LPCTSTR lpszURL, DWORD nFlags, LPCTSTR lpszTargetFrameName, CByteArray& baPostedData, LPCTSTR lpszHeaders, BOOL* pbCancel ) { const char APP_PROTOCOL[] = "app:"; int len = _tcslen(APP_PROTOCOL); if (_tcsnicmp(lpszURL, APP_PROTOCOL, len)==0) { // there is a specific Application’s reaction there. OnAppCmd(lpszURL + len); // Event cancellation, otherwise an error will occur. *pbCancel = TRUE; } CHtmlView::OnBeforeNavigate2(lpszURL, nFlags, lpszTargetFrameName, baPostedData, lpszHeaders, pbCancel); }
由于不仅 HTML,MFC 代码也可以是事件源,所以 MFC 代码必须能够将数据传输到 HTML。为了做到这一点,我们可以调用 HTML 中的脚本(Jscript/VBScript)并作为脚本函数的参数传输数据。此方法的核心思想和实现属于 [Eugene Khodakovsky]。从 HTML 获取“Script”对象。
void CHtmlCtrl::OnDocumentComplete(LPCTSTR lpszURL) { . . . HRESULT hr; hr = GetHtmlDocument()->QueryInterface(IID_IHTMLDocument, (void**) &m_pDocument); if (!SUCCEEDED(hr)) { m_pDocument= NULL; return; } IDispatch *disp; m_pDocument->get_Script( &disp); // get script object . . . }
接下来,调用具有参数的特定脚本函数的 MFC 代码
. . . CStringArray strArray; strArray.Add("Parameter 1"); strArray.Add("Parameter 2"); strArray.Add("Parameter 3"); // the call of "SetParameters" function // from the script, (passing array of strings) m_HtmlCtrl.CallJScript2("SetParameters", strArray); // inside the CallJScript2 function: // GetIDsOfNames() – get the ID number of the script function // Invoke() – call the script-function by the number . . .
HTML 中的脚本是非常强大且易于使用的工具。使用它可以编写整个程序,处理 HTML 内容并响应用户操作。DHTML 对象模型使得这种编程非常容易。
这样,与界面和用户操作交互的 MFC 代码部分可以转移到 HTML 脚本中。如果 HTML 页面与应用程序分开存放,则可以在不重新编译 EXE 文件的情况下更改界面的逻辑。
CHtmlDialog – 基于 HTML 的对话框窗口
为什么需要使用高级托管接口?
每个应用程序除了主窗口之外都有对话框。这些对话框可以具有相当复杂的用户界面。我指的是不仅仅是设计,还包括对用户操作的响应。因此,在这里使用 DHTML 也是有意义的。文章 [Paul DiLascia, MSDN] 解决了“如何将派生自 CView
的类放置在对话框上”的问题。在更改 CHtmlView
后,我们得到了一个 CHtmlCtrl
类(派生自 CView
),它可以放置在对话框上。CHtmlDialog
类解决了两个问题——设置对话框窗口名称及其大小。这些参数在 HTML 页面中指定。CHtmlDialog
类使用。
- 将对话框插入资源。在对话框上放置 STATIC 元素(STATIC 将被 HTML 控件项替换)。接下来,为该对话框创建一个基于 MFC 的类(继承自
CDialog
)。 - 在头文件中将继承类更改为
ChtmlDialog
(例如,*Dlg4.h*)。// inheriting the class from CHtmlDialog class CDlg4 : public CHtmlDialog { // Construction public:
- 在 CPP 文件中(例如,*Dlg4.cpp*)的
Dlg4
类构造函数中添加CHtmlDialog
构造函数调用。CDlg4::CDlg4(CWnd* pParent /*=NULL*/) :CHtmlDialog(CDlg4::IDD,pParent, IDR_HTML4, IDC_STATIC1) // the HTML page resource transmission { //{{AFX_DATA_INIT(CDlg4) // NOTE: the ClassWizard will add member initialization here //}}AFX_DATA_INIT }
ChtmlDialog
类还允许从 HTML 更改对话框大小。(参见 CHtmlDialog
中的 _onHtmlCmd
)。
对话框窗口截图。
此处给出的对话框示例取自实际生活:有必要使用户能够将特定类型的数据块添加到智能卡上。虽然,它可能不是智能卡,而是该卡的数据库文件。因此,此对话框必须是逻辑的:显示必要的图标 – 文档或智能卡(在左上角),使用文本“Key”而不是“Document”。总的来说,对话框必须是可适应的。但这还不是全部。
接下来是可用性方面的内容——对话框最初显示为简化版本,因为用户不需要知道有多少可用空间。该程序是为管理员和开发人员开发的。例如,管理员不必每次都知道并看到可用空间数量以及可以更改新数据块大小的消息。可用空间信息最初提供给用户,在这里主要是开发人员需要(在开发智能卡应用程序时进行测试)。
如果智能卡上没有可用空间,对话框将做出反应——显示“Question”图标、解释性文本,并禁用“OK”按钮。接下来,对话框执行用户的工作——在打开时选择卡上(或文档中)不存在的块。如果用户选择一个现有块,对话框将做出反应——显示“Question”图标、解释性文本,并禁用“OK”按钮。
主程序随时调用此对话框——即使智能卡已满。在这种情况下,对话框将以扩展版本显示,带有“Question”图标、解释性文本和锁定的“OK”按钮。在这里,对话框起到了信息的作用,否则必须显示错误消息“没有剩余空间”,取而代之的是一个熟悉的对话框——结果,用户的记忆负担较小。因此,这种“界面逻辑”需要大量的 C++ 代码。想象一下,应用程序有 5 个这样的对话框。
一切都几乎完成了,但仍然存在两个问题
- 在我们的应用程序中显示 HTML 的窗口(ActiveX 控件)将始终有一个“内凹”边框,并且无法更改。通过
SetWindowLong( GWL_STYLE)
进行显式窗口选项设置不起作用。这看起来不太好。 - 如果用户更改 HTML 显示选项(Internet Explorer 属性 -> 常规选项卡、颜色、字体... 按钮),它将影响我们应用程序中的 HTML 外观,换句话说,字体和颜色将发生变化。这不适合任何人。应用程序用户在下次启动时可能根本认不出它。
如果第一个问题可以容忍,那么第二个问题非常严重。可能会出现这种情况:应用程序的外观会因用户的 IE 设置而异。
这就是如果用户更改 IE 选项中的显示设置,HTML 对话框的外观。要解决此问题,有必要提供高级托管接口支持。高级托管接口(AHI)是 WebBrowser 的 ActiveX 控件所拥有的 COM 接口(从 4.0 版本开始)。它们有助于完全控制 WebBrowser 控件 [MSDN Library]。AHI 在应用程序中的实现以及这些接口的优点在 [Ethan Akhgari] 的示例中得到了很好的展示。
这里描述的问题通过重新定义 OnGetHostInfo
和 OnGetOptionKeyPath
事件来解决(请参阅 *Html_Host_Handlers.cpp*)。我将在这里停止。感谢您的关注。
用法
请查看 源文件 存档中的项目。
修订历史
- 2003 年 12 月,首次在 Russian dev site 发布。
- 2004 年 7 月,翻译成英文。