积极答复者
请教大家一个关于IWebBrowser2判断加载完毕的问题

问题
-
程序是这样:标准C++,没有使用MFC使用IWebBrowser2进行一个HTML文件的显示,由于CSS特效很多,所以加载时间有可能长达3-4秒。为保证不会出现白屏或者之前页面的残存,需要判断是否加载完毕,如果加载完毕,则会用SetWindowPos将窗体显示出来。代码如下:建立窗体m_hWeb = CreateWindowEx(
WS_EX_TOOLWINDOW,
"AtlAxWin90",
"Shell.Explorer.2",
WS_POPUP | WS_VISIBLE ,
m_nLeft,
m_nTop,
m_nWidth,
m_nHeight,
NULL,
NULL,
NULL,
NULL
);
获取IWebBrowser2CComQIPtr<IWebBrowser2> m_pIE;lAtlRet = AtlAxGetControl(m_hWeb, &pDef);
if (lAtlRet != S_OK)
{
return FALSE;
}
m_pIE = pDef;
if (m_pIE == NULL)
{
return FALSE;
}
实现IDispatch接口interface IWebEvent : public IDispatch
{
public:
HANDLE m_hEventHandle;
IDispatch* m_IDispatch;
void setIDispatch(IDispatch* IDispatch)
{
m_IDispatch = IDispatch;
}
void setHandle(HANDLE handle)
{
m_hEventHandle = handle;
}//IDispatch
HRESULT STDMETHODCALLTYPE QueryInterface(REFIID iid, void ** ppvObject)
{
if(IsEqualIID(iid, IID_IUnknown)||
IsEqualIID(iid, IID_IDispatch)||
IsEqualIID(iid, DIID_DWebBrowserEvents2))
{
*ppvObject = this;
}
else
{
*ppvObject = NULL;
return E_NOINTERFACE;
}
return S_OK;
}ULONG STDMETHODCALLTYPE AddRef(void)
{
return 1;
}
ULONG STDMETHODCALLTYPE Release(void)
{
return 0;
}
HRESULT STDMETHODCALLTYPE GetTypeInfo(unsigned int iTInfo, LCID lcid, ITypeInfo FAR* FAR* ppTInfo)
{
if (ppTInfo == NULL)
{
return E_INVALIDARG;
}
*ppTInfo = NULL;
if(iTInfo != 0)
{
return DISP_E_BADINDEX;
}
return S_OK;
}
HRESULT STDMETHODCALLTYPE GetTypeInfoCount(unsigned int FAR* pctinfo)
{
return E_NOTIMPL;
}
HRESULT STDMETHODCALLTYPE GetIDsOfNames(REFIID riid, OLECHAR FAR* FAR* rgszNames, unsigned int cNames, LCID lcid, DISPID FAR* rgDispId)
{
return E_NOTIMPL;
}
HRESULT STDMETHODCALLTYPE Invoke(DISPID dispIdMember, REFIID riid, LCID lcid, WORD wFlags, DISPPARAMS FAR* pDispParams, VARIANT FAR* pVarResult, EXCEPINFO FAR* pExcepInfo, unsigned int FAR* puArgErr)
{
switch(dispIdMember)
{
case DISPID_DOCUMENTCOMPLETE://DocumentComplete
char buff[256];
memset(buff,0,256);
wcstombs(buff,pDispParams->rgvarg[0].pvarVal->bstrVal,256);
if (m_IDispatch == (IDispatch*)pDispParams->rgvarg[1].ppdispVal)
{
SetEvent(m_hEventHandle);
}
break;
default:
break;
}
return S_OK;
}
};
设置链接点m_hDocComplete = CreateEvent(NULL, FALSE, FALSE, NULL);
m_cWebEvent = new IWebEvent();
m_cWebEvent->QueryInterface(IID_IDispatch, (void **)&m_cWebEvent);
m_cWebEvent->setHandle(m_hDocComplete);
IConnectionPointContainer *spCPC;
hRet = m_pIE->QueryInterface(IID_IConnectionPointContainer,(void**)&spCPC);
if(FAILED(hRet))
{
MessageBox(NULL, "m_pIE->QueryInterface", "m_pIE->QueryInterface", NULL);
}IConnectionPoint *spCP;
hRet = spCPC->FindConnectionPoint(DIID_DWebBrowserEvents2,&spCP);
if(FAILED(hRet))
{
MessageBox(NULL, "spCPC->FindConnectionPoint", "spCPC->FindConnectionPoint", NULL);
}
DWORD cookie;
hRet = spCP->Advise(m_cWebEvent, &cookie);
if(FAILED(hRet))
{
MessageBox(NULL, "spCP->Advise", "spCP->Advise", NULL);
}
m_pIE->QueryInterface(IID_IDispatch,(void**)&lpWBDisp);
m_cWebEvent->setIDispatch(lpWBDisp);
等待DOCUMENTCOMPLETE事件中设置的EVENTCComVariant vUrl(chHtmlFileNameBuffer);
CComVariant vEmpty;
hRet = m_pIE->put_Visible(VARIANT_TRUE);
if (hRet != S_OK)
{
return FALSE;
}hRet = m_pIE->Navigate2(&vUrl, &vEmpty, &vEmpty, &vEmpty, &vEmpty);
if (hRet != S_OK)
{
return FALSE;
}if (g_hTopMost == m_hWeb)
{
return TRUE;
}WaitForSingleObject(m_hDocComplete, INFINITE);
现在问题是整个程序是靠主线程中MessageLoop进行控制,用GetMessage接到Message后,调用相应的处理。如果m_pIE->Navigate2和 WaitForSingleObject在主线程中调用的话,在WaitForSingleObject就开始等待DOCUMENTCOMPLETE了。 但此时MessageLoop是停止的,m_pIE->Navigate2也没有继续进行(此为推测,因为一直没有进入响应 DOCUMENTCOMPLETE的代码)如果开一个线程,将m_pIE->Navigate2和WaitForSingleObject在新线程中做,程序正常。但是多开一个线程,就有可能埋藏一些隐含问题,不知道现在使用的这些东西有没有线程安全的隐患。或者有高手知道在一个线程上即能实现等待DOCUMENTCOMPLETE而且MessageLoop也能正常进行的方法么?
之前在这里发过相关问题,如果这个帖子看不太明白,可以参照:
http://social.microsoft.com/Forums/zh-CN/visualcpluszhchs/thread/141fb967-b54e-43ea-9697-ffea5d49f8fc
答案
-
http://blogs.msdn.com/oldnewthing/archive/2005/02/17/375307.aspx
The following is signature, not part of post
Please mark the post answered your question as the answer, and mark other helpful posts as helpful.
Visual C++ MVP- 已标记为答案 VisualElevenModerator 2012年1月20日 10:29
-
http://blog.csdn.net/swqswq8/archive/2008/07/27/2719250.aspx
上面这个链接给出的方法比较简单,也比较管用。- 已标记为答案 VisualElevenModerator 2012年1月20日 10:29
全部回复
-
写了一个小程序,主要功能是根据SOCKET发来的数据(数据就是包含HTML的XML文件),进行HTML文件的显示。
程序的主要技术点:
没有用MFC
使用AtlAxWin90类建立窗体,窗体是Shell.Explorer.2
使用IWebBrowser2来实现HTML文件的加载与显示程序流程如下:
一共2个窗口A,B
一开始都是HIDE的。第一次接到SOCKET数据后优先加载到窗体A中,并SHOW窗体A。
假设窗体A是当前显示的窗体,那么窗体B就是HIDE的。从SOCKET接到数据后,将HTML加载到窗体B。加载完毕后,将窗体A给HIDE掉,并且将窗体B给SHOW出来。再从SOCKET接到数据时,A和B的操作流程相反。
这么设计貌似没有什么问题,因为加载的时候都是HIDE状态,而且都是加载完毕之后再SHOW。但是实际测试时出了一点小问题。具体现象是:
假设之前通过SOCKET传来两组数据:画面1和画面2。
这样的话就是窗体A加载过画面1,窗体B加载过画面2。
此时SOCKET传来画面3,应该是窗体A加载画面3。
但是这时,窗体A被SHOW以后,显示的却是画面1,过一小段时间以后会变成画面3。这段时间一般不长,大概根据画面3真正显示出来的时间而定。而且不知道为什么有的页面本来显示就快,有的慢,两个HTML的大小跟快慢刚好相反,怀疑是使用的CSS特效比较多。这个现象看着很恼人,但并不是次次都出现,几率一般。具体代码:
建立窗体A,B(两个窗体是用的同一个类)
m_hWeb = CreateWindow("AtlAxWin90", "Shell.Explorer.2", WS_POPUP | WS_VISIBLE, m_nLeft, m_nTop, m_nWidth,m_nHeight, NULL, NULL, NULL, NULL);
获取IWebBrowser2
AtlAxGetControl(m_hWeb, &pDef);
m_pIE = pDef;加载HTML
CComVariant vUrl(HtmlFile);
CComVariant vEmpty;
m_pIE->put_Visible(VARIANT_TRUE);
m_pIE->Navigate2(&vUrl, &vEmpty, &vEmpty, &vEmpty, &vEmpty);while (TRUE)
{
hRet = m_pIE->get_Busy(&bBusy);
if ((bBusy != VARIANT_TRUE) && (hRet == S_OK))
{
break;
}
} // 这段代码就是用来等待加载的。看上去没啥问题,但实际在里面设置计数器并输出的话。始终只执行一次。设置窗体位置
SetWindowPos(m_hWeb, 0, nLeft, nTop, nWidth, nHeight, SWP_NOZORDER | SWP_HIDEWINDOW);显示窗体
SetWindowPos(m_hWeb, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_SHOWWINDOW);隐藏窗体
SetWindowPos(m_hWeb, HWND_BOTTOM, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_HIDEWINDOW);我的个人猜测:
一、猜测是SHOW的时候m_pIE并没有完成加载,导致出现之前显示的画面。
IWebBrowser2里也有个ReadyState的属性,但是我在等待的while (TRUE)里把get_Busy换成获取ReadyState的一样不行。可能有别的好方法等真正等待加载完毕,比如NavigateComplete2或者DocumentComplete,但这两个涉及到使用IDispatch::Invoke。我在网上查到的数据都是将MFC的,在WIN32编程时该如何使用?希望大家指点。二、猜测是实际加载已经完成,但是由于CSS中特效过多,导致出现之前显示的画面,等把CSS折腾完以后才进行页面的重绘
如果真是这个猜测的话,解决方法我没想出来……总不能自己去相应WM_PAINT消息吧?感觉那样太麻烦……不知道各位有什么好方法?三、猜测是m_pIE->Navigate2强制把窗体给显示出来了。
如果是这个的话,我有一点不明白,为什么不是次次都那样……在此先谢谢各位了
- 已合并 Sheng Jiang 蒋晟Moderator 2009年12月9日 23:25
-
http://blogs.msdn.com/oldnewthing/archive/2005/02/17/375307.aspx
The following is signature, not part of post
Please mark the post answered your question as the answer, and mark other helpful posts as helpful.
Visual C++ MVP- 已标记为答案 VisualElevenModerator 2012年1月20日 10:29
-
看来还要麻烦一下版主了
改后的程序是这样的,其中
case WM_DATA_ARRIVE:
case WM_SHOW_VIEW:
case WM_SHOW_RESULT:
case WM_ERACE_NOTICE:
case WM_TIMER:
break;
的目的是保证自定义的MSG不会让程序再次进入这个方法。
while(!EndLoop)
{
switch(MsgWaitForMultipleObjects(1, &m_hDocComplete, FALSE, INFINITE, QS_ALLINPUT))
{
case WAIT_OBJECT_0:
EndLoop = true;
break;
case WAIT_OBJECT_0 + 1:
if (PeekMessage(&msg, NULL, 0, 0, PM_NOREMOVE))
{
switch(msg.message)
{
case WM_DATA_ARRIVE:
case WM_SHOW_VIEW:
case WM_SHOW_RESULT:
case WM_ERACE_NOTICE:
case WM_TIMER:
break;
case WM_QUIT:
EndLoop = true;
break;
default:
PeekMessage(&msg, NULL, 0, 0, PM_REMOVE);
if (msg.hwnd == NULL)
{
WriteTime("Debug.log", "%d Message", msg.message);
msg.hwnd = getViewControllerInstance()->getHwnd();
}
WriteTime("Debug.log", "%d Message PeekMessage", msg.message);
TranslateMessage(&msg);
DispatchMessage(&msg);
break;
}
}
break;
}
}
这样改之后,小文件没问题,可以正常切换,但是如果显示过一个复杂的HTML,然后再让程序显示A,B两个页面,即B页面使用的是大页面当时使用的窗体。这个时候A是正常的,但B就无法显示了,不过碰碰键盘或者晃晃鼠标有几率能正常显示出来。我在主线程的回调和上面的PeekMessage加了输出,获取的结果如下
1124 Message windowProc: 14:33:02.012 <---大页面开始加载
32770 Message PeekMessage: 14:33:02.012
1792 Message PeekMessage: 14:33:02.012
32770 Message PeekMessage: 14:33:02.102
32770 Message PeekMessage: 14:33:06.048
1125 Message windowProc: 14:33:06.048
1126 Message windowProc: 14:33:06.048 <---大页面显示完毕
1124 Message windowProc: 14:33:09.423 <---页面A开始加载
32770 Message PeekMessage: 14:33:09.433
1792 Message PeekMessage: 14:33:09.433
32770 Message PeekMessage: 14:33:09.443
32770 Message PeekMessage: 14:33:09.533
1125 Message windowProc: 14:33:09.533
1126 Message windowProc: 14:33:09.533 <---页面A显示完毕
136 Message windowProc: 14:33:09.533
133 Message windowProc: 14:33:09.533
20 Message windowProc: 14:33:09.533
15 Message windowProc: 14:33:09.543
1124 Message windowProc: 14:33:10.685 <---页面B开始加载
32770 Message PeekMessage: 14:33:10.685
1792 Message PeekMessage: 14:33:10.695
32770 Message PeekMessage: 14:33:10.735
32770 Message PeekMessage: 14:33:11.876
512 Message PeekMessage: 14:33:11.886
32770 Message PeekMessage: 14:33:11.886
256 Message PeekMessage: 14:33:17.214
257 Message PeekMessage: 14:33:17.314
512 Message PeekMessage: 14:33:17.314
32770 Message PeekMessage: 14:33:17.314
28 Message windowProc: 14:33:17.314
136 Message windowProc: 14:33:18.015
133 Message windowProc: 14:33:18.015
20 Message windowProc: 14:33:18.015
原来正常情况下,应该是A,B的显示速度会很快,考虑到加载B时会将原来大页面的内容干掉,所以大概有1秒多的延迟,但现在来看,程序卡在了页面B的MSGWAIT那里了……不明白为什么……如果这个方案解决不了的话,请问版主,多线程版的会有什么隐患呢?string已经被改掉了。
而且这个程序改后在虚拟机上比在物理机上还快……很诡异…… -
http://blog.csdn.net/swqswq8/archive/2008/07/27/2719250.aspx
上面这个链接给出的方法比较简单,也比较管用。- 已标记为答案 VisualElevenModerator 2012年1月20日 10:29