Winforms 使用任务栏时的奇怪表单关闭行为';让我们关上所有的窗户

Winforms 使用任务栏时的奇怪表单关闭行为';让我们关上所有的窗户,winforms,Winforms,我有一个Windows窗体应用程序,其中有一个主窗口,打开了0个或更多其他窗口。其他打开的窗口不属于主窗口,也不属于模式对话框或任何内容。但是,默认行为是,如果主窗口关闭,那么应用程序将由于application.Run方法返回而关闭。这很好,但是因为用户可能在其他打开的窗口中有未保存的工作,所以我实现了一些表单关闭逻辑 当其他窗口关闭时,它会检查未保存的更改,并用标准的“保存/不保存/取消Microsoft Word”样式提示用户 当主窗口关闭时,它会尝试首先关闭所有其他打开的窗口。如果其中任

我有一个Windows窗体应用程序,其中有一个主窗口,打开了0个或更多其他窗口。其他打开的窗口不属于主窗口,也不属于模式对话框或任何内容。但是,默认行为是,如果主窗口关闭,那么应用程序将由于
application.Run
方法返回而关闭。这很好,但是因为用户可能在其他打开的窗口中有未保存的工作,所以我实现了一些表单关闭逻辑

当其他窗口关闭时,它会检查未保存的更改,并用标准的“保存/不保存/取消Microsoft Word”样式提示用户

当主窗口关闭时,它会尝试首先关闭所有其他打开的窗口。如果其中任何一个未能关闭(即用户单击“取消”),则会停止关闭事件

此逻辑出现在FormClosing事件中,除非用户使用任务栏的“关闭所有窗口”命令,否则效果很好。当分组处于活动状态时,它会出现在7的新任务栏以及XP/Vista中(尽管当时它被标记为“关闭组”)

此命令似乎向所有窗口发送关闭消息。问题是,每个窗口都会检查更改和提示,然后主窗口会尝试关闭其他窗口。如果我使用标准MessageBox.Show命令提示用户,则关闭事件将在对话框等待用户响应时暂停。单击按钮后,将按正常方式处理该按钮,但所有其他窗口都会放弃或忽略“窗口关闭”命令。他们点击什么也不重要。显示提示的窗体反应正确(如果他们点击Cancel,它将保持打开状态,如果没有,它将正常关闭)。但所有其他窗口,包括主窗口,都表现得好像什么都没发生。他们的FormClosing事件从未引发

如果我使用TaskDialog(通过调用unmanaged),那么在应该出现提示并暂停表单关闭事件时,其他表单将处理其表单关闭事件。这是在同一个线程上(主UI线程)。当主窗口转向时,它会尝试关闭所有窗体,就像关闭普通窗体一样。任何试图提示的表单仍处于打开状态,其余表单由于“关闭所有窗口”命令而自行关闭。主窗口试图关闭那些仍然存在的,导致第二次FormClosing事件要处理,第二次尝试提示(毕竟,更改仍然未保存!),所有这些都在主线程上

最终的结果是,在通过调用堆栈展开后,提示将连续出现两次。我知道这一切都是通过VisualStudio的调用堆栈在同一个线程上发生的。我可以在任何时候回顾第一次提示尝试,直到它将再次调用它为止。似乎只有第二个调用实际处理它并显示提示。第一次通过它几乎就像在非托管代码中的某个地方,它向其他消息屈服。我应该事先提一下,我自己不打电话给Application.DoEvents

TaskDialogIndirect是某种半异步调用吗?但据我所知,我从未离开过这一切的主线。那么,为什么标准MessageBox会立即提示(我认为TaskDialog也应该如此),但随后会删除所有其他窗口关闭事件?其他窗口关闭消息是否正在超时?在模式对话框(消息框)返回之前,它们不应该在消息队列中挂起吗

我有一种感觉,这一切都是由于Windows窗体的“Win32 API托管包装器”特性造成的,这可能是一种错误。

关闭所有Windows(因为XP)的实现有点粗糙。在
FormClosing
实现中,检查表单是否由于显示了
TaskDialog
或任何其他提示而被禁用,并且表单是提示的所有者


当您执行时,检查关闭方案在用户注销时的执行情况,即更改挂起。

关闭所有窗口将
WM_Close
发送到任务栏组中的所有窗口,其中通常(始终?)包括主窗口。许多应用程序在主窗口上有确认对话框提示,但在子窗口上没有。一些子窗口可能会在主窗口之前收到
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);
    }
}

}
下面是一些截取
WM_CLOSE
消息的代码,然后
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);
    }
}

}
使用系统;
使用System.Collections.Generic;
使用系统组件模型;
使用系统图;
使用System.Linq;
使用System.Runtime.InteropServices;
使用系统文本;
使用系统线程;
使用System.Windows.Forms;
命名空间Windows窗体应用程序1{
公共静态类CloseAllWindowsHandler{
私有常量int WM_CLOSE=0x10;
私有常量int WM_DESTROY=0x2;
静态列表关闭=新列表();
静态列表nws=新列表();
静态线程=null;
静态IntPtr hwndMainWindow=IntPtr.Zero;
私有类NW:NativeWindow{
//决定允许或拒绝WM_关闭消息
布尔截距=真;
公共NW(){}
受保护的覆盖无效WndProc(参考System.Windows.Forms.Message m){
如果(m.Msg==WM\u关闭){
如果(!截取){
截距=真;
基准WndProc(参考m);
回来
}
结束。添加(本);
线程t=null;
t=新线程(()=>{
试一试{
睡眠(100);
}捕获{}