挂接浏览器事件

来源:互联网 发布:2017淘宝规则变更 编辑:程序博客网 时间:2024/05/21 17:36

当你决定看这篇文章的时候我已假设你具备了以下知识:①掌握了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. TheIDispatch 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。想转载的朋友当然也欢迎拉,本来就是想为大家做点事,不过请保留原文出处,这是对作者基本的礼貌吧?嘿嘿

0 0