原帖:http://blog.csdn.net/wangjia184/article/details/3684862
论坛上有人问如何在CHtmlView中判断页面加载完成。这里给出一点想法。
首先想想这个问题如果是在JS里面是如何实现的。
JS里面最简单的方式就是利用onload事件让一段JS在页面加载完成后启动。
使用onload事件的好处是,能够保证页面上的image图片都已经加载完成。
比如:
window.onload = function() { // do something } // 或者 window.attachEvent( "onload", function(){ // do something } );
这两种写法稍有不同。
对于前者,是直接替换了onload的处理句柄,也就是说,如果页面已经有onload了,那么执行这句后会导致原本的onload处理会被替换掉(当然,必须保证这句代码在onload前执行)。
而后者attachEvent,它能够为onload事件添加多个处理句柄。
插一句, 多个attachEvent上去的事件执行顺序是不确定的。 以前有人比较过attachEvent和W3C的addEventListener。
发现attachEvent上去的多个句柄的执行顺序完全不可控。这也是为什么在一些JS框架中,如Ext,引入自己的事件处理流,来实现多浏览器的一致性。所以,如果页面已经attachEvent了, 而你又attachEvent,那么结果是很难预测了,尽量避免。
前面都是废话,言归正传,如何在CHtmlView里面来实现?
在CHtmlView中,有下面2个接口函数对应上面的2种JS写法。
IHTMLWindow2::put_onload(VARIANT v); // 或者 IHTMLWindow3::attachEvent( BSTR event, IDispatch *pDisp, VARIANT_BOOL *pfResult );
看到了吧,在MSHTML中,完全有接口函数来实现JS中同样的功能。
首先看看 第一种JS写法对应的 C++ 代码
void CTestHtmlViewView::OnDocumentComplete(LPCTSTR lpszURL) { IHTMLDocument * pDoc = (IHTMLDocument *)GetHtmlDocument(); CComQIPtr<IHTMLDocument2> pDoc2(pDoc); if( pDoc2 ) { CComPtr<IHTMLWindow2> pWnd2; pDoc2->get_parentWindow(&pWnd2); if( pWnd2 ) { VARIANT vEvent = CDOMEventHandler::CreateEventHandlerVariant( &CTestHtmlViewView::OnLoad, (LONG_PTR)this); pWnd2->put_onload( vEvent ); } } CHtmlView::OnDocumentComplete(lpszURL); }
再看看 第2种JS对应的C++代码。
void CTestHtmlViewView::OnDocumentComplete(LPCTSTR lpszURL) { IHTMLDocument * pDoc = (IHTMLDocument *)GetHtmlDocument(); CComQIPtr<IHTMLDocument2> pDoc2(pDoc); if( pDoc2 ) { CComPtr<IHTMLWindow2> pWnd2; pDoc2->get_parentWindow(&pWnd2); CComQIPtr<IHTMLWindow3> pWnd3(pWnd2); if( pWnd3 ) { VARIANT_BOOL vbSuccess = VARIANT_FALSE; pWnd3->attachEvent( _bstr_t(_T("onload")) , CDOMEventHandler::CreateEventHandler( &CTestHtmlViewView::OnLoad, (LONG_PTR)this) , &vbSuccess ); } } }
具体使用哪种写法,依据你自己的实际情况而定。
细心的你一定发现了, CDOMEventHandler是什么?
CTestHtmlViewView::OnLoad是什么?
CTestHtmlViewView::OnLoad是onload事件的响应函数,它在这里作为函数指针传递,函数指针原型为:
typedef void (*PFN_DOM_EVENT_CALLBACK)(VARIANT* pVarResult, LONG_PTR lpUserData);
CDOMEventHandler是一个派生自IDispatch的类。好了,不说那么多了。那应该具体怎么做呢?
第一步,将CDOMEventHandler类加入到你的工程
#pragma once // DOMEventHandler.h //------------------------------------------------------------------------- // Created :2009-1-2 WangJia //------------------------------------------------------------------------- typedef void (*PFN_DOM_EVENT_CALLBACK)(VARIANT* pVarResult, LONG_PTR lpUserData); class CDOMEventHandler : public IDispatch { public: CDOMEventHandler(PFN_DOM_EVENT_CALLBACK pfnCallback, LONG_PTR lpUserData); ~CDOMEventHandler(void); HRESULT __stdcall QueryInterface(REFIID riid, void** ppvObject); DWORD __stdcall AddRef(); DWORD __stdcall Release(); STDMETHOD(GetTypeInfoCount)(unsigned int FAR* pctinfo) { return E_NOTIMPL; } STDMETHOD(GetTypeInfo)(unsigned int iTInfo, LCID lcid, ITypeInfo FAR* FAR* ppTInfo) { return E_NOTIMPL; } STDMETHOD(GetIDsOfNames)(REFIID riid, OLECHAR FAR* FAR* rgszNames, unsigned int cNames, LCID lcid, DISPID FAR* rgDispId) { return S_OK; } STDMETHOD(Invoke)(DISPID dispIdMember , REFIID riid , LCID lcid , WORD wFlags , DISPPARAMS* pDispParams , VARIANT* pVarResult , EXCEPINFO * pExcepInfo , UINT * puArgErr ); static LPDISPATCH CreateEventHandler(PFN_DOM_EVENT_CALLBACK pfnCallback, LONG_PTR lpUserData); static VARIANT CreateEventHandlerVariant(PFN_DOM_EVENT_CALLBACK pfnCallback, LONG_PTR lpUserData); private: PFN_DOM_EVENT_CALLBACK m_pfCallback; long m_lRefCount; LONG_PTR m_lpUserData; };
#include "StdAfx.h" #include "DOMEventHandler.h" // DOMEventHandler.cpp //------------------------------------------------------------------------- // Created :2009-1-2 WangJia //------------------------------------------------------------------------- CDOMEventHandler::CDOMEventHandler(PFN_DOM_EVENT_CALLBACK pfnCallback, LONG_PTR lpUserData) : m_lRefCount(0) , m_pfCallback(pfnCallback) , m_lpUserData(lpUserData) { } CDOMEventHandler::~CDOMEventHandler(void) { } HRESULT __stdcall CDOMEventHandler::QueryInterface(REFIID riid, void** ppvObject) { *ppvObject = NULL; if (IsEqualGUID(riid, IID_IUnknown)) *ppvObject = reinterpret_cast<void**>(this); if (IsEqualGUID(riid, IID_IDispatch)) *ppvObject = reinterpret_cast<void**>(this); if (*ppvObject) { ((IUnknown*)*ppvObject)->AddRef(); return S_OK; } else { return E_NOINTERFACE; } } DWORD __stdcall CDOMEventHandler::AddRef() { return InterlockedIncrement(&m_lRefCount); } DWORD __stdcall CDOMEventHandler::Release() { if (InterlockedDecrement(&m_lRefCount) == 0) { delete this; return 0; } return m_lRefCount; } HRESULT CDOMEventHandler::Invoke(DISPID dispIdMember, REFIID riid, LCID lcid, WORD wFlags, DISPPARAMS* pDispParams, VARIANT* pVarResult, EXCEPINFO * pExcepInfo, UINT * puArgErr) { if (dispIdMember == DISPID_VALUE) { if( m_pfCallback ) (*m_pfCallback)(pVarResult, m_lpUserData); } return S_OK; } LPDISPATCH CDOMEventHandler::CreateEventHandler(PFN_DOM_EVENT_CALLBACK pfnCallback, LONG_PTR lpUserData) { CDOMEventHandler * pHandler = new CDOMEventHandler( pfnCallback, lpUserData); return reinterpret_cast<LPDISPATCH>(pHandler); } VARIANT CDOMEventHandler::CreateEventHandlerVariant(PFN_DOM_EVENT_CALLBACK pfnCallback, LONG_PTR lpUserData) { VARIANT variant; variant.vt = VT_DISPATCH; variant.pdispVal = CDOMEventHandler::CreateEventHandler( pfnCallback, lpUserData); return variant; }
第2步,添加你自己的回调函数。这里只是举例。
class CTestHtmlViewView : public CHtmlView { static void OnLoad(VARIANT* pVarResult, LONG_PTR lpUserData); }; void CTestHtmlViewView::OnLoad(VARIANT* pVarResult, LONG_PTR lpUserData) { CTestHtmlViewView * pThis = reinterpret_cast<CTestHtmlViewView*>(lpUserData); ::MessageBox( NULL, _T("OnLoad"), NULL, MB_OK); }
第3步,根据你自己的情况,挂接onload, 代码参考上面。
基本就这样,如果你的页面有框架,iframe中的window也是能够挂接onload的哦, 反正一切都和JS的一致。JS能够怎么实现,VC就能如何实现。