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

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

网页的内容如下:

   onbeforeunload test    Test  window.onbeforeunload = function () { return 'Are you sure you want to leave this page?'; };    

正如您所看到的,我订阅了onbeforeunload事件,该事件允许在离开此页面之前显示确认对话框。 当我单击重新加载页面的锚点时,这可以正常工作。 将显示确认框,用户可以取消重新加载页面。 这在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"); 

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

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

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


更新:

由于@Hans提供的出色答案 ,现在已经解决了这个问题 。 出于某种原因,我无法使他的解决方案在我的测试机器上运行(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 ); } 

然后我订阅了Form的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); } 

我刚刚处理了类似的问题。 这是一个迟到的答案,但希望它可以帮助某人。 该解决方案基于此MSKB文章 。 它也适用于网页通过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; } } 

上面的代码是针对WebBrowser控件的WinForms版本的。 对于WPF版本,应首先通过reflection获取ActiveXInstance

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

如果您乐意过早地执行事件代码(可能是任何内容),则以下内容会在Window.Load为我捕获字符串;

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