c# - 如何拦截WebBrowser控件中的onbeforeunload事件?

标签 c# winforms browser onbeforeunload

我有一个 WinForms 应用程序,我在其中托管了一个网页 WebBrowser控制。

网页内容如下:

<!DOCTYPE html>
<html lang="en" dir="ltr">
<head>
  <title>onbeforeunload test</title>
  <meta charset="utf-8">
</head>
<body>

<a href="#" onclick="window.location.reload();">Test</a>

<script type="text/javascript">
    window.onbeforeunload = function () {
        return 'Are you sure you want to leave this page?';
    };
</script>
</body>
</html>

如您所见,我已经订阅了 onbeforeunload允许在离开此页面之前显示确认对话框的事件。当我单击重新加载页面的 anchor 时,这工作正常。显示确认框,用户可以取消页面的重新加载。这在 WinForms 托管控件中运行良好。

现在,我遇到的困难是在用户关闭 WinForms 应用程序(例如通过单击 X 按钮)时拦截和执行此事件。

我能够在 WinForms 应用程序中获取此函数的内容,但无论我尝试什么,我都无法获取此函数返回的字符串的内容,以便我稍后可以使用它来伪造 MessageBox当用户试图关闭应用程序时:

webBrowser1.Navigated += (sender, e) =>
{
    webBrowser1.Document.Window.Load += (s, ee) =>
    {
        // In order to get the IHTMLWindow2 interface I have referenced
        // the Microsoft HTML Object Library (MSHTML) COM control
        var window = (IHTMLWindow2)webBrowser1.Document.Window.DomWindow;

        // the bu variable contains the script of the onbeforeunload event
        var bu = window.onbeforeunload();

        // How to get the string that is returned by this function
        // so that I can subscribe to the Close event of the WinForms application
        // and show a fake MessageBox with the same text?
    };
};
webBrowser1.Navigate("file:///c:/index.htm");

我试过 window.execScript方法不可用:

// returns null
var script = string.Format("({0})();", bu);
var result = window.execScript(script, "javascript");

我也试过以下但它也返回空:

var result = window.execScript("(function() { return 'foo'; })();", "javascript");

作为最后的手段,我可​​以使用第三方 javascript 解析器,我可以将此函数的主体提供给它,它会执行它并给我返回值,但这确实是最后的手段。如果有更原生的方法使用 Microsoft 的 MSHTML 库来执行此操作,我会很高兴。


更新:

由于 excellent answer,现在已经解决了这个问题@Hans 提供的。出于某种原因,我无法让他的解决方案在我的测试机器(Win7 x64、.NET 4.0 客户端配置文件、IE9、en-US 语言环境)上运行,并且我总是在 hr = -2147024809 IDispatch.Invoke 调用。因此,我将 IDispatch P/Invoke 签名修改为以下内容(此签名不需要将对 c:\windows\system32\stdole2.tlb 的引用添加到项目中):

using System;
using System.Runtime.InteropServices;
using System.Runtime.InteropServices.ComTypes;

[ComImport]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
[Guid("00020400-0000-0000-C000-000000000046")]
public interface IDispatch
{
    [PreserveSig]
    int GetTypeInfoCount(out int Count);
    [PreserveSig]
    int GetTypeInfo(
        [MarshalAs(UnmanagedType.U4)] int iTInfo,
        [MarshalAs(UnmanagedType.U4)] int lcid, 
        out ITypeInfo typeInfo
    );

    [PreserveSig]
    int GetIDsOfNames(
        ref Guid riid,
        [MarshalAs(UnmanagedType.LPArray, ArraySubType = UnmanagedType.LPWStr)] string[] rgsNames, 
        int cNames, 
        int lcid, 
        [MarshalAs(UnmanagedType.LPArray)] int[] rgDispId
    );

    [PreserveSig]
    int Invoke(
        int dispIdMember, 
        ref Guid riid, 
        uint lcid, 
        ushort wFlags,
        ref System.Runtime.InteropServices.ComTypes.DISPPARAMS pDispParams, 
        out object pVarResult,
        ref System.Runtime.InteropServices.ComTypes.EXCEPINFO pExcepInfo, 
        IntPtr[] pArgErr
    );
}

然后我订阅了表单的 Closing 事件,并且能够获取 onbeforeunload 事件返回的消息并提示用户:

protected override void OnFormClosing(FormClosingEventArgs e)
{
    var window = (IHTMLWindow2)webBrowser1.Document.Window.DomWindow;
    var args = new System.Runtime.InteropServices.ComTypes.DISPPARAMS();
    var result = new object();
    var except = new System.Runtime.InteropServices.ComTypes.EXCEPINFO();
    var idisp = window.onbeforeunload as IDispatch;
    if (idisp != null)
    {
        var iid = Guid.Empty;
        var lcid = (uint)CultureInfo.CurrentCulture.LCID;
        int hr = idisp.Invoke(0, ref iid, lcid, 1, ref args, out result, ref except, null);
        if (hr == 0)
        {
            var msgBox = MessageBox.Show(
                this,
                (string)result,
                "Confirm",
                MessageBoxButtons.OKCancel
            );
            e.Cancel = msgBox == DialogResult.Cancel;
        }
    }
    base.OnFormClosing(e);
}

最佳答案

IHtmlWindow2.onbeforeunload 本身不显示对话框。它只返回一个字符串,主机必须在消息框中使用该字符串。由于您的 Winforms 应用程序是主机,因此它必须使用 MessageBox.Show()。调用 onbeforeunload 比较困难,它是一个 IDispatch 指针,其默认成员(dispid 0)返回字符串。添加对 c:\windows\system32\stdole2.tlb 的引用并粘贴此代码:

using System.Runtime.InteropServices;
...
        [ComImport, InterfaceType(ComInterfaceType.InterfaceIsIUnknown), Guid("00020400-0000-0000-C000-000000000046")]
        public interface IDispatch {
            int dummy1();
            int dummy2();
            int dummy3();
            [PreserveSig]
            int Invoke(int dispIdMember, ref Guid riid, int lcid, int dwFlags, 
                [In, Out] stdole.DISPPARAMS pDispParams, 
                [Out, MarshalAs(UnmanagedType.LPArray)] object[] pVarResult, 
                [In, Out] stdole.EXCEPINFO pExcepInfo, 
                [Out, MarshalAs(UnmanagedType.LPArray)] IntPtr[] pArgErr);
        }

您将像这样使用它:

    protected override void OnFormClosing(FormClosingEventArgs e) {
        var window = (IHTMLWindow2)webBrowser1.Document.Window.DomWindow;
        var args = new stdole.DISPPARAMS();
        var result = new object[1];
        var except = new stdole.EXCEPINFO();
        var idisp = (IDispatch)window.onbeforeunload;
        var iid = Guid.Empty;
        int hr = idisp.Invoke(0, ref iid, 1033, 1, args, result, except, null);
        if (hr == 0) {
            if (MessageBox.Show(this, (string)result[0], "Confirm",
                MessageBoxButtons.OKCancel) == DialogResult.Cancel) e.Cancel = true;
        }
        base.OnFormClosing(e);                                                       
    }

关于c# - 如何拦截WebBrowser控件中的onbeforeunload事件?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/8883076/

相关文章:

c# - C# 中可调整大小的表格布局面板

c# - stackPanel1.Children.RemoveAt 是否会销毁(处置)控件?

javascript - 在 ASP.NET 项目中使用缓存的最佳方法是什么?

c# - 如何从 Razor View 获取产品版本

c# - Asp.net MVC 路由名称带有 '-'

c# - 在 C# 中通过引用传递属性

c# - Winforms:从不同的类调用输入表单函数

c# - 如何在 winforms 应用程序中隐藏我的服务器 IP 变量

configuration - 浏览器设置/功能测试页

c# - 单元测试和使用 Moq 中的问题。返回