名称: dotnet-winui 描述: “构建 WinUI 3 桌面应用程序。Windows App SDK、XAML 模式、MSIX/非打包部署、UWP 迁移。” 用户可调用: false
dotnet-winui
WinUI 3 / Windows App SDK 开发:使用 UseWinUI 和 Windows 10 TFM 进行项目设置,包含编译绑定 (x:Bind) 和延迟加载 (x:Load) 的 XAML 模式,使用 CommunityToolkit.Mvvm 的 MVVM 架构,MSIX 和非打包部署模式,Windows 集成(生命周期、通知、小组件),UWP 迁移指南,以及常见代理陷阱。
版本假设: .NET 8.0+ 基线。Windows App SDK 1.6+(当前稳定版)。TFM net8.0-windows10.0.19041.0。.NET 9 功能明确标记。
范围
- WinUI 3 项目设置(UseWinUI、Windows 10 TFM)
- XAML 模式(x:Bind 编译绑定、x:Load 延迟加载)
- 使用 CommunityToolkit.Mvvm 的 MVVM
- MSIX 和非打包部署模式
- Windows 集成(生命周期、通知、小组件)
- UWP 迁移指南
超出范围
- 桌面 UI 测试(Appium、WinAppDriver)—— 见 [技能:dotnet-ui-testing-core]
- 通用 Native AOT 模式 —— 见 [技能:dotnet-native-aot]
- UI 框架选择决策树 —— 见 [技能:dotnet-ui-chooser]
- WPF 模式 —— 见 [技能:dotnet-wpf-modern]
交叉参考:[技能:dotnet-ui-testing-core] 用于桌面测试,[技能:dotnet-wpf-modern] 用于 WPF 模式,[技能:dotnet-wpf-migration] 用于迁移指南,[技能:dotnet-native-aot] 用于通用 AOT,[技能:dotnet-ui-chooser] 用于框架选择,[技能:dotnet-native-interop] 用于通用 P/Invoke 模式(CsWin32 生成 P/Invoke 声明),[技能:dotnet-accessibility] 用于无障碍模式(AutomationProperties、AutomationPeer、UI Automation)。
项目设置
WinUI 3 使用 Windows App SDK(原 Project Reunion)作为其运行时和 API 层。项目目标为 Windows 10 版本特定的 TFM。
<!-- MyWinUIApp.csproj -->
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net8.0-windows10.0.19041.0</TargetFramework>
<UseWinUI>true</UseWinUI>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<!-- Windows App SDK 版本(通过 UseWinUI 自动引用) -->
<WindowsAppSDKSelfContained>true</WindowsAppSDKSelfContained>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.*" />
<PackageReference Include="CommunityToolkit.WinUI.Controls.SettingsControls" Version="8.*" />
<PackageReference Include="Microsoft.Extensions.Hosting" Version="8.*" />
</ItemGroup>
</Project>
项目布局
MyWinUIApp/
App.xaml / App.xaml.cs # 应用程序入口、资源字典
MainWindow.xaml / .xaml.cs # 主窗口
ViewModels/ # MVVM ViewModels
Views/ # XAML 页面(用于 Frame 导航)
Models/ # 数据模型
Services/ # 服务接口和实现
Assets/ # 图像、图标
Package.appxmanifest # MSIX 清单(打包模式)
Properties/
launchSettings.json
主机构建器模式
现代 WinUI 应用程序使用通用主机进行依赖注入和服务配置:
// App.xaml.cs
public partial class App : Application
{
private readonly IHost _host;
public App()
{
this.InitializeComponent();
_host = Host.CreateDefaultBuilder()
.ConfigureServices((context, services) =>
{
// 服务
services.AddSingleton<INavigationService, NavigationService>();
services.AddSingleton<IProductService, ProductService>();
// ViewModels
services.AddTransient<MainViewModel>();
services.AddTransient<ProductDetailViewModel>();
// 视图
services.AddTransient<MainPage>();
services.AddTransient<ProductDetailPage>();
// 窗口
services.AddSingleton<MainWindow>();
})
.Build();
}
protected override async void OnLaunched(LaunchActivatedEventArgs args)
{
await _host.StartAsync();
var mainWindow = _host.Services.GetRequiredService<MainWindow>();
mainWindow.Closed += async (_, _) =>
{
await _host.StopAsync();
_host.Dispose();
};
mainWindow.Activate();
}
public static T GetService<T>() where T : class
{
var app = (App)Application.Current;
return app._host.Services.GetRequiredService<T>();
}
}
TFM 要求
net8.0-windows10.0.19041.0 TFM 指定:
- .NET 8.0 —— 运行时版本
- Windows 10 构建 19041(版本 2004)—— 最低 Windows SDK 版本
Windows App SDK 功能可能需要更高的 SDK 版本:
- 小组件(Windows 11):
net8.0-windows10.0.22000.0(Windows 11 构建 22000) - Mica 背景:
net8.0-windows10.0.22000.0 - Snap 布局集成:
net8.0-windows10.0.22000.0
XAML 模式
WinUI 3 XAML 与 UWP XAML 不同。根命名空间是 Microsoft.UI.Xaml,而不是 Windows.UI.Xaml。
编译绑定 (x:Bind)
x:Bind 提供编译时类型检查和比 {Binding} 更好的性能。它解析相对于代码隐藏类的属性(而不是 DataContext)。
<Page x:Class="MyApp.Views.ProductListPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="using:MyApp.ViewModels">
<Page.Resources>
<!-- x:Bind 解析相对于代码隐藏,因此将 ViewModel 暴露为属性 -->
</Page.Resources>
<StackPanel Padding="16" Spacing="12">
<TextBox Text="{x:Bind ViewModel.SearchTerm, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
<Button Content="搜索" Command="{x:Bind ViewModel.SearchCommand}" />
<ListView ItemsSource="{x:Bind ViewModel.Products, Mode=OneWay}"
SelectionMode="Single">
<ListView.ItemTemplate>
<DataTemplate x:DataType="vm:ProductViewModel">
<StackPanel Orientation="Horizontal" Spacing="12" Padding="8">
<Image Source="{x:Bind ImageUrl}" Height="60" Width="60" />
<StackPanel>
<TextBlock Text="{x:Bind Name}" Style="{StaticResource BodyStrongTextBlockStyle}" />
<TextBlock Text="{x:Bind Price}" Style="{StaticResource CaptionTextBlockStyle}" />
</StackPanel>
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackPanel>
</Page>
// 代码隐藏:为 x:Bind 暴露 ViewModel 属性
public sealed partial class ProductListPage : Page
{
public ProductListViewModel ViewModel { get; }
public ProductListPage()
{
ViewModel = App.GetService<ProductListViewModel>();
this.InitializeComponent();
}
}
与 {Binding} 的关键区别:
x:Bind在编译时解析(类型安全、更快)- 默认模式是
OneTime(不像{Binding}的OneWay) - 解析相对于代码隐藏类,而不是
DataContext - 在
DataTemplate项目中需要x:DataType
延迟加载 (x:Load)
使用 x:Load 延迟元素创建直到需要,减少初始页面加载时间:
<StackPanel>
<TextBlock Text="始终可见" />
<!-- 这个面板在 ShowDetails 为 true 之前不会创建 -->
<StackPanel x:Load="{x:Bind ViewModel.ShowDetails, Mode=OneWay}" x:Name="DetailsPanel">
<TextBlock Text="按需加载的详细内容" />
<ListView ItemsSource="{x:Bind ViewModel.DetailItems, Mode=OneWay}" />
</StackPanel>
</StackPanel>
何时使用 x:Load: 繁重的 UI 部分(复杂列表、设置面板、详细视图),这些部分不是立即可见的。当绑定属性变为 true 时创建元素,变为 false 时销毁。
NavigationView 模式
WinUI 应用程序通常使用 NavigationView 与 Frame 进行页面导航:
<!-- MainWindow.xaml -->
<Window x:Class="MyApp.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<NavigationView x:Name="NavView"
IsBackButtonVisible="Collapsed"
SelectionChanged="NavView_SelectionChanged">
<NavigationView.MenuItems>
<NavigationViewItem Content="首页" Tag="home" Icon="Home" />
<NavigationViewItem Content="产品" Tag="products" Icon="Shop" />
<NavigationViewItem Content="设置" Tag="settings" Icon="Setting" />
</NavigationView.MenuItems>
<Frame x:Name="ContentFrame" />
</NavigationView>
</Window>
MVVM
WinUI 3 与 CommunityToolkit.Mvvm(MAUI 使用的相同 MVVM 工具包)集成。源生成器消除了属性和命令的样板代码。
// ViewModels/ProductListViewModel.cs
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
public partial class ProductListViewModel : ObservableObject
{
private readonly IProductService _productService;
public ProductListViewModel(IProductService productService)
{
_productService = productService;
}
[ObservableProperty]
[NotifyCanExecuteChangedFor(nameof(SearchCommand))]
private string _searchTerm = "";
[ObservableProperty]
private ObservableCollection<ProductViewModel> _products = [];
[ObservableProperty]
private bool _isLoading;
[ObservableProperty]
private bool _showDetails;
[RelayCommand]
private async Task LoadProductsAsync(CancellationToken ct)
{
IsLoading = true;
try
{
var items = await _productService.GetProductsAsync(ct);
Products = new ObservableCollection<ProductViewModel>(
items.Select(p => new ProductViewModel(p)));
}
finally
{
IsLoading = false;
}
}
[RelayCommand(CanExecute = nameof(CanSearch))]
private async Task SearchAsync(CancellationToken ct)
{
var results = await _productService.SearchAsync(SearchTerm, ct);
Products = new ObservableCollection<ProductViewModel>(
results.Select(p => new ProductViewModel(p)));
}
private bool CanSearch() => !string.IsNullOrWhiteSpace(SearchTerm);
}
关键源生成器属性:
[ObservableProperty]—— 从后备字段生成具有INotifyPropertyChanged的属性[RelayCommand]—— 从方法生成ICommand(支持异步、取消、CanExecute)[NotifyPropertyChangedFor]—— 为依赖属性引发PropertyChanged[NotifyCanExecuteChangedFor]—— 当属性更改时重新评估命令的CanExecute
打包
WinUI 3 支持两种部署模型:MSIX 打包和非打包。选择影响应用程序身份、功能和分发。
MSIX 打包部署
MSIX 是默认打包模型。它提供应用程序身份、干净的安装/卸载、自动更新和对完整 Windows 集成 API 的访问。
<!-- Package.appxmanifest 声明应用程序身份和功能 -->
<Package xmlns="http://schemas.microsoft.com/appx/manifest/foundation/windows10"
xmlns:mp="http://schemas.microsoft.com/appx/2014/phone/manifest"
xmlns:uap="http://schemas.microsoft.com/appx/manifest/uap/windows10">
<Identity Name="MyApp" Publisher="CN=Contoso" Version="1.0.0.0" />
<Applications>
<Application Id="App"
Executable="$targetnametoken$.exe"
EntryPoint="$targetentrypoint$">
<uap:VisualElements DisplayName="我的应用"
Description="WinUI 3 应用程序"
BackgroundColor="transparent"
Square150x150Logo="Assets\Square150x150Logo.png"
Square44x44Logo="Assets\Square44x44Logo.png" />
</Application>
</Applications>
<Capabilities>
<Capability Name="internetClient" />
</Capabilities>
</Package>
# 构建 MSIX 包
dotnet publish -c Release -r win-x64
非打包部署
非打包模式移除 MSIX 要求。应用程序作为标准 Win32 可执行文件运行,没有应用程序身份。
<!-- .csproj:启用非打包模式 -->
<PropertyGroup>
<WindowsPackageType>None</WindowsPackageType>
</PropertyGroup>
权衡:
| 功能 | MSIX 打包 | 非打包 |
|---|---|---|
| 应用程序身份 | 是 | 否 |
| 干净的安装/卸载 | 是(添加/删除程序) | 手动 |
| 自动更新 | 是(商店、应用程序安装程序) | 手动 |
| 后台任务 | 完全支持 | 有限 |
| 吐司通知 | 完全支持 | 需要 COM 注册 |
| 小组件(Windows 11) | 是 | 否 |
| 文件类型关联 | 通过清单 | 通过注册表 |
| 分发 | 商店、侧载、应用程序安装程序 | xcopy、安装程序(MSI/EXE) |
| 启动时间 | 稍慢(包验证) | 更快 |
何时选择非打包:
- 具有现有部署基础设施的内部企业工具
- 需要 xcopy 部署或与现有 MSI/EXE 安装程序集成的应用程序
- 快速原型,其中打包开销不必要
- 不需要 Windows 身份功能的应用程序
Windows 集成
应用程序生命周期
WinUI 3 应用程序使用 Windows App SDK 激活和生命周期模型,与 UWP 的 CoreApplication 不同。
// 处理激活类型(协议、文件、吐司等)
public partial class App : Application
{
protected override void OnLaunched(LaunchActivatedEventArgs args)
{
// 检查特定激活
var activationArgs = AppInstance.GetCurrent().GetActivatedEventArgs();
switch (activationArgs.Kind)
{
case ExtendedActivationKind.Protocol:
var protocolArgs = (ProtocolActivatedEventArgs)activationArgs.Data;
HandleProtocolActivation(protocolArgs.Uri);
break;
case ExtendedActivationKind.File:
var fileArgs = (FileActivatedEventArgs)activationArgs.Data;
HandleFileActivation(fileArgs.Files);
break;
default:
// 正常启动
break;
}
}
}
通知
吐司通知需要 Windows App SDK 通知 API:
using Microsoft.Windows.AppNotifications;
using Microsoft.Windows.AppNotifications.Builder;
// 注册通知激活
var notificationManager = AppNotificationManager.Default;
notificationManager.NotificationInvoked += OnNotificationInvoked;
notificationManager.Register();
// 发送吐司通知
var builder = new AppNotificationBuilder()
.AddText("订单已发货")
.AddText("您的订单 #12345 已发货。")
.AddButton(new AppNotificationButton("跟踪")
.AddArgument("action", "track")
.AddArgument("orderId", "12345"));
AppNotificationManager.Default.Show(builder.BuildNotification());
小组件(Windows 11)
小组件需要 Windows 11(构建 22000+)和 MSIX 打包部署。实现涉及创建实现 IWidgetProvider 的小组件提供程序,并在 MSIX 清单中注册。
关键步骤:
- 实现
IWidgetProvider接口(方法:CreateWidget、DeleteWidget、OnActionInvoked、OnWidgetContextChanged、OnCustomizationRequested、Activate、Deactivate) - 在 MSIX 清单中将提供程序注册为 COM 类
- 使用 Adaptive Cards JSON 格式定义小组件模板
- 从提供程序方法返回更新的小组件内容
参阅 Windows App SDK 小组件文档 获取完整接口契约和清单注册。
任务栏集成
WinUI 3 中的任务栏进度需要通过 ITaskbarList3 接口进行 Win32 COM 互操作。与 UWP 有托管 TaskbarManager 不同,WinUI 3 不暴露托管包装器。
// 任务栏进度在 WinUI 3 中需要 COM 互操作
// 使用 CsWin32 源生成器或手动 P/Invoke 获取 ITaskbarList3
// 1. 添加 CsWin32:<PackageReference Include="Microsoft.Windows.CsWin32" Version="0.3.*" />
// 2. 添加到 NativeMethods.txt:ITaskbarList3
// 参见:https://learn.microsoft.com/en-us/windows/win32/api/shobjidl_core/nn-shobjidl_core-itaskbarlist3
UWP 迁移
从 UWP 迁移到 WinUI 3 涉及命名空间更改、API 替换和项目重构。
命名空间更改
| UWP 命名空间 | WinUI 3 命名空间 |
|---|---|
Windows.UI.Xaml |
Microsoft.UI.Xaml |
Windows.UI.Xaml.Controls |
Microsoft.UI.Xaml.Controls |
Windows.UI.Xaml.Media |
Microsoft.UI.Xaml.Media |
Windows.UI.Xaml.Input |
Microsoft.UI.Xaml.Input |
Windows.UI.Composition |
Microsoft.UI.Composition |
Windows.UI.Text |
Microsoft.UI.Text |
Windows.UI.Colors |
Microsoft.UI.Colors |
保持不变: Windows.Storage、Windows.Networking、Windows.Security、Windows.ApplicationModel、Windows.Devices —— 这些 WinRT API 仍保留在 Windows.* 命名空间中。
API 替换
| UWP API | WinUI 3 替换 |
|---|---|
CoreApplication.MainView |
App.MainWindow(跟踪自己的窗口引用) |
CoreDispatcher.RunAsync |
DispatcherQueue.TryEnqueue |
Window.Current |
在 App 类中手动跟踪窗口引用 |
ApplicationView.Title |
window.Title = "..." |
CoreWindow.GetForCurrentThread |
不可用;对键盘 API 使用 InputKeyboardSource |
SystemNavigationManager.BackRequested |
NavigationView.BackRequested |
迁移步骤
- 创建新的 WinUI 3 项目 使用 Windows App SDK 模板
- 复制源文件 并更新命名空间(
Windows.UI.Xaml到Microsoft.UI.Xaml) - 更新所有
.xaml文件中的 XAML 命名空间 - 替换已弃用的 API(见上表)
- 迁移打包 从
.appxmanifestUWP 格式到 Windows App SDK 格式 - 更新 NuGet 包 到 Windows App SDK 兼容版本
- 测试 Windows 集成 功能(通知、后台任务、文件关联)
有关跨框架的全面迁移路径指南,请参阅 [技能:dotnet-wpf-migration]。
UWP .NET 9 预览路径: 微软宣布了 .NET 9 上的 UWP 支持作为预览。这允许 UWP 应用程序使用现代 .NET 而无需迁移到 WinUI 3。如果完整 WinUI 迁移成本过高,但需要现代 .NET 运行时功能,请评估此路径。
代理陷阱
- 不要混淆 UWP XAML 与 WinUI 3 XAML。 根命名空间从
Windows.UI.Xaml更改为Microsoft.UI.Xaml。使用Windows.UI.Xaml.*类型的代码在 WinUI 3 项目中无法编译。 - 不要使用
Window.Current。 WinUI 3 没有静态Window.Current属性。在App类中手动跟踪窗口引用,并通过 DI 或静态属性传递。 - 不要使用
CoreDispatcher。 将CoreDispatcher.RunAsync()替换为DispatcherQueue.TryEnqueue()。CoreDispatcher是 UWP API,在 WinUI 3 中不可用。 - 不要假设 MSIX 是必需的。 WinUI 3 支持通过
<WindowsPackageType>None</WindowsPackageType>进行非打包部署。仅当需要应用程序身份、商店分发或需要它的 Windows 集成功能时使用 MSIX。 - 不要忘记
x:Bind默认为OneTime。 与{Binding}默认为OneWay不同,x:Bind默认为OneTime。对于初始绑定后更改的属性,始终指定Mode=OneWay或Mode=TwoWay。 - 不要目标低于 19041 的 Windows 10 构建。 Windows App SDK 1.6+ 需要最低构建 19041(版本 2004)。目标更低构建会导致运行时失败。
- 不要在非打包应用程序中使用小组件或 Mica。 这些功能需要具有应用程序身份的 MSIX 打包部署。在非打包模式下尝试使用它们会静默失败或抛出异常。
- 不要混合 CommunityToolkit.Mvvm 与手动 INotifyPropertyChanged。 一致使用
[ObservableProperty]。混合源生成和手写实现会导致微妙的绑定错误。 - 不要忘记主机构建器生命周期。 在
OnLaunched中调用_host.StartAsync(),并在窗口关闭时调用_host.StopAsync()。忘记生命周期管理会导致 DI 注册的IHostedService实例从未启动或停止。
先决条件
- .NET 8.0+ 带有 Windows 桌面工作负载
- Windows App SDK 1.6+(通过
UseWinUI自动引用) - Windows 10 版本 2004(构建 19041)或更高
- Visual Studio 2022+ 带有 Windows App SDK 工作负载,或 VS Code 带有 C# Dev Kit
- 对于小组件:Windows 11(构建 22000+)