我有一个带有主窗口的Windows窗体应用程序,并且打开了0个或更多其他窗口。其他打开的窗口不归主窗口所有,也不是模态对话框或任何东西。但是,默认行为是主窗口关闭,然后应用程序因返回Application.Run
方法而关闭。这没关系,但是因为用户可能在其他打开的窗口中有未保存的工作,所以我实现了一些关闭逻辑的表单。
当其他窗口关闭时,它会检查未保存的更改,并提示用户使用标准的保存/不保存/取消Microsoft Word样式提示。
当主窗口关闭时,它会尝试先关闭所有其他打开的窗口。如果其中任何一个未能关闭(即用户点击取消),则它会停止结束事件。
此逻辑发生在FormClosing事件中并且工作正常,除非用户使用任务栏的“关闭所有窗口”命令。当分组处于活动状态时,它出现在7的新任务栏以及XP / Vista中(尽管它被标记为“关闭组”)。
此命令似乎向所有窗口发送关闭消息。问题是每个其他窗口检查更改和提示,然后主窗口尝试关闭其他窗口。如果我使用标准的MessageBox.Show命令提示用户,则在对话框等待用户响应时,关闭事件会暂停。单击一个按钮后,它将正常处理,但所有其他窗口都会丢弃或忽略窗口关闭命令。它们点击的内容也无关紧要。显示提示的表单会正确反应(如果他们点击取消它会保持打开状态,如果没有,则会正常关闭)。但所有其他窗口,包括主要行为都没有发生。他们的FormClosing事件永远不会被提出。
如果我使用TaskDialog(通过调用非托管TaskDialogIndirect),那么在提示应该出现并暂停表单结束事件时,其他表单将处理其表单结束事件。这是在同一个线程上(主UI线程)。当主窗口转过来时,它会尝试关闭所有形状,就像正常一样。尝试提示的任何表单仍然处于打开状态,其余表单由于“关闭所有窗口”命令而自行关闭。主窗口尝试关闭仍然存在的那些,导致第二个FormClosing事件要处理,第二次尝试提示(毕竟,所有更改仍未保存!)所有主线程都在关注你。
最终结果是,在通过调用堆栈展开后,提示符会连续出现两次。我知道这是通过Visual Studio的调用堆栈在同一个线程上发生的。我可以随时回头看第一次快速尝试直到再次调用它的时间。只有第二个调用似乎实际处理它并显示提示。第一次通过它几乎就像在非托管代码中的某个地方,它正在屈服于其他消息。我应该事先提到我不会在任何地方调用Application.DoEvents。
TaskDialogIndirect是某种半异步调用吗?但据我所知,我从未完全通过所有这一切。然后为什么标准的MessageBox立即提示(因为我认为TaskDialog应该也是如此),但是然后似乎放弃了所有其他窗口关闭事件?其他窗口关闭消息可能只是超时吗?在模态对话框(消息框)返回之前,它们是否应该暂挂在消息队列中?
我有一种感觉,这完全归功于Windows窗体的“Win32 API的托管包装”性质 - 也许是leaky abstraction。
答案 0 :(得分:0)
关闭所有窗口(自XP以来)实现有点hacky。在FormClosing
实施中,检查表单是否已禁用,因为显示TaskDialog
或任何其他提示,表单是提示的所有者。
当你在它时,检查你的结算方案在WM_QUERYENDSESSION
上的执行情况,即用户注销未决的更改。
答案 1 :(得分:0)
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);
}
}
}