正如vc ++ 6.0的演示程序mfcie所示,用应用程序向导创建一个具有web导航能力的mfc应用可谓轻而易举。本文在此基础上,进一步讨论两个问题:资源自包含的实现,上下文菜单以及快捷键的完全控制。
一、资源自包含及res协议
所谓的资源自包含,指的是在最终产品中html文档(包括相关资源如脚本、图片)成为执行文件的一部分,就象其它资源(如应用程序图标)一样,而不是以独立文件形式分发。
资源自包含不仅使产品分发更为方便,而且在最终用户面前隐藏了更多的实现细节,具有一定的现实意义。
mfc类chtmlview不仅把webbrowser控件的所有功能(如导航,用户接口配置等)重新整理成为c++方法和mfc事件映射,还提供了许多“原创”的功能。其中之一便是从包含它的应用程序中读取web页面的能力。完成这个任务的主要方法为chtmlview::loadfromresource(),其原型如下:
bool loadfromresource( lpctstr lpszresource ); //用字符串指定资源id
bool loadfromresource( uint nres ); //用数字指定资源id
loadfromresource()依赖于res://协议(ie在找不到url指定的服务器时,所显示的错误提示页面即使用res协议引出。)res协议的url和常见的http或ftp协议所用url格式不同,它的完整语法为:
res://resource_file[/resource_type]/resource_id
其中resource_file为包含目标资源的执行文件名字。resource_type为资源类型,它可能用数字表示,也可能用字符串表示。当资源为定制或不直接支持类型时用字符串指出资源类型(如gif,jpeg等,可在.rc文件中定义);当资源为已知类型时该值往往用数字表示(如位图为2,html为23)。已知资源类型的完整列表可以参见winuser.h,它是一组rt_常量。
chtmlview::loadfromresource()总是假定目标资源所在的执行文件为当前模块。但在实际应用中,我们往往要把资源分类放在不同文件中。为解决此问题,可以定义一个派生类chtmlviewex覆盖此方法,只需要对原方法略作修改引入模块参数即可:
『清单1』
bool chtmlviewex::loadfromresource(lpctstr lpszmodule, lpctstr lpszresource)
{
cstring strresourceurl;
strresourceurl.format(_t("res://%s/%s"), lpszmodule, lpszresource);
navigate(strresourceurl, 0, 0, 0);
return true;
}
二、上下文菜单和快捷键控制
在实际应用中,为了避免用户查看页面源文件或防止用户用快捷键打开当前页面的另外一个窗口,我们往往要对由webbrowser控件提供的上下文菜单(在页面上按鼠标右键)或快捷键(如ctrl+n打开新窗口)加以定制。
无论是webbrowser控件还是chtmlview类都没有直接提供定制上述操作的方法,因而必须通过实现idochostuihandler接口来完成。在该接口中,可以实现上下文菜单和快捷键控制的方法分别为showcontextmenu()和translateaccelerator()。
由于使用mfc封装类chtmlview比直接应用webbrowser控件更为方便,因而可以考虑把定制之后的接口支持功能集成到mfc框架内。具体实现的基本思路如下:
创建一个新的ole客户站并在其中实现接口idocuihandler
在initinstance()中用一个新的管理类取代缺省配置以引入该客户站
基于以上思路,我们可以从colecontrolsite创建派生类ccustomwebbrowsersite,并在派生类中实现idochostuihandler。colecontrolsite在vc++的mfcsrcoccimpl.h中定义,用于封装控件客户站。新的客户站定义为:
『清单2』
class ccustomwebbrowsersite : public colecontrolsite
{
public:
ccustomwebbrowsersite(colecontrolcontainer *pcnt):
colecontrolsite(pcnt){}
protected:
declare_interface_map();
begin_interface_part(dochostuihandler, idochostuihandler)
stdmethodimp showcontextmenu(dword, point*, iunknown*, idispatch*);
stdmethodimp translateaccelerator(lpmsg, const guid*, dword);
stdmethodimp gethostinfo(dochostuiinfo);
stdmethodimp enablemodeless(bool);
stdmethodimp ondocwindowactivate(bool);
stdmethodimp onframewindowactivate(bool);
stdmethodimp resizeborder(lpcrect, ioleinplaceuiwindow*, bool);
stdmethodimp getoptionkeypath(lpolestr*, dword);
stdmethodimp getdroptarget(idroptarget*, idroptarget**);
stdmethodimp getexternal(idispatch**);
stdmethodimp translateurl(dword, olechar*, olechar**);
stdmethodimp filterdataobject(idataobject*, idataobject**);
stdmethodimp showui(dword, ioleinplaceactiveobject*,
iolecommandtarget*, ioleinplaceframe*, ioleinplaceuiwindow*);
stdmethodimp hideui(void);
stdmethodimp updateui(void);
end_interface_part(dochostuihandler)
};
如上所介绍,在这个接口中我们感兴趣的方法主要有showcontextmenu()和translateaccelerator()两个。以完全禁止上下文菜单显示为例,在派生类ccustomwebbrowsersite中showcontextmenu()的实现代码为:
『清单3』
stdmethodimp ccustomwebbrowsersite::xdochostuihandler::showcontextmenu(
dword, point*, iunknown*, idispatch*)
{
method_prologue(ccustomwebbrowsersite, dochostuihandler)
return s_ok; // 禁止菜单显示
}
用类似的方法可以关闭由控件直接响应的快捷键:
『清单4』
stdmethodimp customwebbrowsersite::xdochostuihandler::translateaccelerator(lpmsg lpmsg,const guid __rpc_far *pguidcmdgroup, dword ncmdid)
{
method_prologue(ccustomwebbrowsersite, dochostuihandler)
return s_ok; // 关闭快捷键
}
在idochostuihandler接口实现之后,我们还需要一个管理类ccustomoccmanager来支持ccustomwebbrowsersite,新的管理类ccustomoccmanager从coccmanager派生,coccmanager也在mfcsrcoccimpl.h中定义:
『清单5』
class ccustomoccmanager : public coccmanager
{
public:
ccustomoccmanager() {}
colecontrolsite* createsite(colecontrolcontainer* pcc)
{
ccustomwebbrowsersite *psite = new ccustomwebbrowsersite(pcc);
return psite;
}
};
到此为止,我们可以使用新的控件管理类ccustomoccmanager了。在应用程序类的initinstance()中找到afxenablecontrolcontainer()调用,将它替换为:
『清单6』
ccustomoccmanager *pmgr = new ccustomoccmanager;
afxenablecontrolcontainer(pmgr);
chtmlview::create()函数将再次调用afxenablecontrolcontainer(),这个调用使得我们原先对控件客户站的声明无效。我们可以在派生类中将chtmlview::create()代码拷贝一份,并删除对afxenablecontrolcontainer()的调用:
『清单7』
bool chtmlviewex::create(lpctstr lpszclassname, lpctstr lpszwindowname,
dword dwstyle, const rect& rect, cwnd* pparentwnd,
uint nid, ccreatecontext* pcontext)
{
m_pcreatecontext = pcontext;
if (!cview::create(lpszclassname, lpszwindowname,
dwstyle, rect, pparentwnd, nid, pcontext))
{
return false;
}
rect rectclient;
getclientrect(&rectclient);
if (!m_wndbrowser.createcontrol(clsid_webbrowser, lpszwindowname,
ws_visible | ws_child, rectclient, this, afx_idw_pane_first))
{
destroywindow();
return false;
}
lpunknown lpunk = m_wndbrowser.getcontrolunknown();
hresult hr = lpunk- >queryinterface(iid_iwebbrowser2, (void**) &m_pbrowserapp);
if (!succeeded(hr))
{
m_pbrowserapp = null;
m_wndbrowser.destroywindow();
destroywindow();
return false;
}
return true;
}
三、一个示例程序
本示例mybrowser具体演示以上讨论的技术,它显示一个包含在mybrowser.exe中的html页面,并通过idochostuihandler接口关闭控件的上下文菜单和快捷键,运行界面如下图1:
【图1】
程序在visual c++ 6.0企业版中调试通过,以下列出具体步骤:
创建mfc exe工程mybrowser,选项依次为:单文档界面(步骤1)、不需要数据库支持(步骤2)需要activex控件支持(步骤3)、不需要复合文档支持(步骤3)、步骤4采用缺省值、步骤5采用缺省值、将视图基类改为chtmlview(步骤6);
加入html文件及相关资源(如html文件“default.htm”);
加入类ccustomwebbrowsersite和ccustomoccmanager,文件为customwebbrowsersite.h和customwebbrowser.cpp;
修改mybrowser.cpp文件,在消息映射声明之前加入:
#include < ..srcoccimpl.h >
#include "customwebbrowsersite.h"
修改cmybrowserapp::initinstance(),用清单6替换afxenablecontrolcontainer();
用类向导创建chtmlview的派生类chtmlviewex,覆盖create()和loadfromresource(),文件为htmlviewex.h和htmlviewex.cpp;
修改cmybrowserview声明,在类声明之前加入#include "htmlviewex.h",使cmybrowserview从chtmlviewex继承;
修改cmybrowserview:: oninitialupdate(),在这里运行初始界面,如:
chtmlview::loadfromresource(_t("default.htm"))。
