Webbrowser Control — отображение текста с помощью записи

Я использую интерфейс IWebBrowser2 для отображения страницы из строки HTML, созданной во время выполнения. Я написал метод (назовем его DisplayHtmlString), который принимает строку HTML и отображает ее, как показано в этот пример. Метод также сначала вызывает Navigate2 с параметром «about:blank», чтобы убедиться, что документ присутствует, а также вызывает метод close после вызова записи.

При первом вызове DisplayHtmlString страница всегда отображается правильно, т. е. браузер отображает HTML в соответствии с переданной строкой. Проблема в том, что последующие вызовы иногда работают некорректно, а вместо этого отображают пустую страницу. Что может быть причиной этого?

Я обнаружил, что когда отображается пустая страница, это результат перехода к about:blank. Это было определено путем перехода к локальному файлу, который затем отображается (в то время как вместо этого должна отображаться строка HTML из-за последующей записи/закрытия). Таким образом, вызов Navigate2 работает, а вызовы write и close иногда не работают.

Я рассматривал внутренние проверки безопасности IE как возможную причину (междоменная проверка?), но я чувствую, что это не то, что здесь происходит.

Мне кажется более вероятным, что это какая-то проблема синхронизации, например, «IE еще не закончил рендеринг до следующего вызова DisplayHtmlString». Мой код изначально не проверял состояние готовности браузера (поскольку пример этого не делает). Я добавил экспериментальный цикл ожидания с вызовом get_readyState и заметил, что состояние никогда не выходило за рамки «загрузки» перед возвратом из метода — вероятно, потому, что рендеринг асинхронный (?). Я также заметил, что когда последовательные вызовы DisplayHtmlString работают правильно, основной цикл обработки сообщений программы запущен (что дает Windows возможность обрабатывать сообщения), чего нельзя сказать о сценарии, в котором последовательные вызовы DisplayHtmlString завершаются неудачно.

Так что я почти уверен, что мне нужно обеспечить правильную синхронизацию здесь, но как? Я заметил, что есть метод с именем onreadystatechange, но еще не экспериментировал с ним из-за множества других вещей, которые я пробовал, блуждая в темноте. Может ли это быть решением, и как правильно его использовать? Или, в качестве альтернативы, я должен просто обрабатывать цикл сообщений внутри DisplayHtmlString, пока состояние готовности не изменится на «завершено»?

ОБНОВЛЕНИЕ: добавлена ​​обработка цикла сообщений в DisplayHtmlString. При первом вызове (который работает) состояние готовности становится «интерактивным», но не далее (что не кажется проблемой). В последующем вызове (когда он завершается с ошибкой) состояние готовности остается в состоянии «загрузка», даже если цикл обработки сообщений обрабатывается.


person Dabbler    schedule 10.08.2013    source источник


Ответы (1)


Вы должны обработать событие 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. Для краткости проверки ошибок очень просты.

person noseratio    schedule 10.08.2013
comment
Я использую С++; если у вас есть пример кода, это действительно поможет. Я еще не понял часть DISPID_VALUE. Синхронный или асинхронный рендеринг мне подходит. Я полагаю, что асинхронный обычно более рекомендуется. - person Dabbler; 10.08.2013
comment
Спасибо, но вы уверены, что эти ссылки указывают на правильный пример? Это C#, и я не могу найти Web0cHost.cpp. - person Dabbler; 11.08.2013