[WPF]如何在关闭非模态子窗体时用消息框确认——解决最小化窗体时抛出的异常
又是一个看起来很简单的问题。像下面这样在Closing里弹出个MessageBox确认一下不就行了?
{
if (MessageBox.Show(string.Format("Are you sure to close the {0}?", (sender as Window).Title),
"Confirm", MessageBoxButton.YesNo, MessageBoxImage.Question, MessageBoxResult.No) == MessageBoxResult.No)
{
e.Cancel = true;
}
}
代码简单得不能再简单了,而且试了一下可以达到目的,似乎是没有什么问题啊。但是很多代码需要大量的测试才能发现问题。这个代码就是其中之一。
在说明BUG之前,先给几个信息,大家可以猜猜如何重现这个问题。
1. 只有非模态的子窗体有这个BUG。主窗口是没有问题的。
2. 弹出MessageBox的时候,UI消息处理线程会被阻塞。
3. 抛出的异常,是InvalidOperationException。
4. 子窗体与主窗体有从属关系,主窗体最小化时,子窗体也跟着最小化。
5. 最后一个信息,已经是把BUG告诉大家了。就是Error Message,如下图所示。
图1. Exception信息
看了Error Message,应该都明白了。主窗体没有问题,因为关主窗体时弹出消息框之后,根本没有什么UI操作可以最小化主窗体。(自己写另一个程序去最小化这个窗体不在考虑范围之内。)所以不会有这个BUG,但是为什么子窗体有呢。
下面描述一个这个Bug的产生过程。
1. 主窗体和子窗体都显示出来。
2. 点击关闭按钮关闭子窗体,此时会弹出消息框问要不要关。不去理这个消息框。
3. 点击任务栏上的主窗体,使主窗体最小化。这里子窗体也最小化了。
4. 再点任务栏上的主窗体,使主窗体还原。异常抛出。而且,即使handle了这个异常,这个子窗体也会变黑的。
可以发现,其实罪魁祸首是上面的第4条信息。操作系统在主窗体最小化时,自动最小化其子窗体,结果帮了倒忙。这种自动做事帮倒忙的事情应该还有不少。还发现过的一例就是在WPF Bug清单里的RadioButton无法绑定的BUG,也是系统自动做事造成的。但是逻辑上来讲,这么做也的确是对的。
最后想办法解决问题的,只能是我们自己。Exception的Message上说在窗体Closing的时候,不能做这做那,但是我们又要做,那怎么办呢?其实是我们的MessageBox阻塞了窗体Close的过程才有这个异常。那么解决方案就出来,让MessageBox不阻塞UI线程不就得了。代码如下 :
{
Prevent Recursion
e.Cancel = true; //Cancel every close at once.
Window window = sender as Window;
window.Dispatcher.BeginInvoke(new Action<Window>(ConfirmClose), window);
}
private static void ConfirmClose(Window window)
{
if (MessageBox.Show(string.Format("Are you sure to close the {0}?", window.Title),
"Confirm", MessageBoxButton.YesNo, MessageBoxImage.Question, MessageBoxResult.No) == MessageBoxResult.Yes)
{
window.Close();
}
}
注:其中的Prevent Recursion代码段是为了防止递归调用的。完全可以用一个临时变量代码代替,而且不会出现硬编码的字符串。分析调用栈只是为了突出代码相对于原版的丑恶。^_^
这次还好,为了Fix系统的逻辑BUG,并没有多写多少代码。