2012-01-16 14 views
8

Ho un'applicazione WinForms in cui ho ospitato una pagina Web all'interno di un controllo WebBrowser.Come intercettare l'evento onbeforeunload in un controllo WebBrowser?

Il contenuto della pagina web è il seguente:

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

Come potete vedere ho sottoscritto l'evento onbeforeunload che permette di mostrare una finestra di conferma prima di uscire da questa pagina. Funziona bene quando clicco sull'ancoraggio che ricarica la pagina. Viene visualizzata la finestra di conferma e l'utente può annullare il ricaricamento della pagina. Funziona bene nel controllo hosted di WinForms.

Ora, ciò che sto incontrando è l'intercettazione e l'esecuzione di questo evento quando l'utente chiude l'applicazione WinForms (facendo clic sul pulsante X per esempio).

Sono in grado di recuperare il contenuto di questa funzione nell'applicazione WinForms ma non importa quello che ho provato, non sono stato in grado di ottenere il contenuto della stringa restituita da questa funzione in modo da poterla utilizzare in seguito per simulare un MessageBox quando l'utente tenta di chiudere l'applicazione:

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

ho provato il metodo window.execScript a non disponibile:

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

ho anche provato quanto segue ma anche restituito nullo:

01.235.

Come risorsa finale potrei usare un parser javascript di terze parti a cui posso alimentare il corpo di questa funzione e lo eseguirà e mi darà il valore di ritorno ma sarebbe davvero l'ultima risorsa. Sarei felice se esistesse un modo più nativo per farlo utilizzando la libreria MSHTML di Microsoft.


UPDATE:

Questo viene ora risolto grazie alla excellent answer che @Hans disponibile. Per qualche motivo non sono riuscito a far funzionare la sua soluzione sulla mia macchina di prova (Win7 x64, profilo client .NET 4.0, IE9, locale en-US) e ricevevo sempre hr = -2147024809 dopo la chiamata IDispatch.Invoke. Così ho modificato la firma IDispatch P/Invoke al seguente (questa firma non richiede un riferimento a c:\windows\system32\stdole2.tlb da aggiungere al progetto):

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

e poi ho sottoscritto l'evento di chiusura del formare ed è stato in grado di recuperare il messaggio restituito dall'evento onbeforeunload e richiedere all'utente:

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

mai fare affidamento su 'window.onbeforeunload()', non potranno mai mai lavorare in Opera Browser. Solo un consiglio. –

+0

@ sh4nx0r, non mi interessa di Opera. Sto usando un controllo WebBrowser ospitato in un'applicazione WinForms. Ciò implica Internet Explorer. –

+0

Apparentemente 'execScript' restituirà sempre null: S Utile lo so. Tutto quello che posso pensare è che si rotola il proprio parser molto molto basilare che divide il risultato di 'bu' da virgolette singole e afferra il primo elemento nella serie di stringhe reture; come ho detto molto molto di base;) –

risposta

10

IHtmlWindow2.onbeforeunload non visualizza la finestra di dialogo. Restituisce semplicemente una stringa che l'host deve quindi utilizzare in una finestra di messaggio. Poiché l'app Winforms è l'host, è necessario utilizzare MessageBox.Show(). Chiamare onbeforeunload è difficile, è un puntatore IDispatch il cui membro predefinito (dispid 0) restituisce la stringa.Aggiungere un riferimento a c:\windows\system32\stdole2.tlb e incolla questo codice:

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

lo userete in questo modo:

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

Wow, questo sembra estremamente promettente. Risposta eccellente. Grazie mille. ottengo 'hr = -2147024809' dopo la chiamata' Invoke'. Sto usando Windows 7 x64 bit.Può avere qualche influenza? Non posso testare su x86 adesso quale sarà il target finale per la mia applicazione. utilizzando il profilo client .NET 4.0 con IE9 installato È possibile ottenere informazioni più dettagliate sull'errore? –

+0

L'ho provato su Win7 x64.Il codice di errore significa "argomento non valido", si tratta di un errore di Windows, non di un errore COM. L'unica cosa forse è 1033, l'ID della lingua per l'inglese americano Non sei inglese Prova invece CultureInfo.LCID –

+0

Sono in una localizzazione en-US. 'CultureInfo.CurrentCulture.LCID = 1033'. Faccio 'MessageBox.Show (nuovo Win32Exc eption (Marshal.GetLastWin32Error()). Message); 'subito dopo la chiamata di Invoke mostra: L'operazione è stata completata correttamente, quindi suppongo che non si tratti di un errore Win32. –

1

Se il vostro felice di eseguire prematuramente il codice evento (che potrebbe essere qualsiasi cosa) la seguente cattura la stringa per me nel tuo Window.Load ;

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

Ho appena affrontato un problema simile. Questa è una risposta tardiva, ma si spera che possa aiutare qualcuno. La soluzione è basata su this MSKB article. Funziona anche nei casi in cui la pagina web gestisce l'evento onbeforeunload tramite attachEvent o 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; 
    } 
} 

Il codice sopra è per la versione di WinForms WebBrowser controllo. Per la versione di WPF, ActiveXInstance dovrebbe essere la prima ottenuta attraverso la riflessione:

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