挂接浏览器事件

系统 1364 0

前几天在《 一个基于 MFC 的自动化 (Automation) 实例 》上说最近会发一个关于如何挂接浏览器事件的教程,现在如期兑现承诺啦。说实话,解决这个问题花了我近一年的时间,虽然期间不是每天都在想这个问题,但无论如何这听起来绝对是一段不短的时间!也许因为我是生物系的吧,不能像计算机系的朋友那样有那么多现成的资源可以利用,一切都靠自学,碰到不懂的问题就在浩瀚的网络世界中寻找答案,有时候的确感到很孤独!

人生最大的痛苦莫过于有了问题没有答案,有了答案又没有 Money !毫不客气的说一句,中国在“开源”方面做的的确不咱的 ~ 一点点小的成就就希望把它转变成大把大把的金钱,结果呢,正如大家看到的,中国菜鸟的死亡率要明显高于美国(因为中国的菜鸟在成长为肉鸟的过程中需要付出太多的 ¥,嘿嘿 ~ )。

当然,我并不崇洋媚外,相反,我觉得自己更像是个“愤青”。我说中国的“开源”做的不好那是事实,没什么好争论的。而我之所以没有沦落为“崇洋派”是因为我注意到了其中的潜台词:“在当前国情下, ….. ”。现在的中国人虽然早已不像六七十年代时那么贫苦,但也不见得就衣食无忧。既如此,那么想挣点钱养家糊口的想法也就很正常拉。这就是中国开源做的不好的原因。而如果我本身就有 1000 万,我又怎么会在乎那么个小程序所带来的利润?这就是美国的开源为什么做的好的原因。有些朋友可能会反驳,美国人最近也不好过啊,金融危机弄的人心慌慌的 ~ 哈,对啊,这就是“沃尔玛”踩踏事件发生的原因。前些天还在看网友对“重庆家乐福踩踏事件”的评论呢,哎呀,说什么中国人劣根性啊,中国人爱贪小便宜的啊,那叫一个多 ~ 当然,中国人民一向勤劳、善良,而我做为一个自豪于祖国五千年灿烂文明的普通中国人自然是不会去说美国人怎么劣根性啦,我只是想揭露这样一个事实:人与人,种族与种族,国家与国家之间是没有什么本质上的区别的,全民素质的不同是基于不同的基本国情的。当把所有人都置入一个相同的环境中,其基本表现是相同的。正如这次金融危机,不就是近似的把中国人和美国人置入了一个同等的环境?那么,他们的表现有不同吗?

时刻记住这句潜台词,那么你对国家的现状将会有一个理性的解读。就拿“开源”这件事来说,人家肯免费给你那自然是人家的深明大意,而人家要收费呢那很正常,没什么可以值得批判的。如果你真的对此看不过去,那么你就应该踏踏实实的多为国家做点事,帮助国家快速发展。等国家富裕了,开源自然也就做好了。正如我上面所说,基本国情将决定全名素质。而我决定免费公开我的文章,那并不是说我有多高尚,而是因为我不在乎这么点钱(哈,是虚拟币啦),如果有一天,我混到流落街头了,大家说我还会这么伟大吗?必竟连都说人的基本需求之一是生理需求啊(说白了就是民以食为天嘛 ~ 嘿嘿)。

扯哪去都不知道了 哈哈 也不知道今天哪来这么多感慨,回归正题吧 ~ 当你决定看这篇文章的时候我已假设你具备了以下知识: ①掌握了 COM 的一些基本知识,如连接点,接收器等 ; ②具有一定的 MFC 编程经验,了解 MFC 接收器( Sink )的内部实现 ; ③了解 HTML 的基础知识 ; ④对 IE 内部接口有一定的了解 ( IWebBrowser2, IHTMLDocument2 )

本文通过一个 MFC 对话框程序实现的接收器达到挂接 IE 事件的目的。在 Visual stdio2008 IE 8.0 下测试通过。用 VC6.0 的朋友需将 Microsoft SDK 更新成 6.0

另外,推荐大家看一篇 MSDN 上的文章 ( Handling HTML Element Events ) ,虽然是英文,看起来会有点吃力,但大家一定要学会看 MSDN ,有什么人会比生产商更了解产品的内部实现呢?看完之后,你就会底气十足的说 ”I CAN DO IT!” 。别犹豫拉,我四级都考 N 次拉 ( 不知道这次有没有过, a meng~~~) ,不照样看 ~

首先给大家介绍一下程序实现的流程 : 利用 MFC 完成接收器 (CSink) 的编写 -> 获得 IWebBrowser2 接口 ->…-> 获得 IHTMLElement 接口指针 -> 调用 AfxConnectionAdvise 实现接收器与连接点的连接 -> 响应事件。

接下来一步步实现上述步骤 :

第一步: 建立一个 MFC 对话框工程,工程名为 HandleEvent ,注意选上自动化支持 -> 添加新类 CSink 使其继承自 CCmdTarget( 此基类实现了 IDispatch), 并选上自动化支持 (Automation) 。完成这些之后这个类其实就是一个 Sink 啦,当然,到目前为止它不具有任何的功能。

下面为这个接收器添加事件处理函数,分两步:

1. sink.cpp 中的 BEGIN_DISPATCH_MAP(CSink, CCmdTarget) END_DISPATCH_MAP() 之间间加入 DISP_FUNCTION_ID(CSink,"onclick",DISPID_HTMLELEMENTEVENTS2_ONCLICK,OnClick,VT_VARIANT,VTS_DISPATCH)

2. sink.h 中加入 OnClick 的实现 :

BOOL CSink::OnClick(IHTMLEventObj *pEvtObj)

{

::AfxMessageBox(L "Button clicked!" );

return TRUE;

}

注意:请在 sink.h 中包含 :mshtml.h mshtmdid.h

至此我们已经完成了一个接收器该做的所有事,下面说说这两步的内含 : 前面已经说过, CCmdTarget 类已经实现了 IDispatch 接口,当通过 AfxConnectionAdvise() 将连接点和接收器连接之后,当有事件发生时,连接点将调用 CCmdTarget CSink )的 Invoke() 函数,这是在外部。而在内部, CCmdTarget Invoke 的调用映射到 DISPATCH_MAP 上,在其上查找有无与当前事件的 DISPID 对应的处理函数。比如当发生 onclick 事件时,连接点将调用 Invoke(..,DISPID_HTMLELEMENTEVENTS2_ONCLICK,…) ,而在 CCmdTarget 内部,它会在 DISPATCH_MAP 上寻找有没有 DISPID= DISPID_HTMLELEMENTEVENTS2_ONCLICK 的处理函数。如果有则再将事件映射到 OnClick 上。

MSDN 中有这么一段供参考:

Handling Events using MFC

* The MFC CCmdTarget class implements the IDispatch interface, which means that any class derived from this class can implement an event sink. The IDispatch feature requires a call to the CCmdTarget::EnableAutomation method.

* The MFC AfxConnectionAdvise and AfxConnectionUnadvise functions find a specified connection point and then advise or unadvise appropriately.

* The DECLARE_DISPATCH_MAP, BEGIN_DISPATCH_MAP, DISP_FUNCTION, DISP_FUNCTION_ID and END_DISPATCH_MAP macros are used to map each event method to your event handler function.

* When handling events from a Microsoft ActiveX control you are hosting in your MFC applications, use the DECLARE_EVENTSINK_MAP, BEGIN_EVENTSINK_MAP, ON_EVENT and END_EVENTSINK_MAP macros.

* If you are hosting the WebBrowser Control on a dialog box, you can use the Microsoft Visual C++ ClassWizard to map events to event handlers.

* The MFC CHtmlView class hosts the WebBrowser Control and provides overridable methods for the WebBrowser Control events.

第二步: 获得 IWebBrowser2 接口。能看这篇文章的朋友不应该不熟悉这个接口吧?它的获取有多种方式,如果真的不知道,那就直接用基于 CHtmlView 的单文档程序,嘿嘿,够直接了吧 ~ 在本例中,通过一个对话框程序获得 IE 浏览器中的 IWebBrowser2 接口的,由于实现较复杂,用到了 ”oleacc.dll” ,如果讨论太多会偏离主题,因此在第三步中只将代码贴出,有兴趣的朋友可以研究一下,如有不懂,我或许可以再出一篇教程。

第三步: 通过函数 GetIHTMLElement(IWebBrowser2 *pwb2) 获得 IHTMLElement 接口指针,由于这些都是平时 IE 编程的基础这里就不多说拉:

本例中使用到的 html 文件的代码如下 :

<button>CLICK ME!</button>

哈哈,别怀疑撒,就是这么一句话 ~ 当然这是为了测试的方便,否则我就太对不起我 PHP 程序员的称号啦 ~~

以下是 GetIHTMLElement 的实现代码,注意 获取 Internet Explorer_Server 句柄 这部分是针对 IE8.0 的,如果是另外浏览器当然也可以,不过就请你自己获取吧,过程差不多啦!另外请在 HandleEventDlg.h 中包含 :atlbase.h,oleacc.h

IHTMLElement* CHandleEventDlg::GetIHTMLElementPoint()

{

// 获取 Internet Explorer_Server 句柄

HWND hIEFrame=::FindWindow("IEFrame",NULL);

HWND hFrameTab=::FindWindowEx(hIEFrame,NULL,"Frame Tab",NULL);

HWND hTabWindowClass=::FindWindowEx(hFrameTab,NULL,"TabWindowClass",NULL);

HWND hShellDocObjectView=::FindWindowEx(hTabWindowClass,NULL,"Shell DocObject View",NULL);

HWND hExplorer_Server=::FindWindowEx(hShellDocObjectView,NULL,"Internet Explorer_Server",NULL);

//CString str;

//str.Format("%d",hExplorer_Server);

//::AfxMessageBox(str);

// 获取 IHTMLDocument2 接口

CComQIPtr<IHTMLDocument2>hd2;

UINT uMsg;

uMsg=::RegisterWindowMessage(_T("WM_HTML_GETOBJECT"));

LRESULT lRes;

::SendMessageTimeout(hExplorer_Server,uMsg,0L,0L,SMTO_ABORTIFHUNG,1000,(DWORD*)&lRes);

HINSTANCE hDll=::LoadLibrary(_T("OLEACC.dll"));

LPFNOBJECTFROMLRESULT pfObjectFromLresult=(LPFNOBJECTFROMLRESULT)::GetProcAddress(hDll,"ObjectFromLresult");

pfObjectFromLresult(lRes,IID_IHTMLDocument2,0,(LPVOID*)&hd2);

CComVariant color("black");

hd2->put_bgColor(color);

::FreeLibrary(hDll);

///////////////////////////////////////////////////////////////////////////////////////

CComQIPtr<IHTMLElementCollection>hec;

LRESULT hr=NULL;

CComQIPtr<IHTMLElementCollection>pElemColl;

hd2->get_all(&pElemColl);

IDispatch* pElemDisp = NULL;

IHTMLElement* pElem = NULL;

VARIANT varID,varIdx;

varID.vt=VT_I4;

varID.lVal=0;

varIdx.vt=VT_I4;

varIdx.lVal=0;

hr = pElemColl->item(varID, varIdx, &pElemDisp);

hr = pElemDisp->QueryInterface(IID_IHTMLElement, (void**)&pElem);

return pElem;

}

第四步: 调用 AfxConnectionAdvise 实现接收器与连接点的连接。在对话框中加入一个 Button, 添加 LBUTTONDOWN 事件,我们将在这里实现两者的连接,请在 HandleEventDlg.h 中加入 CSink *m_pSink,DWORD m_dwCookie 代码如下:

void CHandleEventDlg::OnConnect()

{

m_pElem=GetIHTMLElementPoint();

m_pSink=new CSink();

LPUNKNOWN pUnkSink=m_pSink->GetIDispatch(FALSE);

if(!::AfxConnectionAdvise(m_pElem,DIID_HTMLElementEvents2,pUnkSink,FALSE,&m_dwCookie))

{

::AfxMessageBox(" 连接失败! ");

}

else

{

::AfxMessageBox(" 连接成功!请点击按钮测试! ");

}

}

第五步: OnDestroy() 进行垃圾回收:

void CHandleEventFromIEDlg::OnDestroy()

{

CDialog::OnDestroy();

// TODO: 在此处添加消息处理程序代码

LPUNKNOWN pUnkSink=m_pSink->GetIDispatch(FALSE);

if(::AfxConnectionUnadvise(pElem,DIID_HTMLElementEvents2,pUnkSink,FALSE,m_dwCookie))

{

if(m_pSink!=NULL)

{

delete m_pSink;

m_pSink=NULL;

m_dwCookie=NULL;

}

}

}

这里唠叨一下,在 AfxConnectionAdvise 内部会调用 QueryInterface(IID_IConnectionPointContainer,..) FindConnectionPoint(), 因此只需要把 m_pSink 直接传进去即可,要是在外部调用这两个函数再传进去就明显会出错拉!

哈,结束拉,现在点击测试吧 ~ 注意先把网页打开哦,为了方便没有做异常处理,如果先运行程序的话会使程序崩溃的呀 ~

最后,我会在近期把工程 文件上传 ,如果你是在别处看到此文章的,那么请回到我的博客,这里将会有下载的链接: http://blog.csdn.net/xiaodao1986/archive/2008/12/31/3672062.aspx ,也欢迎在我的博客留言, 或也可以给我发邮件 zhangwenbo_1986@163.com 。想转载的朋友当然也欢迎拉,本来就是想为大家做点事,不过请保留原文出处,这是对作者基本的礼貌吧?嘿嘿

挂接浏览器事件


更多文章、技术交流、商务合作、联系博主

微信扫码或搜索:z360901061

微信扫一扫加我为好友

QQ号联系: 360901061

您的支持是博主写作最大的动力,如果您喜欢我的文章,感觉我的文章对您有帮助,请用微信扫描下面二维码支持博主2元、5元、10元、20元等您想捐的金额吧,狠狠点击下面给点支持吧,站长非常感激您!手机微信长按不能支付解决办法:请将微信支付二维码保存到相册,切换到微信,然后点击微信右上角扫一扫功能,选择支付二维码完成支付。

【本文对您有帮助就好】

您的支持是博主写作最大的动力,如果您喜欢我的文章,感觉我的文章对您有帮助,请用微信扫描上面二维码支持博主2元、5元、10元、自定义金额等您想捐的金额吧,站长会非常 感谢您的哦!!!

发表我的评论
最新评论 总共0条评论