在桌面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: true 或 File.WriteAllTextAsync,以避免阻塞 UI |
| 最小化状态大小 | 仅保存还原用户位置所需的数据 |
| 处理电源事件 | 侦听 PowerManager.EnergySaverStatusChanged 以调整行为 |
| 测试意外关机 | 使用任务管理器终止应用并验证状态恢复 |