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