Нечетное закрытие формы при использовании панели задач «Закрыть все окна» - PullRequest
0 голосов
/ 14 апреля 2010

У меня есть приложение Windows Forms с главным окном и 0 или более других открытых окон. Другие открытые окна не принадлежат главному окну и не являются модальными диалоговыми окнами или чем-то еще. Тем не менее, поведение по умолчанию - если главное окно закрывается, то приложение закрывается из-за возврата метода Application.Run. Это нормально, но поскольку у пользователя может быть несохраненная работа в других открытых окнах, я реализовал некоторую логику закрытия форм.

Когда другое окно закрывается, оно проверяет несохраненные изменения и предлагает пользователю стандартную подсказку в стиле «Сохранить / Не сохранять / Отменить» в Microsoft Word.

Когда главное окно закрывается, оно пытается сначала закрыть все остальные открытые окна. Если какой-либо из них не удается закрыть (т. Е. Пользователь нажал кнопку Отмена), он останавливает событие закрытия.

Эта логика встречается в событиях FormClosing и прекрасно работает, за исключением случаев, когда пользователь использует команду панели задач «Закрыть все окна». Это появляется в новой панели задач 7, а также в XP / Vista, когда группировка активна (хотя тогда она помечена как «Закрыть группу»).

Эта команда, похоже, отправляет сообщение о закрытии всем окнам. Проблема заключается в том, что каждое другое окно проверяет наличие изменений и подсказок, а затем основное окно пытается закрыть другие окна. Если я запрашиваю пользователя с помощью стандартной команды MessageBox.Show, то событие закрытия приостанавливается, пока диалоговое окно ожидает ответа пользователя. После нажатия кнопки она обрабатывается как обычно, но все остальные окна либо игнорируют, либо игнорируют команду закрытия окна. И не важно, на что они нажали. Форма, отображающая подсказку, реагирует правильно (если они нажимают кнопку Отмена, она остается открытой, если нет, она закрывается нормально). Но все остальные окна, включая основной акт, как будто ничего не случилось. Их событие FormClosing никогда не возникает.

Если я использую TaskDialog (посредством вызова неуправляемого TaskDialogIndirect ), то в тот момент, когда должно появиться приглашение и приостановить событие закрытия формы, вместо этого другие формы обрабатывают свои события закрытия формы. Это на том же потоке не меньше (основной поток пользовательского интерфейса). Когда наступает очередь главного окна, оно пытается закрыть все формы, как обычно. Любая форма, которая попыталась получить приглашение, все еще открыта, остальные с тех пор закрылись сами по себе из-за команды «Закрыть все окна». Главное окно пытается закрыть все еще оставшиеся, в результате чего обрабатывается второе событие FormClosing и вторая попытка запросить (в конце концов, изменения все еще не сохранены!) Все в главном потоке.

Конечным результатом является то, что подсказка появляется дважды подряд после разматывания через стек вызовов. Я знаю, что все это происходит в одном потоке через стек вызовов Visual Studio. Я могу оглянуться назад в любой момент до первой попытки запроса до того момента, когда он собирается вызвать его снова. Кажется, только второй вызов действительно обрабатывает его и показывает подсказку. Первый раз через это почти как где-то в неуправляемом коде, который он уступает другим сообщениям. Я должен упомянуть заранее, что я нигде не вызываю Application.DoEvents.

Является ли TaskDialogIndirect своего рода полусинхронным вызовом? Но я никогда не оставляю основной поток через все это, насколько я могу судить. И тогда почему стандартный MessageBox немедленно запрашивает (как я думаю, что TaskDialog тоже должен), но затем появляется, чтобы отбросить все другие события закрытия окна? Возможно, другие сообщения о закрытии окна просто истекают? Разве они не должны находиться в очереди сообщений, пока не вернется модальное диалоговое окно (окно сообщения)?

У меня такое ощущение, что это все из-за природы управляемой оболочки для Win32 API в Windows Forms & mdash; дырявая абстракция возможно.

Ответы [ 2 ]

0 голосов
/ 19 августа 2016

Close all windows отправляет WM_CLOSE во все окна в группе панелей задач, которая обычно (всегда?) Включает в себя главное окно. Многие приложения имеют диалоговое окно подтверждения в главном окне, но не в дочерних окнах. Некоторые дочерние окна могут получить сообщение WM_CLOSE перед главным окном и, таким образом, будут закрыты, даже если пользователь решит отменить запрос на закрытие.

Вот некоторый код, который перехватывает сообщения WM_CLOSE и затем posts WM_CLOSE в главном окне, если это было одно из окон, которому было отправлено сообщение. Это предотвращает закрытие дочерних окон, что хорошо, если пользователь решает отменить запрос на закрытие.

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;
using System.Windows.Forms;

namespace WindowsFormsApplication1 {

public static class CloseAllWindowsHandler {

    private const int WM_CLOSE = 0x10;
    private const int WM_DESTROY = 0x2;

    static List<NW> closing = new List<NW>();
    static List<NW> nws = new List<NW>();
    static Thread thread = null;
    static IntPtr hwndMainWindow = IntPtr.Zero;

    private class NW : NativeWindow {

        // determine to allow or deny the WM_CLOSE messages
        bool intercept = true;

        public NW() {}

        protected override void WndProc(ref System.Windows.Forms.Message m) {
            if (m.Msg == WM_CLOSE) {
                if (!intercept) {
                    intercept = true;
                    base.WndProc(ref m);
                    return;
                }

                closing.Add(this);

                Thread t = null;
                t = new Thread(() => {
                    try {
                        Thread.Sleep(100);
                    } catch {}

                    if (thread == t) {
                        // no more close requests received in the last 100 ms
                        // if a close request was sent to the main window, then only post a message to it
                        // otherwise send a close request to each root node at the top of the owner chain
                        NW nwMain = null;
                        foreach (NW nw in closing) {
                            if (nw.Handle == hwndMainWindow) {
                                nwMain = nw;
                                break;
                            }
                        }

                        BackgroundWorker bgw = new BackgroundWorker();
                        var closing2 = closing;
                        closing = new List<NW>();
                        bgw.RunWorkerCompleted += (o, e) => {
                            try {
                                if (nwMain != null) {
                                    // if the 'Close all windows' taskbar menu item is clicked, then closing2.Count
                                    // will contain all the window handles
                                    nwMain.intercept = false;
                                    PostMessage(hwndMainWindow, WM_CLOSE, IntPtr.Zero, IntPtr.Zero);
                                }
                                else {
                                    // doesn't seem to ever happen, closing2.Count always equals 1
                                    // so nothing really has to be done
                                    // if (closing2.Count > 1)

                                    foreach (NW nw in closing2) {
                                        nw.intercept = false;
                                        PostMessage(nw.Handle, WM_CLOSE, IntPtr.Zero, IntPtr.Zero);
                                    }
                                }
                                bgw.Dispose();
                            } catch {}
                        };
                        bgw.RunWorkerAsync();
                    }
                });
                thread = t;
                t.IsBackground = true;
                t.Priority = ThreadPriority.Highest;
                t.Start();
                return;
            }
            else if (m.Msg == WM_DESTROY) {
                ReleaseHandle();
                nws.Remove(this);
            }

            base.WndProc(ref m);
        }
    }

    [DllImport("user32.dll")]
    private static extern bool PostMessage(IntPtr hWnd, uint Msg, IntPtr wParam, IntPtr lParam);

    [DllImport("user32.dll")]
    public static extern IntPtr GetParent(IntPtr hWnd);

    private static void RegisterWindow(IntPtr hwnd) {
        NW nw = new NW();
        nws.Add(nw); // prevent garbage collection
        nw.AssignHandle(hwnd);
    }

    private const int WINEVENT_OUTOFCONTEXT = 0;
    private const int EVENT_OBJECT_CREATE = 0x8000;

    public static void AssignHook(IntPtr mainWindowHandle) {
        hwndMainWindow = mainWindowHandle;
        uint pid = 0;
        uint tid = GetWindowThreadProcessId(mainWindowHandle, out pid);
        CallWinEventProc = new WinEventProc(EventCallback);
        hHook = SetWinEventHook(EVENT_OBJECT_CREATE, EVENT_OBJECT_CREATE, IntPtr.Zero, CallWinEventProc, pid, tid, WINEVENT_OUTOFCONTEXT);      
    }

    [DllImport("user32.dll")]
    private static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint lpdwProcessId);

    [DllImport("user32.dll")]
    private static extern IntPtr SetWinEventHook(uint eventMin, uint eventMax, IntPtr hmodWinEventProc, WinEventProc lpfnWinEventProc, uint idProcess, uint idThread, uint dwFlags);

    [DllImport("user32.dll")]
    private static extern int UnhookWinEvent(IntPtr hWinEventHook);

    private static IntPtr hHook = IntPtr.Zero;
    private static WinEventProc CallWinEventProc;
    private delegate void WinEventProc(IntPtr hWinEventHook, int iEvent, IntPtr hWnd, int idObject, int idChild, int dwEventThread, int dwmsEventTime);
    private static void EventCallback(IntPtr hWinEventHook, int iEvent, IntPtr hWnd, int idObject, int idChild, int dwEventThread, int dwmsEventTime) {
        if (iEvent == EVENT_OBJECT_CREATE) {    
            IntPtr pWnd = GetParent(hWnd);
            if (pWnd == IntPtr.Zero) { // top level window
                RegisterWindow(hWnd);
            }
        }
    }
}

public class Form2 : Form {

    public Button btnOpen = new Button { Text = "Open" };
    public CheckBox cbConfirmClose = new CheckBox { Text = "Confirm Close" };
    private static int counter = 0;
    public Form2() {
        Text = "Form" + counter++;
        FlowLayoutPanel panel = new FlowLayoutPanel { Dock = DockStyle.Top };
        panel.Controls.AddRange(new Control [] { btnOpen, cbConfirmClose });
        Controls.Add(panel);

        btnOpen.Click += btnOpen_Click;
    }

    void btnOpen_Click(object sender, EventArgs e) {
        Form2 f = new Form2();
        f.Owner = this;
        f.Size = new Size(300,300);
        f.Show();
    }

    protected override void OnFormClosing(FormClosingEventArgs e) {
        if (cbConfirmClose.Checked) {
            var dr = MessageBox.Show(this, "Confirm close?", "Close " + Text, MessageBoxButtons.OKCancel);
            if (dr != System.Windows.Forms.DialogResult.OK)
                e.Cancel = true;
        }

        base.OnFormClosing(e);
    }
}

public class Program2 {

    [STAThread]
    static void Main() {

        Application.EnableVisualStyles();
        Application.SetCompatibleTextRenderingDefault(false);
        Form2 f2 = new Form2();
        f2.HandleCreated += delegate {
            CloseAllWindowsHandler.AssignHook(f2.Handle);
        };
        Application.Run(f2);
    }
}

}
0 голосов
/ 14 апреля 2010

Закрыть все окна (начиная с XP) реализация довольно хакерская. В вашей реализации FormClosing проверьте, отключена ли форма, потому что TaskDialog или любое другое приглашение отображается с формой, являющейся владельцем приглашения.

Пока вы это делаете, проверьте, как работает ваша схема закрытия при WM_QUERYENDSESSION, т.е. пользователь выходит из системы с ожидающими изменениями.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...