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);
}
}
}
我有适当的检查,应该关机或任务管理器强制退出,它会跳过这些检查更改,只是丢弃它们。 Close-all-windows命令显示为标准的UserClosing原因,就像他们点击红色的X.我无法检查打开的TaskDialogs,因为它们还没有出现。我第一次试图展示一个,它似乎在实际出现之前屈服于其他形式的关闭事件。 – 2010-04-14 16:41:32
在显示保存更改提示之前,检查WS_DISABLED的表单。启动记事本并键入任何内容,然后尝试注销并获得确认。 – wqw 2010-04-14 17:39:14