【问题标题】:IWebBrowser2: how to force links to open in new window?IWebBrowser2:如何强制链接在新窗口中打开?
【发布时间】:2011-02-24 21:35:31
【问题描述】:

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

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

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

感谢回复!

【问题讨论】:

  • 请注意,我并不是建议将问题保持不变,而是提出一个关于可靠识别 BeforeNavigate2() 等人的上下文的具体问题的问题。
  • Georg,我明白,但我的目标只是解决上述问题,我对任何解决方案都很满意,无论是 BeforeNavigate2 还是其他。

标签: c++ internet-explorer com iwebbrowser2


【解决方案1】:

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

我实际上得到了 MS 开发人员支持,这种方法是他们的建议。他们说这就是 Outlook 用于基于 HTML 的电子邮件的方式,这是我想要模仿的用户体验。他们还确认,没有可靠的方法来过滤由用户操作产生的 OnBeforeNavigate 事件和由脚本活动产生的事件。

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

【讨论】:

  • 哇,干得好。很高兴发现这个问题有一个明确的答案。
  • 你有一篇关于如何使用 IHTMLDocument 的文章吗,我发现很多但都在使用 IWebBrowser
  • 嗨,Madhur - 我最初发布这个问题时遇到了同样的问题。所有文档都指向 IWebBrowser。老实说,我不记得是什么让我再看 IHTMLDocument(这是近两年前的事了),但我认为我本身没有一篇文章。我只是使用了 MSDN 网站上的 API 文档和大量的实验来找出我需要什么。祝你好运。
【解决方案2】:

在使用 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 中的AttachClickEvents:

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

【讨论】:

    【解决方案3】:

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

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

    【讨论】:

      【解决方案4】:

      您可以尝试不同的方法,并在物理上添加属性target="_blank" 到渲染文档中的所有&lt;a&gt; 标签。

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

      【讨论】:

      【解决方案5】:

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

      【讨论】:

      • Oleg,您写道:“继续使用 BeforeNavigate2 事件来找出应该打开“新 IE 窗口”的时刻”,这正是我要问的问题。 AFAIK 无法区分脚本启动的 BeforeNavigate2 事件和用户启动的 BeforeNavigate2 事件。只有用户发起的事件才会产生一个新窗口。 (该窗口是否可以或应该在新进程中打开,不是我的问题)。
      • 好的,谢谢!现在我明白了。我应该考虑一下。您正在寻找某种挂钩某些脚本功能的方法?当你需要这种行为时,你能描述一个实际情况吗?使用 ActiveX 组件也可能会遇到一些问题。
      • Oleg,我只想在新窗口中打开链接。实际情况是我正在编写一个需要这种行为的应用程序。我愿意接受任何解决方案。 Trapping BeforeNavigate2 已经被推荐过很多次了,但是我发现它是不够的,因为你无法分辨哪些 BeforeNavigate2 事件是由脚本触发的,哪些是由用户触发的。
      • BeforeNavigate2 可以控制很多事情。首先你控制浏览的起始页。然后您会收到任何页面的完整 html 代码。您可以扫描所有链接、下载和分析内容。您可以在向用户显示之前动态修改任何页面。如果您想要完全控制,您可以实现它。我认为强制在新窗口中打开链接没有问题,但拒绝某些脚本是更多的工作。可以实现,但是我真的不明白在什么情况下真正需要它。如果是机密信息,那就没问题了。我好奇。但是为什么要降级呢?
      • 奥列格,这并没有回答发布的问题。
      猜你喜欢
      • 2011-07-07
      • 2013-10-09
      • 1970-01-01
      • 2010-11-01
      • 1970-01-01
      • 2010-11-05
      • 1970-01-01
      • 2014-04-25
      • 1970-01-01
      相关资源
      最近更新 更多