用MFC实现WebGUI--(CDHtmlDialog)

自从去年年底一次棘手的界面,开始研究用web做界面到现在大约1年,这一年间不是局限在实现层面,也并非一直研究这一个问题,有很多问题其实不是问题,只是自己没有想清楚或者思想没放开。对于一个界面开发人员,想必拉的对话框不少于100个,腻味不必说,光是对话框大小改变导致控件跟着变化都需要一番功夫,加上界面美观,界面的风格统一,界面的灵活多变......,头痛。在对话框里面加载位图,加载gif,超链接......,啊,没法控制了吧!在考虑远点,现在.net3.0技术已经完全打破应用和桌面的界限,我们的界面html资源完全可以放在一个web站点上,这样界面是完全动态的。

其间写过2篇这方面的文章,基于vc6实现,绕弯很大。在vc7.1、vc8里面要简单很多,主要是把几个以前为公开的类公开了,最重要的是在CWnd里面加入了一个虚函数CreateControlSite使得有机会改变控件站点以修改控件行为。在mfc类层次上,CHTMLView和CDHtmlDialog为开发者提供了创建webgui的一系列基础设施,包括事件机制、窗口行为、以及对html文档操纵接口。我们在此基础上实现webgui很简单,然而仍然困惑我很久,经理也催过我几次我一直未肯决定最终方案。在我脑袋里一直琢磨是要应用程序完全操纵html文档,还是html访问应用获取信息,其实就是它们之间的通信模式。一直到昨天我才定下方案,应用通过IWebBrowser2接口操纵html元素,html通过vbscript、javascript脚本响应本身事件,访问应用。主要是考虑通信自然畅通,而以前我一味想通过应用指令完全控制html元素,导致去解析html文档,费力不讨好。下面开始我的想法:

写一个dll,封装CDHtmlDialog,提供一个类似html容器的对话框,功能就是加载html网页,以及创建与html呼应的com组件。它本身不包含与应用功能有关代码,应用有关的部分是html页面和对于的com功能组件。这里需要对CDHtmlDialog进行了适当的改造以适合自己的目标:

首先从CDHtmlDialog派生一个类CHTMLContainerDlg,默认情况下会生成一个网页资源,这个网页是这个对话框创建时加载的,我们需要的其实是一个容器而不是一个具体的对话框,所以删除网页资源,修改对话框头文件:
enum { IDD = IDD_HTMLCONTAINERDLG, IDH = 0 };
这里把IDH修改为0,因为我们删除了网页资源。然而在对话框创建后会加载该资源,在CDHtmlDialog的OnInitDialog函数里面我们可以看到:
if (m_nHtmlResID)
        LoadFromResource(m_nHtmlResID);
    else if (m_szHtmlResID)
        LoadFromResource(m_szHtmlResID);
    else if (m_strCurrentUrl)
        Navigate(m_strCurrentUrl);
结果就是对话框一出现就会出现加载一个无效地址的页面,出现无法打开链接的页面,为了避免这个问题,需要重载OnInitDialog函数。其实就是拷贝mfc代码然后去掉上面那段代码就ok,强制不加载页面。那么为了加载指定页面,需要一个函数:
void CHTMLContainerDlg::SetHtmlAndCom(CString strURL, CString strProg)
{
    HRESULT        hr        = NOERROR;
    m_strURL = strURL;
    hr = m_spComDisp.CoCreateInstance(strProg);
    if(FAILED(hr))
    {
        TRACE(_T("Some error when create com object\n"));
    }
    SetExternalDispatch(m_spComDisp);
}
指定html的url和对应功能组件的progid,这样在网页里面可以通过脚本window.external访问该com组件。
这样就可以加载html网页,但是html页面里面的元素风格却是2k风格(至少在ie7以下版本是如此),这个怕是没起到一点美观作用,为之我考虑了半天,问过做web的人是否有办法,最终还是灵感光临,误撞上了。重载GetHostInfo函数:
STDMETHODIMP CHTMLContainerDlg::GetHostInfo(DOCHOSTUIINFO* pInfo)
{
    pInfo->dwFlags = DOCHOSTUIFLAG_THEME;
    return S_OK;
}
这个多得不说,^_^。
下面就可以演示了,在vs2005里面找个向导来show一下:
CHTMLContainerDlg    dlg;
    TCHAR                szPath[MAX_PATH] 
=   0 } ;
    CString                strPath;
    GetCurrentDirectory(MAX_PATH, szPath);
    strPath 
=  szPath;
    strPath 
+=  _T( " \\Default.htm " );
    dlg.SetHtmlAndCom(strPath, _T(
" TestWebCom.WebComCtrl " ));
    dlg.DoModal();


对话框标题其实可以通过解析html文档获取title标题设置,目前还未处理。下面看看html与应用交互的组件。
生成一个atl工程,TestWebCom,添加一个com组件WebComCtrl,添加方法处理上面那个带...的按钮(文件夹浏览按钮):
STDMETHODIMP CWebComCtrl::ShowFolderBrowser( void )
{
    AFX_MANAGE_STATE(AfxGetStaticModuleState());

    
// TODO: 在此添加实现代码
    AfxMessageBox(_T("In Com, you can show folder select dialog"));
    
return S_OK;
}

这里不作具体处理,只是象征性弹出一个对话框。好了,上面我们在对话框里面已经设置了com组件的progid,这里可以把html和组件关联上了,通过脚本可以访问com组件方法:
< BUTTON  CLASS ="buttonClass3Custom"  ID ="BrowseBtn"  TYPE ="BUTTON"  TITLE ="浏览头文件。"  onClick ="OnBrowseHeaderFile();" > </ BUTTON >
脚本如下:
function  OnBrowseHeaderFile()
{
    window.external.ShowFolderBrowser();
}
下面运行试一试,按下选择文件夹按钮会出现如下询问组件是否安全的对话框:

这个很恼人,用户可没有耐心忍受每次多弹出这个对话框询问组件是否安全。我开始打算将组件实现安全接口解决掉此问题,不过不知道缘何,没有成功,网上搜索一下好像说在ie7里面无效,没办法还是看mfc源码来解决问题。
CDHtmlDialog类获取external代码如下:
STDMETHODIMP CDHtmlDialog::GetExternal(IDispatch  ** ppDispatch)
{
    
if(ppDispatch == NULL)
        
return E_POINTER;
        
    
*ppDispatch = NULL;
    
if (m_spExternalDisp.p && CanAccessExternal())
    
{
        m_spExternalDisp.p
->AddRef();
        
*ppDispatch = m_spExternalDisp.p;
        
return S_OK;
    }

    
return E_NOTIMPL;
}
看到CanAccessExternal函数,肯定就是验证安全性的代码,找到函数声明,幸好是虚函数,重载直接返回TRUE:
BOOL CHTMLContainerDlg::CanAccessExternal()
{
    
// we trust all com object (haha, you can make virus)
    return TRUE;
}
有兴趣的朋友可以看下内部实现。
这下就好了,按下网页选择文件夹按钮,弹出对话框:

一套流程完备,方案个人觉得不错,各司其职,通信自然畅通,一个html配对一个com功能组件,功能组件化不仅使代码封装性好,而且可以用于多种语言。

由于此技术不用于公司开发,今整理提供 下载
ATL发展到现在,从未提供像MFC中的CDHtmlDialog一样的HTML页面布局且方便开发者使用的窗口基类,虽然也有DHtml Control可供使用,但并不能像MFC那样通过宏映射来方便地让开发者与页面元素之间进行任意的双向交互,尤其是需要响应页面元素事件的时候,ATL/WTL爱好者必须自己编写相应的代码来完成这些工作。基于这个原因,通过理解分析MFC中CDHtmlDialog类的功能和实现行为,这里完全使用ATL一样的实现机制来模仿MFC实现的功能编写了一个头文件,使ATL爱好者在无需MFC庞大的支持库的情形下实现跟CDHtmlDialog一样的功能,并且扩展了其能力。下载的压缩包中只有一个头文件 atldhtmldlg.h,头文件的开始部分是一段开发者使用示例的注释,以方便开发者容易地上手。这个文件提供了若干个类和模板类,开发者只需关注其中两个模板类:CDHtmlDialogImpl和CMultiPageDHtmlDialogImpl,第一个模板类实现WEB页面布局的对话框,第二个模板类以第一个类为基础,扩展成了在一个对话框中支持多个页面。使用方式非常简单,从上述两个模板类之一继承实现一个对话框类,然后添加相应的映射宏,实现宏映射中的方法即可构造一个完美、表现能力强、控制方便的对话框窗口,例子代码像下面这样:class CMainDlg : public CDHtmlDialogImpl{ ...... // 实现页面元素事件的处理 // 请注意响应函数原型定义:HRESULT Foo(IHTMLElement*) BEGIN_DHTML_EVENT_MAP(CMainDlg) DHTML_EVENT_ONCLICK(_T("elementid1"), OnClick) DHTML_EVENT_ONMOUSEMOVE(_T("elementid2"), OnMouseMove) DHTML_EVENT_ELEMENT(DISPID_HTMLELEMENTEVENTS_ONMOUSEOVER, _T("elementid"), OnMouseOver) // id为elementid的元素事件响应 DHTML_EVENT_CLASS(DISPID_HTMLELEMENTEVENTS_ONMOUSEOVER, _T("myclass"), OnMouseOver) // class为myclass的元素事件响应 DHTML_EVENT_TAG(DISPID_HTMLELEMENTEVENTS_ONMOUSEOVER, _T("div"), OnMouseOver) // 所有DIV元素的onmouseover事件响应 DHTML_EVENT_AXCONTROL(controlMethodDISPID, _T("objectid"), OnControlMethod) // ActiveX控件事件响应 END_DHTML_EVENT_MAP() // 实现外部DISPATCH方法,能在这里添加任意的方法供页面中的脚本调用 // 调用方式如下:window.external.about(123, "abc") // 请注意响应函数的原型定义:void Foo(VARIANT*,VARIANT*,VARIANT*) // 目前仅仅定义了三个参数,一般也足够使用了。如果脚本实际调用中只提供了2个参数,则第三个指针参数是NULL。以此类推 BEGIN_EXTERNAL_METHOD_MAP(CMainDlg) EXTERNAL_METHOD(_T("about"), OnAbout) END_EXTERNAL_METHOD_MAP() void OnAbout(VARIANT* para1, VARIANT* para2, VARIANT* para3) { // your code is here. } HRESULT OnClick(IHTMLElement *pElement) { // your code is here. return S_OK; } HRESULT OnMouseMove(IHTMLElement *pElement) { // your code is here. return S_OK; } ......}除了上面的例子,还有CMultiPageDHtmlDialogImpl中的一些映射宏,以及还有一些虚函数可供重载。对话框设计好后,调用方法也很简单,例子如下: CMainDlg dlg; dlg.m_nHtmlResID = IDR_YOUR_HTML_RESOURCE_ID; // open your resource or dlg.m_szHtmlResID = _T("C:\\yourname.html"); // open your resource or dlg.m_strCurrentUrl = _T("www.microsoft.com"); // open local html file or external URL dlg.Create(NULL); // or dlg.DoModal(NULL);好了,介绍就到这里,如果有更多疑问,请下载头文件查看源代码以及注释,本次下载的文件版本是 1.02。当前版本的一个缺憾是尚未支持DDX/DDV,原因是ATL并未提供DDX机制,下个版本将完全仿照WTL的方式实现该类的DDX/DDV。另外为了通用性以及减少依赖,代码中完全未使用CString或CAtlString,主要是MFC、ATL、WTL各自提供了自己的CString实现。版权特别声明:本软件源码完全属作者James(胡柏华)自创,作者拥有修改和变更代码特性的权利,任何团体或个人均可以自由下载并免费使用。如用于商业用途,请在所属商业软件的版权声明中加注本声明。使用者使用本源码的过程中产生的任何错误,作者并无义务提供技术支持,由此导致的任何损失,作者概不负责。2007-11-7
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值