2012-01-16 140 views
8

我有一个WinForms应用程序,我在其中托管了一个WebBrowser控件中的网页。如何拦截WebBrowser控件中的onbeforeunload事件?

网页的内容如下:

<!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事件,让导航离开该页面之前,显示一个确认对话框。当我点击重新加载页面的锚时,这工作正常。显示确认框,用户可以取消重新加载页面。这在WinForms托管控件内工作正常。

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

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

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"); 

我也曾尝试以下,但它也返回null:

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

作为最后的手段,我可​​以使用第三方的JavaScript解析器,我可以将这个函数的主体提供给它,它会执行它并给我返回值,但这真的是最后的手段。如果使用Microsoft的MSHTML库有更原始的方式来做到这一点,我会很高兴。


UPDATE:

现在这是解决这得益于@Hans提供的excellent answer。出于某种原因,我无法使他的解决方案在我的测试机器上运行(Win7 x64,.NET 4.0客户端配置文件,IE9,en-US语言环境),并且在拨打IDispatch.Invoke之后我总是收到hr = -2147024809。所以,我已经修改了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); 
} 
+0

从来不靠'window.onbeforeunload()',他们永远不会在Opera浏览器的工作。只是一个小费。 – 2012-01-16 16:37:04

+0

@ sh4nx0r,我不在乎Opera。我正在使用WinForms应用程序中托管的WebBrowser控件。这意味着Internet Explorer。 – 2012-01-16 16:39:17

+0

显然'execScript'将总是返回null:S有帮助,我知道。我所能想到的是,你可以推出自己非常基本的解析器,它将'bu'的结果用单引号分开,并抓住retured字符串数组中的第一项;就像我说的非常基本;) – 2012-01-16 17:27:50

回答

10

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);              
    } 
+0

哇,这看起来非常有希望。非常好的答案。非常感谢。我在'Invoke'调用后得到'hr = -2147024809',我在Windows 7 x64位上运行,这是否会有一些影响?现在无法在x86上测试,这将成为我的应用程序的最终目标。使用.NET 4.0客户端配置文件与IE9安装。是否有可能得到一些更详细的错误信息? – 2012-01-16 19:56:43

+0

我在Win7 x64上测试了这个错误代码的意思是“无效参数”,它是一个Windows错误,而不是COM错误。确定是什么原因引起的,唯一的可能是1033,美国英语的语言ID,你不是英语,试试CultureInfo.LCID代替 – 2012-01-16 20:07:33

+0

我在一个en-US语言环境中,'CultureInfo.CurrentCulture.LCID = 1033'。我做'MessageBox.Show(new Win32Exc eption(Marshal.GetLastWin32Error())Message);'在Invoke调用之后它显示:操作成功完成,所以我猜这不是Win32错误。 – 2012-01-16 20:17:23

1

如果你高兴过早执行事件代码(可以是任何东西)以下捕获字符串我在你Window.Load ;

Object[] args = { @"(" + bu + ")();" }; 
string result = webBrowser1.Document.InvokeScript("eval", args).ToString(); 
3

我只是处理了类似的问题。这是一个迟到的答案,但希望它可以帮助某人。该解决方案基于this MSKB article。它也适用于网页通过attachEventaddEventListener处理onbeforeunload事件的情况。

void Form1_FormClosing(object sender, FormClosingEventArgs e) 
{ 
    // this code depends on SHDocVw.dll COM interop assembly, 
    // generate SHDocVw.dll: "tlbimp.exe ieframe.dll", 
    // and add as a reference to the project 

    var activeX = this.webBrowser.ActiveXInstance; 
    object arg1 = Type.Missing; 
    object arg2 = true; 
    ((SHDocVw.WebBrowser)activeX).ExecWB(SHDocVw.OLECMDID.OLECMDID_ONUNLOAD, SHDocVw.OLECMDEXECOPT.OLECMDEXECOPT_DODEFAULT, ref arg1, ref arg2); 
    if (!(bool)arg2) 
    { 
     e.Cancel = true; 
    } 
} 

以上代码适用于WinForms版本的WebBrowser控件。对于WPF版本,ActiveXInstance应首先通过反射获得:

var activeX = this.WB.GetType().InvokeMember("ActiveXInstance", 
        BindingFlags.GetProperty | BindingFlags.Instance | BindingFlags.NonPublic, 
        null, this.WB, new object[] { }) as SHDocVw.WebBrowser;