首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 更多程式語言 >> 更多程式設計知識

C# WinForms 關閉視窗確認對話框:標準實作與進階模式

     編輯:更多程式設計知識

標準實作:FormClosing 事件攔截

WinForms 中攔截關閉行為的標準切入點是 FormClosing 事件,透過 FormClosingEventArgs.Cancel 決定是否取消關閉。

private void MainForm_FormClosing(object sender, FormClosingEventArgs e)
{
    // 僅使用者主動關閉 (點擊 ×、Alt+F4、系統選單) 才詢問
    // 避免 Application.Exit()、Task Manager 關閉、Windows 關機時誤觸發
    if (e.CloseReason == CloseReason.UserClosing)
    {
        var result = MessageBox.Show(
            "此操作會捨棄您的當前設置,確定要繼續?",
            "確認退出",
            MessageBoxButtons.YesNo,
            MessageBoxIcon.Question,
            MessageBoxDefaultButton.Button2); // 預設 No,安全優先

        if (result != DialogResult.Yes)
        {
            e.Cancel = true; // 取消關閉
        }
    }
}

關鍵細節解析

屬性/參數 作用 注意事項
e.CloseReason 關閉來源:UserClosing/ApplicationExitCall/WindowsShutDown/TaskManagerClosing/FormOwnerClosing 僅攔截 UserClosing,其他情況讓系統正常關閉
e.Cancel true = 取消關閉,表單保持開啟;false = 允許關閉 預設 false,僅在需阻止時設 true
MessageBoxDefaultButton.Button2 預設焦點在「否」按鈕 防止用戶慣性按 Enter 誤關閉,符合「破壞性操作需確認」UX 原則
MessageBoxIcon.Question 顯示問號圖示 視覺提示此為詢問而非警告/錯誤

進階模式一:髒旗標 (Dirty Flag) 只有修改才提示

避免用戶未修改任何資料時也彈窗,體驗更佳。

public partial class SettingsForm : Form
{
    private bool _isDirty = false;

    public SettingsForm()
    {
        InitializeComponent();
        // 綁定所有輸入控制項的變更事件
        txtName.TextChanged += (s, e) => _isDirty = true;
        chkEnable.CheckedChanged += (s, e) => _isDirty = true;
        cmbTheme.SelectedIndexChanged += (s, e) => _isDirty = true;
        // ... 其他控制項
    }

    private void SettingsForm_FormClosing(object sender, FormClosingEventArgs e)
    {
        if (e.CloseReason == CloseReason.UserClosing && _isDirty)
        {
            var result = MessageBox.Show(
                "您有未儲存的變更,確定要捨棄並關閉?",
                "未儲存的變更",
                MessageBoxButtons.YesNo,
                MessageBoxIcon.Warning,
                MessageBoxDefaultButton.Button2);

            if (result != DialogResult.Yes)
                e.Cancel = true;
        }
    }

    private void btnSave_Click(object sender, EventArgs e)
    {
        SaveSettings();
        _isDirty = false; // 儲存後重置
    }
}

進階模式二:三選項對話框 (儲存 / 不儲存 / 取消)

參考 VS/Word 關閉未儲存文件的體驗。

private void EditorForm_FormClosing(object sender, FormClosingEventArgs e)
{
    if (e.CloseReason != CloseReason.UserClosing || !_isDirty) return;

    // 自訂按鈕文字需用 TaskDialog (Win32) 或自訂 Form
    // 這裡用 MessageBox 模擬:Yes=儲存並關閉, No=不儲存關閉, Cancel=取消關閉
    var result = MessageBox.Show(
        "要儲存變更嗎?",
        "未儲存的變更",
        MessageBoxButtons.YesNoCancel,
        MessageBoxIcon.Warning,
        MessageBoxDefaultButton.Button1); // 預設儲存

    switch (result)
    {
        case DialogResult.Yes:
            if (!SaveSettings()) // 儲存失敗 (如驗證不通過)
                e.Cancel = true; // 取消關閉,讓用戶修正
            break;
        case DialogResult.No:
            // 直接關閉,_isDirty 會在 Dispose 時自動清理
            break;
        case DialogResult.Cancel:
            e.Cancel = true; // 取消關閉
            break;
    }
}

private bool SaveSettings()
{
    try 
    {
        // 驗證邏輯...
        // 持久化邏輯...
        _isDirty = false;
        return true;
    }
    catch (Exception ex)
    {
        MessageBox.Show($"儲存失敗:{ex.Message}", "錯誤", MessageBoxButtons.OK, MessageBoxIcon.Error);
        return false;
    }
}

進階模式三:非同步關閉確認 (適用於長時間儲存操作)

若儲存涉及雲端同步、大檔案寫入,避免 UI 凍結。

private async void EditorForm_FormClosing(object sender, FormClosingEventArgs e)
{
    if (e.CloseReason != CloseReason.UserClosing || !_isDirty) return;

    e.Cancel = true; // 先取消,等非同步完成再決定

    var result = await Task.Run(() => MessageBox.Show(
        "要儲存變更嗎?\n(儲存可能需要幾秒鐘)",
        "未儲存的變更",
        MessageBoxButtons.YesNoCancel,
        MessageBoxIcon.Warning));

    if (result == DialogResult.Yes)
    {
        bool saved = await SaveSettingsAsync(); // 非同步儲存
        if (saved) this.Close(); // 程式化關閉,不再觸發確認
    }
    else if (result == DialogResult.No)
    {
        _isDirty = false;
        this.Close();
    }
    // Cancel: 什麼都不做,表單保持開啟
}

private async Task SaveSettingsAsync()
{
    try 
    {
        await Task.Run(() => 
        {
            // 耗時 I/O 操作...
        });
        _isDirty = false;
        return true;
    }
    catch
    {
        // UI 執行緒顯示錯誤
        this.Invoke(() => MessageBox.Show("儲存失敗", "錯誤", MessageBoxButtons.OK, MessageBoxIcon.Error));
        return false;
    }
}

WPF / MAUI 版本對照


private void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e)
{
    if (_isDirty)
    {
        var result = MessageBox.Show("儲存變更?", "確認", MessageBoxButton.YesNoCancel, MessageBoxImage.Warning);
        if (result == MessageBoxResult.Cancel) e.Cancel = true;
        else if (result == MessageBoxResult.Yes) { if (!Save()) e.Cancel = true; }
    }
}

// MAUI: Page.Disappearing 或 Shell.Navigating
protected override bool OnBackButtonPressed()
{
    if (_isDirty) 
    {
        var result = await DisplayAlert("未儲存", "捨棄變更?", "是", "否");
        return !result; // true = 阻止返回
    }
    return base.OnBackButtonPressed();
}

單元測試關鍵點

[Test]
public void FormClosing_UserClosing_Dirty_ShowsDialog_And_Cancels_When_No()
{
    // Arrange
    var form = new SettingsForm();
    form.SetDirty(true); // 透過反射或公共方法設定
    
    // Act - 模擬 UserClosing
    var args = new FormClosingEventArgs(CloseReason.UserClosing, false);
    form.InvokeFormClosing(args); // 需暴露 protected 方法或用測試專用繼承類

    // Assert
    Assert.IsTrue(args.Cancel); // 取消關閉
}

// 測試非 UserClosing 不攔截
[Test]
public void FormClosing_ApplicationExitCall_DoesNotCancel()
{
    var form = new SettingsForm();
    form.SetDirty(true);
    var args = new FormClosingEventArgs(CloseReason.ApplicationExitCall, false);
    form.InvokeFormClosing(args);
    Assert.IsFalse(args.Cancel);
}

結語

關閉確認看似微不足道,實則是資料保護的最後一道防線。從基本的 FormClosing 攔截,到髒旗標優化、三選項交互、非同步儲存支援,每一層演進都在平衡防止誤操作損失與不干擾正常流程。記住核心原則:僅在使用者主動關閉且有未持久化資料時才彈窗,預設按鈕指向安全選項,提供清晰的「儲存/捨棄/取消」三路徑。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved