5

WebBrowser Customization上的 MSDN 文档解释了如何防止打开新窗口以及如何取消导航。就我而言,我的应用程序托管了一个 IWebBrowser2,但我不希望用户导航到我的应用程序中的新页面。相反,我想在新的 IE 窗口中打开所有链接。期望的行为是:用户单击一个链接,并打开一个带有该 URL 的新窗口。

在这里提出并回答了一个类似的问题,建议我打开一个新的讨论,而不是污染回答的帖子。

相关帖子上的成员建议我应该能够通过捕获 DISPID_BEFORENAVIGATE2、设置取消标志和编写代码来打开一个新窗口来做到这一点,但我发现浏览器控件获得了很多似乎是 BeforeNavigate2 事件由主页上的脚本启动。例如,amazon.com 疯狂地触发 BeforeNavigate2 事件,它们不是链接调用的结果。

回复赞赏!

4

5 回答 5

4

我最终做的是直接使用 IHTMLDocument 而不是 IWebBrowser。IWebBrowser 是 IHTMLDocument 的超集,由 IWebBrowser 实现的导航模型无法按照我想要的程度进行定制。

我实际上让 MS 开发人员支持参与其中,这种方法是他们的建议。他们说这就是 Outlook 用于基于 HTML 的电子邮件的方式,这是我想要模仿的用户体验。他们还确认,没有可靠的方法可以将用户操作导致的 OnBeforeNavigate 事件与脚本活动导致的事件过滤。

希望这可以帮助任何面临同样问题的人。移植代码以使用 IHTMLDocument 并不难。如果您最终这样做,您可能还会发现自己正在寻找一种方法来确定文档何时完成加载。为此,挂钩 HTMLDocumentEvents 而不是 DWebBrowserEvents,并查找 DISPID_HTMLDOCUMENTEVENTS_ONREADYSTATECHANGE 事件。它不会告诉你什么是就绪状态。您需要调用 IHTMLDocument::get_readyState 并解析结果字符串。愚蠢,但你去。

于 2010-06-17T23:11:36.163 回答
3

在使用 IHTMLDocument2::put_onclick() 在 OnCreate() 中创建浏览器时,您可以在文档完成之前绑定到 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() 来检查是否允许在当前 webbrowser 窗口中打开 url 或者是否使用用户计算机上的默认浏览器打开它.

这会处理所有链接,即使它们是使用 javascript 附加到文档中的。

表单的“onsubmit”事件也应该这样做。

我也认为我有一个解决方案,用于 javascript 中的“window.location”重定向,我还没有测试过,但我很快就会测试它,然后我会更新这个答案。您可以结合使用“onunload”和“onbeforeunload”事件以及 DWebBrowserEvents2::BeforeNavigate2(),在调用 onunload/onbeforeunload 之后,您将知道用户正在离开当前页面,因此现在在 BeforeNavigate2() 中您可以取消它。您可以使用 IHTMLWindow2::put_onunload() 和 IHTMLWindow2::put_onbeforeunload() 附加卸载事件。

请参阅下面“onclick”的完整解决方案的来源。

在 BrowserFrame 中附加点击事件:

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

在 ClickEvents(IDispatch) 中调用:

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

于 2012-02-23T06:05:28.800 回答
2

我在这里假设,但另一种方法可能是保持导航事件的计数,在出现and时增加计数器DISPID_BEFORENAVIGATE2并减少它。有了这个,你可以推测,每当你得到并且你的计数器为零时,它就是实际的用户导航/链接调用。DISPID_NAVIGATECOMPLETE2DISPID_NAVIGATEERRORDISPID_BEFORENAVIGATE2

我不知道这种方法是否有效,或者这些是否是您需要使其工作的正确事件,但它可能值得研究。

于 2010-06-01T20:55:13.073 回答
1

您可以尝试不同的方法,并将属性物理添加target="_blank" 到呈现文档中的所有<a>标签。

这种方法将涉及等待DISPID_DOCUMENTCOMPLETE然后使用IHTMLDocument3::getElementsByTagName()来获取所有锚元素。然后,您将使用它们中的每一个IHTMLElement::setAttribute()进行设置。target="_blank"

于 2010-05-28T08:27:11.610 回答
0

在我看来,您想要“在新的 IE 窗口中打开所有链接”,这意味着您希望必须在另一个进程中打开新窗口。最简单的方法:使用CreateObject("InternetExplorer.Application")方式(参见另一个解决问题的问题,与您的问题相反:InternetExplorer.Application object and cookie container)。通过这种方式,您将获得与应用程序的最佳隔离,并且单击链接的用户将获得 IE 中存在的所有可能性。您应该继续使用 BeforeNavigate2 事件来找出应该打开“新 IE 窗口”的时刻。

于 2010-05-27T22:23:16.897 回答