2

我正在使用 IWebBrowser2 接口从运行时创建的 HTML 字符串呈现页面。我编写了一个方法(我们称之为 DisplayHtmlString),它采用 HTML 字符串并呈现它,如本例所示。该方法还首先使用“about:blank”调用 Navigate2,以确保存在文档,并且在调用 write 后它还会调用 close。

第一次调用DisplayHtmlString,页面总是正确渲染,即浏览器根据我传递的字符串显示HTML。问题是后续调用有时无法正常工作,而是呈现空白页面。这可能是什么原因造成的?

我发现当显示空白页时,这是导航到 about:blank 的结果。这是通过导航到本地文件来确定的,然后显示该文件(而应该显示 HTML 字符串,因为随后的写入/关闭)。所以对 Navigate2 的调用有效,而对 write 和 close 的调用有时不起作用。

我认为 IE 内部安全检查是一个可能的原因(跨域检查?),但我的直觉是这不是这里发生的事情。

在我看来,它更有可能是某种同步问题,类似于“IE 在下一次调用 DisplayHtmlString 之前尚未完成渲染”。我的代码最初没有检查浏览器的就绪状态(因为示例没有)。我添加了一个实验性的等待循环,调用 get_readyState 并观察到状态在从方法返回之前从未超过“加载” - 可能是因为渲染是异步的(?)。我还注意到,当对 DisplayHtmlString 的连续调用正常工作时,程序的主消息循环已经运行(让 Windows 有机会处理消息),而在对 DisplayHtmlString 的连续调用失败的情况下,情况并非如此。

所以我很确定我需要在这里提供正确的同步,但是如何呢?我注意到有一个名为 onreadystatechange 的方法,但还没有尝试过,因为我在黑暗中摸索时尝试了许多其他事情。这可能是解决方案,如何正确使用它?或者,我是否应该只处理 DisplayHtmlString 中的消息循环,直到就绪状态变为“完成”?

更新:向 DisplayHtmlString 添加了消息循环处理。在第一次调用(有效)中,就绪状态变为“交互式”,但没有进一步(这似乎不是问题)。在随后的调用中(当它失败时),就绪状态保持在“正在加载”,即使消息循环已被处理。

4

1 回答 1

3

您应该处理对象readystatechange上的事件。document在 JavaScript 中,它看起来像这样:

<body>
<body>Hi, this is going to be replaced!</body>
<script>
window.onload = function()
{
    document.open("text/html");
    document.onreadystatechange = function() { 
        if (document.readyState == "complete")
            alert("Done!"); 
    }
    document.write("<b>Hello again!</b>");
    document.close();   
}
</script>
</body>

要使用 C++ 或 C# 完成它,可能最简单的方法是IDispatch提供IHTMLDocument2::put_readystatechange. 然后IDispatch::Invoke(DISPID_VALUE)将在readystatechange事件发生时被回调。

如果您指定使用的语言,我可能可以帮助提供代码示例。

[编辑]您可以从这里 获取完整的示例 (C++/ATL/VS2012) 。代码通过将自定义消息发布到主窗口来异步执行此操作。这里引用太长了,下面是相关部分。

IDispatch 实现,用于 onreadystatechange 事件接收器:

class CEventSink: 
  public CComObjectRoot,
  public IDispatch
{
private:
  HWND m_hwnd;
  UINT m_message;

public:
  CEventSink()
  {
    m_hwnd = NULL;
    m_message = NULL;
  }

  BEGIN_COM_MAP(CEventSink)
    COM_INTERFACE_ENTRY(IDispatch)
  END_COM_MAP()

  // Init
  void Init(HWND hwnd, UINT message)
  {
    m_hwnd = hwnd;
    m_message = message;
  }

  // IDispatch
  STDMETHODIMP GetTypeInfoCount(UINT* pctinfo) { 
    return E_NOTIMPL; }

  STDMETHODIMP GetTypeInfo(UINT itinfo, LCID lcid, ITypeInfo** pptinfo) {
    return E_NOTIMPL; }

  STDMETHODIMP GetIDsOfNames(REFIID riid, LPOLESTR* rgszNames, UINT cNames, LCID lcid, DISPID* rgdispid) { 
    return E_NOTIMPL; }

  STDMETHODIMP Invoke(
    DISPID dispidMember, REFIID riid, LCID lcid, WORD wFlags,
    DISPPARAMS* pDispParams, VARIANT* pvarResult,
    EXCEPINFO*  pExcepInfo, UINT* puArgErr)
  {
    if ( dispidMember != NULL )
      return DISP_E_MEMBERNOTFOUND;

    // Just post a message to notify the main window
    ::PostMessage(m_hwnd, m_message, 0, 0);
    return S_OK;
  }

};

使用它:

CComObject<CEventSink>* p = NULL;
hr = CComObject<CEventSink>::CreateInstance(&p);
if ( FAILED(hr) ) 
  return 0;
p->Init(m_hWnd, WM_DOCUMENTREADYSTATECHANGE);
m_eventSink = p; // does AddRef

// ...

m_htmlDocument2->put_onreadystatechange(CComVariant(m_eventSink));

有关更多详细信息,请获取来源并查看WebOcHost.cpp. 为简洁起见,错误检查非常基本。

于 2013-08-10T10:15:14.897 回答