前幾天在《 一個(gè)基于 MFC 的自動(dòng)化 (Automation) 實(shí)例 》上說(shuō)最近會(huì)發(fā)一個(gè)關(guān)于如何掛接瀏覽器事件的教程,現(xiàn)在如期兌現(xiàn)承諾啦。說(shuō)實(shí)話,解決這個(gè)問(wèn)題花了我近一年的時(shí)間,雖然期間不是每天都在想這個(gè)問(wèn)題,但無(wú)論如何這聽起來(lái)絕對(duì)是一段不短的時(shí)間!也許因?yàn)槲沂巧锵档陌桑荒芟裼?jì)算機(jī)系的朋友那樣有那么多現(xiàn)成的資源可以利用,一切都靠自學(xué),碰到不懂的問(wèn)題就在浩瀚的網(wǎng)絡(luò)世界中尋找答案,有時(shí)候的確感到很孤獨(dú)!
人生最大的痛苦莫過(guò)于有了問(wèn)題沒(méi)有答案,有了答案又沒(méi)有 Money !毫不客氣的說(shuō)一句,中國(guó)在“開源”方面做的的確不咱的 ~ 一點(diǎn)點(diǎn)小的成就就希望把它轉(zhuǎn)變成大把大把的金錢,結(jié)果呢,正如大家看到的,中國(guó)菜鳥的死亡率要明顯高于美國(guó)(因?yàn)橹袊?guó)的菜鳥在成長(zhǎng)為肉鳥的過(guò)程中需要付出太多的 ¥,嘿嘿 ~ )。
當(dāng)然,我并不崇洋媚外,相反,我覺得自己更像是個(gè)“憤青”。我說(shuō)中國(guó)的“開源”做的不好那是事實(shí),沒(méi)什么好爭(zhēng)論的。而我之所以沒(méi)有淪落為“崇洋派”是因?yàn)槲易⒁獾搅似渲械臐撆_(tái)詞:“在當(dāng)前國(guó)情下, ….. ”。現(xiàn)在的中國(guó)人雖然早已不像六七十年代時(shí)那么貧苦,但也不見得就衣食無(wú)憂。既如此,那么想掙點(diǎn)錢養(yǎng)家糊口的想法也就很正常拉。這就是中國(guó)開源做的不好的原因。而如果我本身就有 1000 萬(wàn),我又怎么會(huì)在乎那么個(gè)小程序所帶來(lái)的利潤(rùn)?這就是美國(guó)的開源為什么做的好的原因。有些朋友可能會(huì)反駁,美國(guó)人最近也不好過(guò)啊,金融危機(jī)弄的人心慌慌的 ~ 哈,對(duì)啊,這就是“沃爾瑪”踩踏事件發(fā)生的原因。前些天還在看網(wǎng)友對(duì)“重慶家樂(lè)福踩踏事件”的評(píng)論呢,哎呀,說(shuō)什么中國(guó)人劣根性啊,中國(guó)人愛貪小便宜的啊,那叫一個(gè)多 ~ 當(dāng)然,中國(guó)人民一向勤勞、善良,而我做為一個(gè)自豪于祖國(guó)五千年?duì)N爛文明的普通中國(guó)人自然是不會(huì)去說(shuō)美國(guó)人怎么劣根性啦,我只是想揭露這樣一個(gè)事實(shí):人與人,種族與種族,國(guó)家與國(guó)家之間是沒(méi)有什么本質(zhì)上的區(qū)別的,全民素質(zhì)的不同是基于不同的基本國(guó)情的。當(dāng)把所有人都置入一個(gè)相同的環(huán)境中,其基本表現(xiàn)是相同的。正如這次金融危機(jī),不就是近似的把中國(guó)人和美國(guó)人置入了一個(gè)同等的環(huán)境?那么,他們的表現(xiàn)有不同嗎?
時(shí)刻記住這句潛臺(tái)詞,那么你對(duì)國(guó)家的現(xiàn)狀將會(huì)有一個(gè)理性的解讀。就拿“開源”這件事來(lái)說(shuō),人家肯免費(fèi)給你那自然是人家的深明大意,而人家要收費(fèi)呢那很正常,沒(méi)什么可以值得批判的。如果你真的對(duì)此看不過(guò)去,那么你就應(yīng)該踏踏實(shí)實(shí)的多為國(guó)家做點(diǎn)事,幫助國(guó)家快速發(fā)展。等國(guó)家富裕了,開源自然也就做好了。正如我上面所說(shuō),基本國(guó)情將決定全名素質(zhì)。而我決定免費(fèi)公開我的文章,那并不是說(shuō)我有多高尚,而是因?yàn)槲也辉诤踹@么點(diǎn)錢(哈,是虛擬幣啦),如果有一天,我混到流落街頭了,大家說(shuō)我還會(huì)這么偉大嗎?必竟連都說(shuō)人的基本需求之一是生理需求啊(說(shuō)白了就是民以食為天嘛 ~ 嘿嘿)。
扯哪去都不知道了 哈哈 … 也不知道今天哪來(lái)這么多感慨,回歸正題吧 ~ 當(dāng)你決定看這篇文章的時(shí)候我已假設(shè)你具備了以下知識(shí): ①掌握了 COM 的一些基本知識(shí),如連接點(diǎn),接收器等 ; ②具有一定的 MFC 編程經(jīng)驗(yàn),了解 MFC 接收器( Sink )的內(nèi)部實(shí)現(xiàn) ; ③了解 HTML 的基礎(chǔ)知識(shí) ; ④對(duì) IE 內(nèi)部接口有一定的了解 ( 如 IWebBrowser2, IHTMLDocument2 等 )
本文通過(guò)一個(gè) MFC 對(duì)話框程序?qū)崿F(xiàn)的接收器達(dá)到掛接 IE 事件的目的。在 Visual stdio2008 , IE 8.0 下測(cè)試通過(guò)。用 VC6.0 的朋友需將 Microsoft SDK 更新成 6.0 。
另外,推薦大家看一篇 MSDN 上的文章 ( Handling HTML Element Events ) ,雖然是英文,看起來(lái)會(huì)有點(diǎn)吃力,但大家一定要學(xué)會(huì)看 MSDN ,有什么人會(huì)比生產(chǎn)商更了解產(chǎn)品的內(nèi)部實(shí)現(xiàn)呢?看完之后,你就會(huì)底氣十足的說(shuō) ”I CAN DO IT!” 。別猶豫拉,我四級(jí)都考 N 次拉 ( 不知道這次有沒(méi)有過(guò), a meng~~~) ,不照樣看 ~
首先給大家介紹一下程序?qū)崿F(xiàn)的流程 : 利用 MFC 完成接收器 (CSink) 的編寫 -> 獲得 IWebBrowser2 接口 ->…-> 獲得 IHTMLElement 接口指針 -> 調(diào)用 AfxConnectionAdvise 實(shí)現(xiàn)接收器與連接點(diǎn)的連接 -> 響應(yīng)事件。
接下來(lái)一步步實(shí)現(xiàn)上述步驟 :
第一步: 建立一個(gè) MFC 對(duì)話框工程,工程名為 HandleEvent ,注意選上自動(dòng)化支持 -> 添加新類 CSink 使其繼承自 CCmdTarget( 此基類實(shí)現(xiàn)了 IDispatch), 并選上自動(dòng)化支持 (Automation) 。完成這些之后這個(gè)類其實(shí)就是一個(gè) Sink 啦,當(dāng)然,到目前為止它不具有任何的功能。
下面為這個(gè)接收器添加事件處理函數(shù),分兩步:
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 的實(shí)現(xiàn) :
BOOL CSink::OnClick(IHTMLEventObj *pEvtObj)
{
::AfxMessageBox(L "Button clicked!" );
return TRUE;
}
注意:請(qǐng)?jiān)? sink.h 中包含 :mshtml.h 和 mshtmdid.h
至此我們已經(jīng)完成了一個(gè)接收器該做的所有事,下面說(shuō)說(shuō)這兩步的內(nèi)含 : 前面已經(jīng)說(shuō)過(guò), CCmdTarget 類已經(jīng)實(shí)現(xiàn)了 IDispatch 接口,當(dāng)通過(guò) AfxConnectionAdvise() 將連接點(diǎn)和接收器連接之后,當(dāng)有事件發(fā)生時(shí),連接點(diǎn)將調(diào)用 CCmdTarget ( CSink )的 Invoke() 函數(shù),這是在外部。而在內(nèi)部, CCmdTarget 將 Invoke 的調(diào)用映射到 DISPATCH_MAP 上,在其上查找有無(wú)與當(dāng)前事件的 DISPID 對(duì)應(yīng)的處理函數(shù)。比如當(dāng)發(fā)生 onclick 事件時(shí),連接點(diǎn)將調(diào)用 Invoke(..,DISPID_HTMLELEMENTEVENTS2_ONCLICK,…) ,而在 CCmdTarget 內(nèi)部,它會(huì)在 DISPATCH_MAP 上尋找有沒(méi)有 DISPID= DISPID_HTMLELEMENTEVENTS2_ONCLICK 的處理函數(shù)。如果有則再將事件映射到 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 接口。能看這篇文章的朋友不應(yīng)該不熟悉這個(gè)接口吧?它的獲取有多種方式,如果真的不知道,那就直接用基于 CHtmlView 的單文檔程序,嘿嘿,夠直接了吧 ~ 在本例中,通過(guò)一個(gè)對(duì)話框程序獲得 IE 瀏覽器中的 IWebBrowser2 接口的,由于實(shí)現(xiàn)較復(fù)雜,用到了 ”oleacc.dll” ,如果討論太多會(huì)偏離主題,因此在第三步中只將代碼貼出,有興趣的朋友可以研究一下,如有不懂,我或許可以再出一篇教程。
第三步: 通過(guò)函數(shù) GetIHTMLElement(IWebBrowser2 *pwb2) 獲得 IHTMLElement 接口指針,由于這些都是平時(shí) IE 編程的基礎(chǔ)這里就不多說(shuō)拉:
本例中使用到的 html 文件的代碼如下 :
<button>CLICK ME!</button>
哈哈,別懷疑撒,就是這么一句話 ~ 當(dāng)然這是為了測(cè)試的方便,否則我就太對(duì)不起我 PHP 程序員的稱號(hào)啦 ~~
以下是 GetIHTMLElement 的實(shí)現(xiàn)代碼,注意 ” 獲取 Internet Explorer_Server 句柄 ” 這部分是針對(duì) IE8.0 的,如果是另外瀏覽器當(dāng)然也可以,不過(guò)就請(qǐng)你自己獲取吧,過(guò)程差不多啦!另外請(qǐng)?jiān)? 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;
}
第四步: 調(diào)用 AfxConnectionAdvise 實(shí)現(xiàn)接收器與連接點(diǎn)的連接。在對(duì)話框中加入一個(gè) Button, 添加 LBUTTONDOWN 事件,我們將在這里實(shí)現(xiàn)兩者的連接,請(qǐng)?jiān)? 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(" 連接成功!請(qǐng)點(diǎn)擊按鈕測(cè)試! ");
}
}
第五步: 在 OnDestroy() 進(jìn)行垃圾回收:
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 內(nèi)部會(huì)調(diào)用 QueryInterface(IID_IConnectionPointContainer,..) 及 FindConnectionPoint(), 因此只需要把 m_pSink 直接傳進(jìn)去即可,要是在外部調(diào)用這兩個(gè)函數(shù)再傳進(jìn)去就明顯會(huì)出錯(cuò)拉!
哈,結(jié)束拉,現(xiàn)在點(diǎn)擊測(cè)試吧 ~ 注意先把網(wǎng)頁(yè)打開哦,為了方便沒(méi)有做異常處理,如果先運(yùn)行程序的話會(huì)使程序崩潰的呀 ~
最后,我會(huì)在近期把工程 文件上傳 ,如果你是在別處看到此文章的,那么請(qǐng)回到我的博客,這里將會(huì)有下載的鏈接: http://blog.csdn.net/xiaodao1986/archive/2008/12/31/3672062.aspx ,也歡迎在我的博客留言, 或也可以給我發(fā)郵件 zhangwenbo_1986@163.com 。想轉(zhuǎn)載的朋友當(dāng)然也歡迎拉,本來(lái)就是想為大家做點(diǎn)事,不過(guò)請(qǐng)保留原文出處,這是對(duì)作者基本的禮貌吧?嘿嘿
更多文章、技術(shù)交流、商務(wù)合作、聯(lián)系博主
微信掃碼或搜索:z360901061

微信掃一掃加我為好友
QQ號(hào)聯(lián)系: 360901061
您的支持是博主寫作最大的動(dòng)力,如果您喜歡我的文章,感覺我的文章對(duì)您有幫助,請(qǐng)用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點(diǎn)擊下面給點(diǎn)支持吧,站長(zhǎng)非常感激您!手機(jī)微信長(zhǎng)按不能支付解決辦法:請(qǐng)將微信支付二維碼保存到相冊(cè),切換到微信,然后點(diǎn)擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對(duì)您有幫助就好】元
