有效管理应用状态

在桌面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 来存储较大的数据。 这些位置由系统管理,并在卸载应用时进行清理。

注释

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,该事件为对象AppWindowClosingEventArgsCancel提供属性。

// 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;
}

最佳做法

练习 Guidance
经常保存 不要等到关机——在用户工作过程中保存状态
使用异步 I/O 使用 FileStream 搭配 useAsync: trueFile.WriteAllTextAsync,以避免阻塞 UI
最小化状态大小 仅保存还原用户位置所需的数据
处理电源事件 侦听 PowerManager.EnergySaverStatusChanged 以调整行为
测试意外关机 使用任务管理器终止应用并验证状态恢复