none
请教大家一个关于IWebBrowser2判断加载完毕的问题 RRS feed

  • 问题

  • 程序是这样:
    标准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
            );

    获取IWebBrowser2
        CComQIPtr<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事件中设置的EVENT
            CComVariant 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
    2009年12月9日 5:51

答案

全部回复

  • 写了一个小程序,主要功能是根据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强制把窗体给显示出来了。
    如果是这个的话,我有一点不明白,为什么不是次次都那样……

    在此先谢谢各位了

     

    2009年11月4日 15:01
  • 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
    2009年12月9日 23:26
    版主
  • 谢谢楼上回答,但这个解决方案在程序中不是很合适,因为程序里有很多自定义的MSG,有的MSG处理比较复杂……不过我一会去尝试一下~先谢斑竹了~
    2009年12月10日 1:34
  • 看来还要麻烦一下版主了
    改后的程序是这样的,其中
             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已经被改掉了。
    而且这个程序改后在虚拟机上比在物理机上还快……很诡异……
    2009年12月10日 7:08
  • http://blog.csdn.net/swqswq8/archive/2008/07/27/2719250.aspx
    上面这个链接给出的方法比较简单,也比较管用。
    2009年12月15日 13:27
    版主
  • 先谢谢Michael Lee2,你给的网页是用MFC的,我的程序用不上,呵呵
    2009年12月16日 1:50
  • 用得上啊,这和MFC没什么关系。 你只要实现DWebBrowserEvents2接口,并保证WebBrowser控件通过QI可以查询的到。就可以收到WebBrowser控件发出来的NavigateComplete 等等一系列的消息。很简单啊,不需要MFC。
    2009年12月16日 4:15
    版主
  • 呃……如果你说的是实现DWebBrowserEvents2接口……其实我贴出来的程序就是这么做的……只不过出现了一些关于后续处理的问题……而且我用的是标准C++没有用到控件……
    2009年12月17日 0:55
  • 我说的那种方法,是我时间过的,应该没问题的。 我当时也是用ATL host 了WebBrowser ActiveX 控件来做的。

    2009年12月17日 3:14
    版主