| By chenglee 发表于 2006-1-12 17:20:00 | |
前言 由于本人在开发中经常要在程序中嵌入浏览器,为了符合自己的需求经常要对浏览器进行扩展和定制, 解决这些问题需在网上找资料和学习的过程,我想可能很多开发者或许会遇到同样的问题,特写此文,以供大家参考。 在MFC中使用浏览器 在MFC中微软为我们提供了CHtmlView、CDHtmlDialog类让我们的程序很方便的嵌入浏览器和进行浏览器的二次开发,这比直 接使用WebBrowser控件要方便很多,所以本文中讨论的浏览器的问题都是针对CHtmlView来讨论的。文中将提到一个类CLhpHtmlView, 它是CHtmlView的派生类,文中提及的扩展或定制都将在CLhpHtmlView类(或派生类)上实现。 怎样扩展或定制浏览器 浏览器定义了一些扩展接口(如IDocHostUIHandler可以定制浏览器界面有关的行为),以便开发者进行定制和扩展。浏览 器会在需要的时候向他的控制站点查询这些接口,在控制站点里实现相应的接口就可以进行相应的扩展。在MFC7.01类 库中,CHtmlView使用的控制站点是CHtmlControlSite的,在CHtmlControlSite类中 只实现了接口IDocHostUIHandler, 而要实现更多的扩展接口,必须用自定义的控制站类来取代CHtmlControlSite,在下文中提及的类CDocHostSite即为自定义 的控制站类。 如何使自定义的控制站点来替换默认的控制站点呢?在MFC7.0中只需重载CHtmlView的虚函数CreateControlSite即可: // 创建自己的控制站点实例 return (*ppSite) ? TRUE : FALSE;} VC6.0要替换控制站要复杂的多,这里就不讨论了。 定制鼠标右键弹出出菜单 要定制浏览器的鼠标右键弹出菜单,必须在自定义的控制站点类中实现IDocHostUIHandler2接口,并且IE的 版本是5.5或以上。在接口IDocHostUIHandler2的ShowContextMenu方法中调用浏览器类的OnShowContextMenu虚函数,我们 在浏览器类的派生类重载此虚函数即可实现右键菜单的定制,参见代码 HRESULT CDocHostSite::XDocHostUIHandler::ShowContextMenu(DWORD dwID, HRESULT CLhpHtmlView::OnShowContextMenu(DWORD dwID, switch(m_ContextMenuMode) return result; enum CONTEXT_MENU_MODE // 上下文菜单 通过CLhpHtmlView的函数SetContextMenuMode来设置右键菜单的类型。如果设定的右键弹出菜单是“自定义菜单”类型, 我们只要在CLhpHtmlView的派生类中重载OnShowCustomContextMenu虚函数即可,如下代码 CDemoView是CLhpHtmlView的派生类 HRESULT hr=0; HWND hwnd=NULL; IHTMLElementPtr pElem=NULL; IHTMLElementPtr pParentElem=NULL; _bstr_t tagID; pElem->get_id(&tagID.GetBSTR()); CMenu Menu,SubMenu; if(strTagID == "red") oleWnd->Release(); return S_OK; 在你嵌入了浏览器的工程中,如果网页的脚本中能调用C++代码,那将是一件很惬意的事情,要实现这种交互,就必须实现脚本扩展。实现脚本扩展就是在程序中实现一个IDispatch接口,通过CHtmlView类的OnGetExternal虚函数返回此接口指针,这样就可以在脚本中通过window.external.XXX(关键字window可以省略)来 引用接口暴露的方法或属性(XXX为方法或属性名)。在MFC中从CCmdTarget派生的类都可以实现自动化,而不必在MFC工程中引入繁杂的ATL。从CCmdTarget派生的类实现自动化接口的时候不要忘了在构造函数中调用EnableAutomation函数。 要使虚函数OnGetExternal发挥作用必须在 自定义的控制站点类中实现IDocHostUIHandler,在接口IDocHostUIHandler的GetExternal方法中调用浏览器类的OnGetExternal虚函数,我们在浏览器类的派生类重载OnGetExternal虚函数, 通过参数lppDispatch返回一个IDispatch指针,这样脚本中引用window.external时就是引用的返回的接口,参见代码 CLhpHtmlView::CLhpHtmlView(BOOL isview) HRESULT CLhpHtmlView::OnGetExternal(LPDISPATCH *lppDispatch) 下用具体示例来说明怎样实现脚本扩展 示例会在网页上点击一个按钮而使整个窗口发生抖动 从CLhpHtmlView派生一个类CDemoView,在类中实现IDispatch, 并通过IDispatch暴露方法WobbleWnd 文件 DemoView.h 文件 DemoView.cpp ...... // 把成员函数映射到Dispatch映射表中,暴露方法给脚本 ...... void CDemoView::WobbleWnd() 文件 Demo.htm ...... onclick="external.WobbleWnd()" ......
BOOL TestFunc(LPCSTR param1, int param2, int param3) C++代码中如何调用网页脚本中的函数 IHTMLDocument2::scripts属性表示HTML文档中所有脚本对象。使用脚本对象的IDispatch接口的GetIDsOfNames方法可以得到脚本函数的 DispID,得到DispID后,使用IDispatch的Invoke函数可以调用对应的脚本函数。CLhpHtmlView提供了方便的调用JavaScript的函数,请参考CLhpHtmlView中有关键字“JScript”的代码。
在自定义的控制站点类中实现IDocHostShowUI接口,在接口的ShowMessage方法中调用浏览器的OnShowMessage,我们重载 OnShowMessage虚函数即可定制消息框的标题,实现代码如下: // 窗口标题"Microsoft Internet Explorer"的资源标识
#define IDS_MESSAGE_BOX_TITLE 2213
HRESULT CLhpHtmlView::OnShowMessage(HWND hwnd,
LPOLESTR lpstrText,
LPOLESTR lpstrCaption,
DWORD dwType,
LPOLESTR lpstrHelpFile,
DWORD dwHelpContext,
LRESULT * plResult)
{
//载入Shdoclc.dll 和IE消息框标题字符串
HINSTANCE hinstSHDOCLC = LoadLibrary(TEXT("SHDOCLC.DLL"));
if (hinstSHDOCLC == NULL)
return S_FALSE;
CString strBuf,strCaption(lpstrCaption);
strBuf.LoadString(hinstSHDOCLC, IDS_MESSAGE_BOX_TITLE);
// 比较IE消息框标题字符串和lpstrCaption
// 如果相同,用自定义标题替换
if(strBuf==lpstrCaption)
strCaption = m_DefaultMsgBoxTitle;
// 创建自己的消息框并且显示
*plResult = MessageBox(CString(lpstrText), strCaption, dwType);
//卸载Shdoclc.dll并且返回
FreeLibrary(hinstSHDOCLC);
return S_OK;
}
从代码中可以看到通过设定m_DefaultMsgBoxTitle的值来改变消息宽的标题,修改此值是同过SetDefaultMsgBoxTitle来实现 void CLhpHtmlView::SetDefaultMsgBoxTitle(CString strTitle)
{
m_DefaultMsgBoxTitle=strTitle;
}
怎样定制、修改浏览器向Web服务器发送的HTTP请求头 在集成了WebBrowser控件的应用中,Web服务器有时可能希望客户端(浏览器)发送的HTTP请求中附带一些额外的信息或自定义的 HTTP头字段,这样就必须在浏览器中控制向Web服务器发送的HTTP请求。 下面是捕获的一个普通的用浏览器发送的HTTP请求头: CHtmlView的 函数参数lpszHeaders可以指定HTTP请求头,示例如下: 我们捕获的HTTP头如下: 怎样修改浏览器标识 在HTTP请求头中User-Agent字段表明了浏览器的版本以及操作系统的版本等信息。WEB服务器经常需要知道用户请求页面时是来自IE还是来自自己的客户端中的WebBrowser控件, 以便分开处理,而WebBrowser控件向WEB服务器发送的浏览器标识(User-Agent字段)跟用IE发送的是一样的,怎样区分自己的浏览器和IE呢? 微软没有提供现成的方法,要自己想法解决。 前面讨论的定制HTTP请求头就是为这一节准备的。 思路是这样的: 在自己的浏览器里处理每一个U页面请求,把请求头User-Agent改成自己想要的。 在CHtmlView的OnBeforeNavigate2虚函数里来修改HTTP请求是再好不过了, class NvToParam
if(!strHeaders.IsEmpty()) NvToParam* pNvTo = new NvToParam; // 发送一个自定义的导航消息,并把参数发过去 CHtmlView::OnBeforeNavigate2(lpszURL, LRESULT CDemoView::OnNvTo(WPARAM wParam, LPARAM lParam) 在OnBeforeNavigate2中如果发现没有自定义的User-Agent串,就加上这个串,并取消本次导航,再Post一个消息(一定要POST,让OnBeforeNavigate2跳出以后再进行导航 ),在消息中再次导航,再次导航时请求头已经有了自己的标识,所以能正常的导航。 在程序中使用了CHtmlView以后,我们在调整窗口大小的时候经常会看到输出窗口输出的异常警告: ReusingBrowser.exe 中的 0x77e53887 处最可能的异常: Microsoft C++ exception: COleException @ 0x0012e348 。 这是由于CHtmlView在处理WM_SIZE消息时的一点小问题引起的,采用如下代码处理WM_SIZE消息就不会有此警告了 if (::IsWindow(m_wndBrowser.m_hWnd))
有时可能有这样的需求,我们希望在资源管理器里托一个文件到浏览器而做出相应的处理,甚至是将文件拖到某一个网页元素上来做出相应的处理,而浏览器默认的处理拖放文件操作是将文件打开,但WebBrowser控件给了我们一个自己处理拖放的机会。 那就是在自定义的控制站点类中实现IDocHostUIHandler,在接口IDocHostUIHandler的GetDropTarget方法中调用 浏览器类的OnGetDropTarget虚函数。要处理网页内的拖放,必需在OnGetDropTarget函数中返回一个自己定义的IDropTarget接口指针, 所以我们自己写一个类CMyOleDropTarget从COleDropTarget类派生,并且在实现IDropTarget接口,此类的代码在这就不列出了,请下载演示 程序,参考文件MyOleDropTarget.h和MyOleDropTarget.cpp。我们看CLhpHtmlView中OnGetDropTarget的代码 LPDROPTARGET pMyDropTarget; return S_FALSE; m_DropTarget即为自定义的处理拖放的对象。这样就能通过在从CLhpHtmlView派生的类中重载OnDragEnter、OnDragOver、 OnDrop、OnDragLeave虚函数来处理拖放了。在这里顺带讲一下视图是怎样处理拖放的。 要使视图处理拖放,首先在视图里添加一个COleDropTarget(或派生类)成员变量,如CLhpHtmlView中的“CMyOleDropTarget m_DropTarget;”,再在 视图创建时调用COleDropTarget对象的ReGISter,即把视图与COleDropTarget对象关联起来,如CLhpHtmlView中的“m_DropTarget.Register(this);”,再对拖放 触发的事件进行相应的处理, OnDragEnter 把某对象拖入到视图时触发,在此检测拖入的对象是不是视图想接受的对象,如是返回“DROPEFFECT_MOVE”表示接受此对象,如 OnDragOver 被拖对象在视图上移动,同OnDragEnter一样检测拖入对象,如果要接受此对象返回“DROPEFFECT_MOVE”。 OnDrop 拖着被拖对象在视图上放开鼠标,在这里对拖入对象做出处理; OnDragLeave 拖着被拖对象离开视图。 C++的代码写好了,但事情还没完,还必须在网页里用脚本对拖放事件进行处理, 即页面里哪个元素要接受拖放对象哪个元素就要处理ondragenter、ondragover、ondrop,代码其实很简单,让事件的返回值为false即可,这样 C++的代码才有机会处理拖放事件,代码如下: 如果要使整个视图都接受拖放,则在Body元素中处理此三个事件。 注意:别忘了让工程对OLE的支持即在初始化应用程序时调用AfxOleInit()。 怎样禁止网页元素的选取 用网页做界面时多数情况下是不希望网页上的元素是能够被鼠标选中的, 要使网页元素不能被选中做法是:给浏览器的“宿主信息标记”加上DOCHOSTUIFLAG_DIALOG标记。 “宿主信息标记”用N个标记位来控制浏览器的许多性质,如: 怎样修改“宿主信息标记”? 在CDocHostSite中实现IDocHostUIHandler, 在GetHostInfo方法中调用浏览器的OnGetHostInfo虚函数,在虚函数OnGetHostInfo中便可指定“宿主信息标记”,如: HRESULT CLhpHtmlView::OnGetHostInfo(DOCHOSTUIINFO * pInfo) return S_OK; 用脚本也可实现: 在Head中加入脚本: 或者 其它 在CLhpHtmlView中还提供了几个函数, 修改网页元素的内容: 设置表单元素的值: 给表单元素设置焦点: |

浙公网安备 33010602011771号