介绍 本文描述了在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交互。 在本文中,我描述了用于创建Web样式GUI的CHtmlDialog、CHtmlScript和CHtmlCtrl类。本文附带了一个存档,包括ChtmlDialog类及其使用示例(使用前请阅读readme.txt)。 web风格GUI实现的示例 当然,web风格的GUI应用程序是第一次在微软应用程序中使用,这些应用程序是Windows操作系统的一部分。 网页界面在视窗XP帮助窗口。 网络界面在诺顿防病毒程序。 Web-GUI在Windows XP“用户帐户”对话框中实现了归纳用户界面。 使用CHtmlView类显示HTML 我们需要更改应用程序的HTML代码,使其看起来与Internet Explorer不同。 在MFC应用程序中显示HTML非常简单。您所要做的就是使用CHtmlView类。要尝试它,你必须在“MFC AppWizard (exe)”的帮助下启动一个新项目。选择«单一文档»类型并打开文档视图架构支持。在**View类向导的最后一页中,将«CHtmlView»设置为基类。接下来,将HTML页面添加到应用程序资源中。 为了使我们的程序看起来更像一个应用程序而不是一个Internet Explorer窗口,有必要改变一些在HTML页面代码: 将Windows应用程序的标准背景色设置为背景色。 禁止显示Internet Explorer上下文菜单(除了编辑框字段上方的上下文菜单)。 禁止对HTML内容进行鼠标选择。只允许选择编辑框字段中的文本。 禁止鼠标光标在静态文本上改变。在IE中,放置在文本上的光标变成了一个编辑光标“I”(在编辑框字段中),这样用户就可以从HTML页面标记和复制文本。在Windows应用程序中,文本光标通常只出现在编辑框中。 下面的HTML代码执行这些更改。由于DHTML技术,这些操作是可能的。隐藏,收缩,复制Code
… <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技术,由于它,这里的一切都是可能的。由于DOM(文档对象模型)技术,DHTML技术本身成为可能。DOM将HTML文档表示为对象[MSDN Library]。 HTML窗口事件处理 CHtmlView中缺少什么? 使用接口元素的典型脚本假定从它们接收事件(例如按钮)和放置数据(例如,放入文本字段)。特别是在我们的例子中,有必要组织HTML窗口与MFC代码的交互。这并不复杂——其思想是使用CHtmlView类[Thomas Aust]的OnBeforeNavigate2函数将事件从HTML传输到MFC代码。 在HTML(或者更具体的动态HTML)中,也存在事件的概念。DHTML中事件模型的可能性非常大,例如,可以在处理的某个步骤停止事件。 在HTML中发生事件时,可以将其转换为window. navigation (%line%)调用。MFC代码将接收这样一个事件,即带有%line%参数的OnBeforeNavigate2调用。在%line%中传输任何HTML参数是可能的,MFC代码将能够处理用户的操作。传输事件“点击确定按钮”,同时传输txtBox中的文本:复制Code
. . . <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="50" id=txtBox > <input type=BUTTON value="Ok" onClick="onBtnOk()" style="45%"> // the button has an event handler – the onBtnOk() script function . . . </BODY> </HTML>
处理事件的MFC代码:复制Code
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中。为了使它成为可能我们可以调用脚本(JscriptVBScript)在HTML和传输数据作为脚本函数的参数。这种方法的思想和实现属于[尤金·霍达科夫斯基]。从HTML中获取“脚本”对象:Hide复制Code
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代码使用参数调用特定的脚本函数:复制Code
. . . 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也是有意义的。“如何将一个从CView派生的类放在一个对话框中”的问题在[Paul DiLascia, MSDN]的文章中得到了解决。在CHtmlView中进行了更改之后,我们有了一个CHtmlCtrl类(派生自CView),可以将它放在一个对话框中。CHtmlDialog类解决了两个问题——设置对话框窗口名和窗口大小。这些参数在HTML页面上显示。CHtmlDialog类使用: 将对话框插入到资源中。将静态元素放在其中(静态元素将被HTML控件项替换)。接下来,在这个对话框(从CDialog继承)上创建一个基于MFC的类。 将头文件中的继承类更改为ChtmlDialog(例如Dlg4.h)。隐藏,从CHtmlDialog中继承类 类CDlg4:公共CHtmlDialog { / /建设 公众: 在CPP文件(例如Dlg4. CPP)的Dlg4类构造函数中添加CHtmlDialog构造函数调用。隐藏,拷贝CodeCDlg4::CDlg4(CWnd* pParent /*=NULL*/) : CHtmlDialog (CDlg4: IDD, pParent IDR_HTML4, IDC_STATIC1) // HTML页面资源传输 { / / {{AFX_DATA_INIT (CDlg4) //注意:ClassWizard会在这里添加成员初始化 / /}} AFX_DATA_INIT } ChtmlDialog类还允许从HTML更改对话框大小。(参见CHtmlDialog中的_onHtmlCmd)。 对话框窗口截图。 这里给出的对话实例取自现实生活:有必要给用户在智能卡上添加某种类型的数据块的可能性。虽然,它可以不是智能卡,而是这张卡的文件数据库。因此,这个对话框必须是合理的:显示必要的图标-一个文件或智能卡(在左上角),使用文本“键”而不是“文件”。通常,对话框必须是可适应的。但这还不是全部。 接下来,来自可用性区域的东西来了——对话框首先显示为一个简短的变体,因为用户不需要知道有多少可用的空闲空间。该程序是为管理员和开发人员开发的。例如,管理员不必每次都知道和看到空闲空间号和可以更改新数据块大小的消息。关于可用空间的信息在一开始就提供给用户,在这里主要是开发人员需要的(在开发智能卡应用程序时进行测试)。 如果智能卡上没有空闲空间,对话框将显示“问题”图标,说明文本,并将禁用“确定”按钮。接下来,对话框执行用户的工作——选择在开始时智能卡(或文档)上不存在的块。如果用户选择了一个已经存在的块,对话框将会做出反应——显示“问题”图标,说明文本,并禁用“确定”按钮。 主程序随时调用这个对话框——即使智能卡已经满了。在这种情况下,对话框会以扩展形式出现,带有“问题”图标、解释性文本和被阻止的“OK”按钮。在这里,对话框扮演信息信息的角色,否则必须显示错误消息“没有空闲空间”,取而代之的是一个已知的对话框——结果,用户的内存没有被加载。因此,这种“接口逻辑”需要大量c++代码。假设应用程序有5个这样的对话框。 一切都快完成了,但是还有两个问题: 在我们的应用程序中显示HTML的窗口(ActiveX元素)将总是有一个“按下”的边界,这是不能改变的。通过SetWindowLong(GWL_STYLE)设置的显式窗口选项不起作用。这看起来不太好。 如果用户更改HTML显示选项(Internet Explorer属性->一般标签,颜色,字体,..按钮),它将影响应用程序中的HTML外观,换句话说,字体和颜色将改变。这对任何人都不合适。应用程序用户可能在下次启动时无法识别它。 如果第一个问题可以容忍,第二个问题就很严重了。当应用程序根据用户的IE设置看起来不同时,这种情况可能会发生。 这是一个HTML对话框的样子,如果用户改变显示设置在IE选项。为了解决这个问题,有必要提供高级托管接口支持。高级托管接口(AHI)是web浏览器的ActiveX元素拥有的COM接口(从4.0版本开始)。它们有助于完全控制网络浏览器元素[MSDN库]。AHI在应用程序中的实现和这个接口的优点在[Ethan Akhgari]的例子中得到了很好的展示。 这里描述的问题由OnGetHostInfo和OnGetOptionKeyPath事件重新定义解决(请参阅html_host_handler .cpp)。我就讲到这里。谢谢大家。 使用 请查看源文件归档中的项目。 修订历史 2003年12月,首次公开发布在俄罗斯开发网站。 2004年7月,翻译成英文。 本文转载于:http://www.diyabc.com/frontweb/news11974.html