2012-01-16 141 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;