2010-05-27 1 views
4

В документации MSDN по адресу WebBrowser Customization объясняется, как предотвратить открытие новых окон и как отменить навигацию. В моем случае в моем приложении размещен IWebBrowser2, но я не хочу, чтобы пользователь переходил на новые страницы в моем приложении. Вместо этого я хотел бы открыть все ссылки в новом окне IE. Желаемое поведение: пользователь щелкает ссылку, и с этим URL открывается новое окно.IWebBrowser2: как заставить ссылки открыть в новом окне?

Аналогичный вопрос был задан и ответил here и вместо того, чтобы загрязнять этот ответный пост, было предложено открыть новое обсуждение.

Членов на соответствующую должность предложили я должен быть в состоянии сделать это путем захвата DISPID_BEFORENAVIGATE2, установив флаг отмены, и писать код, чтобы открыть новое окно, но я обнаружил, что управление браузером получает много событий BeforeNavigate2, которые, по-видимому, инициируются скриптами на главной странице. Например, amazon.com запускает события BeforeNavigate2 как сумасшедшие, и они не являются результатом вызова ссылки.

Ответов оценили!

+0

Обратите внимание, что я не предлагал сохранить этот вопрос там статична, а спросить вопрос о конкретной проблеме с надежной идентификацией контекста для 'BeforeNavigate2()' и др. –

+0

Георг, я понимаю, но моя цель - просто решить проблему, изложенную выше, и я буду рад любому решению, будь то BeforeNavigate2 или что-то еще. –

ответ

4

Извините, что ответите на мой собственный вопрос здесь, но в надежде, что это может помочь кому-то другому, я закончил работу с использованием IHTMLDocument напрямую, а не IWebBrowser. IWebBrowser - это надмножество IHTMLDocument, а модель навигации, реализованная IWebBrowser, не настраивается в той степени, в которой я хотел.

Я действительно получил поддержку MS Developer, и этот подход был их рекомендацией. Они говорят, что это то, что Outlook использует для электронной почты на основе HTML, и это пользовательский опыт, который я хотел подражать. Они также подтвердили, что нет надежного способа фильтровать события OnBeforeNavigate, которые являются результатом действия пользователя из действий, которые возникают в результате работы скрипта.

Надеюсь, это поможет любому, кто сталкивается с теми же проблемами. Неправильно было переносить код на использование IHTMLDocument.Если вы это сделаете, вы также можете найти способ выяснить, когда документ будет загружен. Для этого перетащите HTMLDocumentEvents вместо DWebBrowserEvents и найдите событие DISPID_HTMLDOCUMENTEVENTS_ONREADYSTATECHANGE. Он не говорит вам, что такое готовое состояние; вам нужно вызвать IHTMLDocument :: get_readyState и проанализировать полученную строку. Гуфи, но ты туда.

+1

Ничего себе, хорошая работа. Приятно узнать, есть ли окончательный ответ на этот вопрос. –

+0

У вас есть статья о том, как использовать IHTMLDocument, я нашел много, но все использовали IWebBrowser –

+0

Привет, Мадхур. У меня была такая же проблема, когда я отправил этот вопрос изначально. Вся документация указала на IWebBrowser. Я честно не помню, что заставило меня больше взглянуть на IHTMLDocument (это было почти два года назад), но я не думаю, что у меня была статья сама по себе. Я просто использовал документацию API на сайте MSDN и много экспериментов, чтобы выяснить, что мне нужно. Удачи. –

0

Мне кажется, что вы хотите «открыть все ссылки в новом окне IE», это означает, что вы хотите, чтобы открытие новых окон выполнялось в другом процессе. Самый простой способ сделать это: используя метод CreateObject("InternetExplorer.Application") (см. Другой вопрос, который решает проблему, которая противоположна вашему вопросу: InternetExplorer.Application object and cookie container). Таким образом, вы получите лучшую изоляцию от своего приложения, а пользователь, который нажимает на ссылку, получит все возможности, существующие в IE. Вы должны продолжать использовать события BeforeNavigate2, чтобы узнать момент, когда нужно открыть «новое окно IE».

+0

Oleg, Вы писали: «Продолжайте использовать события BeforeNavigate2, чтобы узнать момент, когда нужно открыть« новое окно IE », и это именно тот вопрос, который я задаю. AFAIK нет возможности различать инициированные сценарием события BeforeNavigate2 и инициированные пользователем события BeforeNavigate2. Только инициируемые пользователем события должны привести к появлению нового окна. (Можно ли это окно открыть или открыть в новом процессе или нет, это не мой вопрос). –

+0

Хорошо, спасибо! Теперь я понимаю. Я должен подумать об этом. Вы ищете способ подключения некоторых функций скрипта? Не могли бы вы описать практическую ситуацию, когда вам нужно такое поведение? У вас могут быть проблемы с использованием компонентов ActiveX. – Oleg

+0

Олег, я просто хочу, чтобы ссылки открывались в новых окнах. Практическая ситуация заключается в том, что я пишу приложение, которое требует такого поведения. Я открыт для любого решения. Ловушка BeforeNavigate2 была рекомендована много раз, но я обнаружил ее недостаточной, так как вы не можете определить, какие события BeforeNavigate2 были вызваны сценариями и которые были вызваны пользователем. –

1

Вместо этого вы можете попробовать другой подход и физически добавить атрибут target="_blank" всем <a> тегам в визуализированном документе.

Этот подход предполагает ожидание DISPID_DOCUMENTCOMPLETE, а затем с использованием IHTMLDocument3::getElementsByTagName() для извлечения всех элементов привязки. Затем вы должны использовать IHTMLElement::setAttribute() для установки target="_blank" на каждом из них.

+0

Фил, я попробую. Я пробовал что-то в этом направлении перед отправкой вопроса (я установил атрибут в элементе ), но у меня возникли проблемы: 1, пользователь может щелкнуть, пока не придет DocumentComplete, и 2, я получал HRESULT, который указал на сбой записи. Ошибка записи, возможно, была признаком того, что я делал неправильно, но тот факт, что пользователь мог щелкнуть ссылки до того, как документ был полностью запутанным. Я опубликую больше после того, как вы снова вникнете в это ... –

+0

Что-то иметь в виду - что происходит с сайтами, где скрипты динамически обновляют DOM? –

2

Я предполагаю, что здесь есть еще один подход, который мог бы поддерживать подсчет событий навигации, увеличивая счетчик на DISPID_BEFORENAVIGATE2 и уменьшая его на вхождения DISPID_NAVIGATECOMPLETE2 и DISPID_NAVIGATEERROR. С учетом этого вы можете предположить, что всякий раз, когда вы получаете DISPID_BEFORENAVIGATE2, а ваш счетчик равен нулю, это фактическая навигация пользователя/ссылка на вызов.

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

0

Вы можете привязать к OnClick событие, прежде чем документ является полным при создании браузера в OnCreate() с помощью IHTMLDocument2 :: put_onclick():

#include <comutil.h> 

ClickEvents<RootFrame> clickEvents; 
_variant_t clickDispatch; 
clickDispatch.vt = VT_DISPATCH; 
clickDispatch.pdispVal = &clickEvents; 

CComQIPtr<IDispatch> dispatch; 
hr = webBrowser2->get_Document(&dispatch); 
ASSERT_EXIT(SUCCEEDED(hr), "webBrowser->get_Document(&dispatch)"); 

CComQIPtr<IHTMLDocument2> htmlDocument2; 
hr = dispatch->QueryInterface(IID_IHTMLDocument2, (void**) &htmlDocument2); 
ASSERT_EXIT(SUCCEEDED(hr), "dispatch->QueryInterface(&htmlDocument2)"); 

htmlDocument2->put_onclick(clickDispatch); 

ClickEvents класс реализует IDispatch, вам нужно только реализовать Invoke метод, в остальное возвращение E_NOTIMPL:

HRESULT STDMETHODCALLTYPE Invoke(DISPID dispIdMember, REFIID riid, LCID lcid, WORD wFlags, 
    DISPPARAMS *pDispParams, VARIANT *pVarResult, EXCEPINFO *pExcepInfo, UINT *puArgErr) 
{ 
    HRESULT hr; 

    CComQIPtr<IWebBrowser2> webBrowser2; 
    hr = rootFrame->GetDlgControl(rootFrame->rootview.GetDlgCtrlID(), IID_IWebBrowser2, (void**) &webBrowser2); 
    ASSERT_EXIT(SUCCEEDED(hr), "rootframe->GetDlgControl(IID_IWebBrowser2) failed"); 

    CComQIPtr<IDispatch> dispatch; 
    hr = webBrowser2->get_Document(&dispatch); 
    ASSERT_EXIT(SUCCEEDED(hr), "webBrowser2->get_Document(&dispatch)"); 

    CComQIPtr<IHTMLDocument2> htmlDocument2; 
    hr = dispatch->QueryInterface(IID_IHTMLDocument2, (void**) &htmlDocument2); 
    ASSERT_EXIT(SUCCEEDED(hr), "dispatch->QueryInterface(&htmlDocument2)"); 

    CComQIPtr<IHTMLWindow2> htmlWindow2; 
    hr = htmlDocument2->get_parentWindow((IHTMLWindow2**) &htmlWindow2); 
    ASSERT_EXIT(SUCCEEDED(hr), "htmlDocument2->get_parentWindow(&htmlWindow2)"); 

    CComQIPtr<IHTMLEventObj> htmlEvent; 
    hr = htmlWindow2->get_event(&htmlEvent); 
    ASSERT_EXIT(SUCCEEDED(hr), "htmlWindow2->get_event(&htmlEvent)"); 

    CComQIPtr<IHTMLElement> htmlElement; 
    hr = htmlEvent->get_srcElement(&htmlElement); 
    ASSERT_EXIT(SUCCEEDED(hr), "htmlEvent->get_srcElement(&htmlElement)"); 

    CComBSTR hrefAttr(L"href"); 
    VARIANT attrValue; 
    VariantInit(&attrValue); 
    hr = htmlElement->getAttribute(hrefAttr, 0 | 2, &attrValue); // 0 = case insensitive, 2 = return BSTR 
    ASSERT_EXIT(SUCCEEDED(hr), "htmlElement->getAttribute()"); 

    wchar_t href[2084]; // maximum url length in IE, http://support.microsoft.com/kb/208427 
    wcsncpy_s(href, _countof(href), attrValue.bstrVal, _TRUNCATE); 

    if (!rootFrame->IsURLAllowed(href)) { 

     VARIANT variant; 
     variant.vt = VT_BOOL; 
     variant.boolVal = VARIANT_FALSE; 
     htmlEvent->put_returnValue(variant); 

     ShellExecute(0, L"open", href, 0, 0, SW_SHOWNORMAL); 
    } 

    return S_OK; 
} 

Как вы можете видеть, после запроса некоторых интерфейсов, я наконец-то есть элемент, который получил щелкнул, то я называю IsURLAllowed(), определенный в моем корневом фрейме, чтобы проверить, следует ли разрешить открытие url в текущем окне веб-браузера или открыть его, используя браузер по умолчанию на компьютере пользователя.

Это обрабатывает все ссылки, даже если они были добавлены в документ с использованием javascript.

То же самое должно быть сделано с событиями «onsubmit» для форм.

Я также думаю, что у меня есть решение для переадресации «window.location» в javascript, я еще не протестировал его, но я скоро проверю его, и тогда я обновлю этот ответ. Вы можете использовать комбинацию событий «onunload» и «onbeforeunload» вместе с DWebBrowserEvents2 :: BeforeNavigate2(), после вызова onunload/onbeforeunload вы узнаете, что пользователь покидает текущую страницу, поэтому теперь в BeforeNavigate2() вы можете отменить ее. Вы можете присоединить разгрузочные события, используя IHTMLWindow2 :: put_onunload() и IHTMLWindow2 :: put_onbeforeunload().

См. Источники полного решения для «onclick» ниже.

AttachClickEvents в BrowserFrame:

http://code.google.com/p/phpdesktop/source/browse/phpdesktop-msie/msie/browser_frame.h?r=709d00b991b5#125

Invoke в ClickEvents (IDispatch):

http://code.google.com/p/phpdesktop/source/browse/phpdesktop-msie/msie/click_events.h?r=a5b0b350c933#132