2012-01-16 2 views
8

У меня есть приложение WinForms, в котором я разместил веб-страницу внутри элемента управления WebBrowser.Как перехватить событие onbeforeunload в элементе управления WebBrowser?

Содержание веб-страницы заключается в следующем:

<!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, но независимо от того, что я пытался, я не смог получить содержимое строки, возвращаемой этой функцией, чтобы впоследствии использовать ее для подделки 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"); 

Я также попытался следующие, но он также возвращается нуль:

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

В качестве окончательного варианта я мог бы использовать сторонний анализатор javascript, которому я могу накормить тело этой функции, и он выполнит его и предоставит мне возвращаемое значение, но это действительно будет последним средством. Я был бы рад, если бы был более естественный способ сделать это, используя библиотеку Microsoft MSHTML.


UPDATE:

Это сейчас решается благодаря excellent answer, что @Hans при условии. По какой-то причине я не мог заставить свое решение работать на моей тестовой машине (Win7 x64, .NET 4.0 Client Profile, IE9, en-US locale), и я всегда получал hr = -2147024809 после вызова IDispatch.Invoke. Так что я изменил подпись 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 
    ); 
} 

, а затем я подписался на заключительное мероприятие Форма и был в состоянии принести сообщение, возвращенное 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. Просто подсказка. –

+0

@ sh4nx0r, меня не волнует Opera. Я использую элемент управления WebBrowser, размещенный в приложении WinForms. Это подразумевает использование Internet Explorer. –

+0

Очевидно, что «execScript» всегда возвращает null: S Полезно знать. Все, что я могу думать, это то, что вы катите свой собственный очень простой синтаксический анализатор, который разбивает результат «bu» на одинарные кавычки и захватывает первый элемент в сохраненном массиве строк; как я сказал очень очень простой;) –

ответ

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

Wow, это выглядит очень многообещающе. Отличный ответ. Большое вам спасибо. что я получаю 'hr = -2147024809' после вызова' Invoke'. Я запускаюсь на Windows 7 x64. Может ли это иметь какое-то влияние? Не могу проверить на x86 прямо сейчас, который станет конечной целью для моего приложения. используя профиль .NET 4.0 Client с установленным IE 9. Возможно ли получить более подробную информацию об ошибке? –

+0

Я тестировал это на Win7 x64. Код ошибки означает «Недопустимый аргумент», это ошибка Windows, а не ошибка COM. конечно, что вызвало это. Единственное, может быть, 1033, идентификатор языка для американского английского. Вы не английский. Попробуйте CultureInfo.LCID вместо этого. –

+0

Я нахожусь в en-US locale. 'CultureInfo.CurrentCulture.LCID = 1033'. Я делаю 'MessageBox.Show (новый Win32Exc eption (Marshal.GetLastWin32Error()). Сообщение); 'сразу после вызова Invoke показывает: операция завершена успешно, поэтому я думаю, что это не ошибка Win32. –

1

Если ваш счастливый досрочно выполнить код события (который может быть что угодно) следующее захватывает строку для меня в вашем Window.Load ;

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

Я просто имел дело с подобной проблемой. Это поздний ответ, но, надеюсь, это может помочь кому-то. Решение основано на this MSKB article. Он также работает в случаях, когда веб-страница обрабатывает событие onbeforeunload через attachEvent или 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; 
    } 
} 

Приведенный выше код для 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; 
Смежные вопросы