Как перехватить событие onbeforeunload в элементе управления WebBrowser? - PullRequest
8 голосов
/ 16 января 2012

У меня есть приложение WinForms, в котором я разместил веб-страницу внутри элемента управления 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");

У меня естьтакже попробовал следующее, но также вернул null:

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

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


ОБНОВЛЕНИЕ:

Теперь эта проблема решена благодаря превосходному ответу что @Hans предоставил.По какой-то причине я не смог заставить его работать на моем тестовом компьютере (Win7 x64, клиентский профиль .NET 4.0, IE9, локаль en-US), и я всегда получал 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);
}

Ответы [ 3 ]

10 голосов
/ 16 января 2012

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);                                                       
    }
3 голосов
/ 16 сентября 2013

Я только что имел дело с подобной проблемой. Это поздний ответ, но, надеюсь, он может кому-то помочь. Решение основано на этой статье MSKB . Это также работает для случаев, когда веб-страница обрабатывает событие 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;
1 голос
/ 16 января 2012

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

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