有效管理應用程式狀態

在桌面版 Windows 應用程式 SDK 應用程式中,你的程序會持續執行,直到使用者明確關閉它為止。 桌面應用程式不會像 UWP 應用程式那樣自動暫停、恢復和終止生命週期。 這不僅讓你有更多控制權,也更有管理州的責任。

先決條件

  • 一個 Windows 應用程式 SDK 桌面專案。 關於設定步驟,請參見 「建立你的第一個 WinUI 3 應用程式」。
  • 熟悉 Microsoft.UI.Windowing.AppWindow 類別,以及若為封裝應用程式,則熟悉 Windows.Storage.ApplicationData

Important

桌面版 Windows 應用程式 SDK 應用程式不會像 UWP 應用程式那樣自動暫停並恢復。 你的應用程式會持續運行,直到使用者或系統關閉為止。 你仍應實施良好的狀態管理措施,以應對意外關機、系統重啟和斷電。

為什麼國家管理很重要

即使桌面應用程式不會被作業系統暫停,仍有一些情況下你的應用程式可能會失去未儲存的狀態:

  • 使用者在工作進行時關閉應用程式
  • 系統會重新啟動以進行更新
  • 停電或當機會終止該處理程序
  • 使用者從 Windows 登出

設計你的應用程式能優雅地處理這些情況,經常儲存重要狀態,並在應用程式啟動時還原。

逐步儲存狀態

與其在關機時儲存所有狀態,不如隨著使用者操作逐步儲存狀態。 這降低了資料遺失的風險,並將 I/O 成本分散到應用程式的整個使用壽命中。

private async void OnThemeChanged(object sender, SelectionChangedEventArgs e)
{
    var selectedTheme = (sender as ComboBox)?.SelectedItem?.ToString();
    if (selectedTheme != null)
    {
        await SaveSettingAsync("AppTheme", selectedTheme);
    }
}

private async Task SaveSettingAsync(string key, string value)
{
    // Use local file storage, a database, or ApplicationData for packaged apps.
    var localFolder = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData);
    var settingsDir = System.IO.Path.Combine(localFolder, "MyApp");
    Directory.CreateDirectory(settingsDir);
    var settingsPath = System.IO.Path.Combine(settingsDir, "settings.json");

    // Read existing settings, update, and save asynchronously.
    var settings = LoadSettings(settingsPath);
    settings[key] = value;
    await File.WriteAllTextAsync(settingsPath, System.Text.Json.JsonSerializer.Serialize(settings));
}

private Dictionary<string, string> LoadSettings(string path)
{
    // Read and deserialize the settings file, or return an empty set if it doesn't exist yet.
    if (!File.Exists(path))
    {
        return new Dictionary<string, string>();
    }

    var json = File.ReadAllText(path);
    return System.Text.Json.JsonSerializer.Deserialize<Dictionary<string, string>>(json)
        ?? new Dictionary<string, string>();
}

使用 ApplicationData 來處理打包應用程式

如果你的應用程式是用 MSIX 包裝,可以用 Windows.Storage.ApplicationData.Current.LocalSettings 來儲存小型設定和 LocalFolder 較大的資料。 這些位置由系統管理,並在應用程式卸載後清理。

Note

ApplicationData.Current 需要套件識別。 如果您的應用程式未封裝,請改用 Environment.SpecialFolder.LocalApplicationData 或其他標準檔案位置。

// For packaged apps with package identity:
string filePath = "C:\\Users\\Example\\Documents\\report.docx";
var localSettings = Windows.Storage.ApplicationData.Current.LocalSettings;
localSettings.Values["LastOpenedFile"] = filePath;

啟動時還原狀態

當你的應用程式啟動時,檢查之前有沒有儲存過的狀態並還原它。 這讓使用者體驗流暢——他們能從中斷的地方繼續。

private void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
    // Restore the last opened file, if any.
    var localSettings = Windows.Storage.ApplicationData.Current.LocalSettings;
    if (localSettings.Values.TryGetValue("LastOpenedFile", out var path))
    {
        OpenFile(path.ToString());
    }
}

private void OpenFile(string path)
{
    // Load the document at the given path and update the UI.
}

處理 AppWindow.Closing 事件

使用 Microsoft.UI.Windowing.AppWindow 上的 Closing 事件,在應用程式終止前儲存關鍵狀態。 這是使用者關閉視窗時,你最後一次持續維持狀態的機會。

Important

在 WinUI 3 中,活動 Window.Closed 不支援取消。 若要在關閉前提示使用者(例如儲存未儲存的變更),請使用 Microsoft.UI.Windowing.AppWindow.Closing 事件,該事件會提供 AppWindowClosingEventArgs 帶有 Cancel 屬性的物件。

// In your Window constructor or initialization code:
var appWindow = this.AppWindow;
appWindow.Closing += AppWindow_Closing;

void AppWindow_Closing(AppWindow sender, AppWindowClosingEventArgs args)
{
    // See the following examples for what to do here.
}
private void AppWindow_Closing(AppWindow sender, AppWindowClosingEventArgs args)
{
    SaveCurrentDocument();
    SaveWindowPosition();
}

private void SaveCurrentDocument()
{
    // Persist the current document to disk.
}

private void SaveWindowPosition()
{
    // Persist the window's current size and position.
}

若要在關閉前提示使用者,請設定 args.Cancel = true 為防止視窗關閉,然後在使用者確認後以程式方式關閉視窗。 使用保護旗標以防止重複進入,因為呼叫 this.Close()Closing 重新觸發事件:

private bool _isClosing = false;
private bool HasUnsavedChanges { get; set; }

private async void AppWindow_Closing(AppWindow sender, AppWindowClosingEventArgs args)
{
    if (_isClosing) return;

    if (HasUnsavedChanges)
    {
        args.Cancel = true; // Prevent the window from closing.

        // ShowSaveDialog returns true if the user chose to save.
        var shouldSave = await ShowSaveDialog();
        if (shouldSave)
        {
            await SaveCurrentDocumentAsync();
        }

        // Set the guard flag before closing to prevent re-entrancy.
        _isClosing = true;
        this.Close();
    }
}

private Task<bool> ShowSaveDialog()
{
    // Prompt the user to save, discard, or cancel.
    return Task.FromResult(true);
}

private Task SaveCurrentDocumentAsync()
{
    // Persist the current document to disk asynchronously.
    return Task.CompletedTask;
}

最佳做法

練習 指導
經常存檔 不要等關機——在使用者運作時儲存狀態
使用非同步輸入輸出 使用 FileStream 搭配 useAsync: trueFile.WriteAllTextAsync,以避免阻塞使用者介面
最小化州份大小 只儲存恢復使用者位置所需的資料
處理電源事件 監聽 PowerManager.EnergySaverStatusChanged 以調整行為
測試意外關機 使用工作管理員終止應用程式並驗證狀態恢復