How to catch the onbeforeunload event in a WebBrowser control?

I have a WinForms application in which I hosted a webpage inside a WebBrowser .

The content of the web page is as follows:

<!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> 

As you can see, I signed up for the onbeforeunload event, which allows you to display a confirmation dialog before proceeding from this page. This works great when I click on an anchor that reloads the page. A confirmation window will appear, and the user can cancel the page reload. This works great inside a hosted WinForms control.

Now I am having difficulty intercepting and executing this event when the user closes the WinForms application (for example, by pressing the X button).

I can get the contents of this function in a WinForms application, but no matter what I tried, I was unable to get the contents of the string returned by this function to subsequently use it to fake a MessageBox when the user tries to close the application:

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

I tried the window.execScript method window.execScript that it is not available:

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

I also tried the following, but also returned null:

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

As a final option, I could use a third-party javascript analyzer, which I can feed the body of this function, and it will execute it and give me a return value, but it really will be the last resort. I would be happy if there was a more natural way to do this using the Microsoft MSHTML library.


UPDATE:

This is now resolved thanks to the excellent answer that @Aans provided. For some reason, I couldn't get my solution to work on my test machine (Win7 x64, .NET 4.0 Client Profile, IE9, en-US locale), and I always got hr = -2147024809 after calling IDispatch.Invoke . Therefore, I changed the IDispatch P / Invoke signature as follows (this signature does not require a link to c:\windows\system32\stdole2.tlb to add to the project):

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

and then I signed up for the form closing event and was able to receive the message returned by the onbeforeunload event and request the user:

 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); } 
+8
c # browser winforms onbeforeunload
source share
3 answers

IHtmlWindow2.onbeforeunload itself does not display a dialog. It simply returns the string that the host should use in the message box. Since your Winforms application is the host, it should use MessageBox.Show (). Calling onbeforeunload is difficult; it is an IDispatch pointer whose default member (dispid 0) returns a string. Add a link to c:\windows\system32\stdole2.tlb and paste this code:

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

You will use it as follows:

  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); } 
+10
source share

I just looked at a similar problem. This is a late answer, but hopefully this can help someone. The solution is based on this MSKB article . It also works in cases where the webpage handles the onbeforeunload event via attachEvent or addEventListener .

 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; } } 

The above code is for WinForms version of WebBrowser . For the WPF version, ActiveXInstance must first be obtained through reflection:

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

If you are happy to prematurely execute the event code (which could be anything), the following line header is for me in your Window.Load ;

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

All Articles