在 WPF 非模態子視窗的 Closing 事件中直接呼叫 MessageBox.Show 確認關閉,看似正常卻隱藏嚴重 Bug:若使用者在對話框彈出期間最小化主視窗(子視窗跟隨最小化),再還原主視窗時會拋出 InvalidOperationException,且子視窗畫面變黑無法恢復。
MessageBox.Show 會阻塞 UI 執行緒,導致子視窗的 Closing 程序無法完成。此期間 Windows 自動將子視窗隨主視窗最小化,破壞了視窗關閉流程的狀態機,造成後續還原時狀態不一致。
利用 Dispatcher.BeginInvoke 將確認邏輯排程到 UI 執行緒稍後執行,立即返回 Closing 事件,避免阻塞:
public static void OnWindowClosingAdv(object sender, CancelEventArgs e) {
// 防止遞迴呼叫
if (new StackTrace().GetFrames().Any(frame => frame.GetMethod().Name == "ConfirmClose")) return;
e.Cancel = true; // 先取消關閉
Window window = sender as Window;
window.Dispatcher.BeginInvoke(new Action<Window>(ConfirmClose), window);
}
private static void ConfirmClose(Window window) {
if (MessageBox.Show($"Are you sure to close the {window.Title}?", "Confirm",
MessageBoxButton.YesNo, MessageBoxImage.Question, MessageBoxResult.No) == MessageBoxResult.Yes) {
window.Close(); // 使用者確認後真正關閉
}
}
e.Cancel = true 先攔截關閉,避免視窗進入不可逆狀態Dispatcher.BeginInvoke 讓 MessageBox 在下一個 Dispatcher 迴圈顯示,Closing 事件順利結束StackTrace 檢查或簡單旗標變數,防止 window.Close() 再次觸發 Closing 進入死循環ShowDialog) 不存在此問題| 方案 | 優點 | 缺點 |
|---|---|---|
| Dispatcher.BeginInvoke (本文) | 程式碼少、不阻塞 UI、相容性好 | 需處理遞迴防護 |
| 自訂非模態確認視窗 | 完全控制 UI/邏輯 | 開發成本高 |
| 關閉前預檢 (無對話框) | 零風險 | 使用者體驗差 |