关于 dotnet-winui
使用 Windows App SDK 构建 WinUI 3 应用的开发技能,面向 .NET 开发者,涵盖 XAML、MVVM、部署模式、Windows 集成和 UWP 迁移。
主要功能
- 01WinUI 3 项目设置
- 02编译绑定与延迟加载 XAML
- 03CommunityToolkit.Mvvm 模式
- 04MSIX 与非打包部署
- 05通知、小组件与生命周期集成
- 06UWP 迁移指导
适用场景
- 01启动 WinUI 3 应用
- 02组织视图与视图模型
- 03选择应用部署模式
- 04将 UWP 项目迁移到 WinUI 3
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ wshaddix/ dotnet-skills --skill dotnet-winui安装后在 Claude Code 中输入/dotnet-winui确认已加载。
详细介绍
dotnet-winui
WinUI 3 / Windows App SDK 开发:使用 UseWinUI 和 Windows 10 TFM 配置项目,采用编译绑定(x:Bind)和延迟加载(x:Load)的 XAML 模式,使用 CommunityToolkit.Mvvm 实现 MVVM,MSIX 和非打包部署模式,Windows 集成(生命周期、通知、小组件)、UWP 迁移指南,以及常见的 Agent 陷阱。
**版本假设:**以 .NET 8.0+ 为基准。Windows App SDK 1.6+(当前稳定版)。TFM 为 net8.0-windows10.0.19041.0。.NET 9 功能会明确标注。
**范围界定:**本技能负责 WinUI 3 项目配置、XAML 模式、MVVM 集成、打包模式、Windows 平台集成和 UWP 迁移指南。桌面测试由 [skill:dotnet-ui-testing-core] 负责。迁移决策矩阵由 [skill:dotnet-wpf-migration] 负责。
**范围之外:**桌面 UI 测试(Appium、WinAppDriver)——参见 [skill:dotnet-ui-testing-core]。通用 Native AOT 模式——参见 [skill:dotnet-native-aot]。UI 框架选择决策树——参见 [skill:dotnet-ui-chooser]。WPF 模式——参见 [skill:dotnet-wpf-modern]。
交叉引用:[skill:dotnet-ui-testing-core](桌面测试),[skill:dotnet-wpf-modern](WPF 模式),[skill:dotnet-wpf-migration](迁移指南),[skill:dotnet-native-aot](通用 AOT),[skill:dotnet-ui-chooser](框架选择),[skill:dotnet-native-interop](通用 P/Invoke 模式,CsWin32 会生成 P/Invoke 声明),[skill:dotnet-accessibility](无障碍模式:AutomationProperties、AutomationPeer、UI Automation)。
项目配置
WinUI 3 使用 Windows App SDK(前身为 Project Reunion)作为运行时和 API 层。项目以特定 Windows 版本的 TFM 为目标。
<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>
<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 # Application entry, resource dictionaries
MainWindow.xaml / .xaml.cs # Main window
ViewModels/ # MVVM ViewModels
Views/ # XAML pages (for Frame navigation)
Models/ # Data models
Services/ # Service interfaces and implementations
Assets/ # Images, icons
Package.appxmanifest # MSIX manifest (packaged mode)
Properties/
launchSettings.json
Host Builder 模式
现代 WinUI 应用使用通用主机进行依赖项注入和服务配置:
// App.xaml.cs
public partial class App : Application
{
private readonly IHost _host;
public App()
{
this.InitializeComponent();
_host = Host.CreateDefaultBuilder()
.ConfigureServices((context, services) =>
{
// Services
services.AddSingleton<INavigationService, NavigationService>();
services.AddSingleton<IProductService, ProductService>();
// ViewModels
services.AddTransient<MainViewModel>();
services.AddTransient<ProductDetailViewModel>();
// Views
services.AddTransient<MainPage>();
services.AddTransient<ProductDetailPage>();
// Windows
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 build 19041(版本 2004)——最低 Windows SDK 版本
Windows App SDK 功能可能需要更高版本的 SDK:
- 小组件(Windows 11):
net8.0-windows10.0.22000.0(Windows 11 build 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>
</Page.Resources>
<StackPanel Padding="16" Spacing="12">
<TextBox Text="{x:Bind ViewModel.SearchTerm, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
<Button Content="Search" 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>
// Code-behind: expose ViewModel property for x:Bind
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="Always visible" />
<StackPanel x:Load="{x:Bind ViewModel.ShowDetails, Mode=OneWay}" x:Name="DetailsPanel">
<TextBlock Text="Detail content loaded on demand" />
<ListView ItemsSource="{x:Bind ViewModel.DetailItems, Mode=OneWay}" />
</StackPanel>
</StackPanel>
**适用场景:**较重的 UI 区域(复杂列表、设置面板、详细信息视图),且这些区域不会立即显示。绑定属性变为 true 时创建元素,变为 false 时销毁元素。
NavigationView 模式
WinUI 应用通常使用 NavigationView 配合 Frame 进行页面导航:
<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="Home" Tag="home" Icon="Home" />
<NavigationViewItem Content="Products" Tag="products" Icon="Shop" />
<NavigationViewItem Content="Settings" Tag="settings" Icon="Setting" />
</NavigationView.MenuItems>
<Frame x:Name="ContentFrame" />
</NavigationView>
</Window>
MVVM
WinUI 3 与 CommunityToolkit.Mvvm 集成(与 MAUI 使用的 MVVM Toolkit 相同)。源生成器可为属性和命令消除样板代码。
// 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 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="My App"
Description="WinUI 3 application"
BackgroundColor="transparent"
Square150x150Logo="Assets\Square150x150Logo.png"
Square44x44Logo="Assets\Square44x44Logo.png" />
</Application>
</Applications>
<Capabilities>
<Capability Name="internetClient" />
</Capabilities>
</Package>
# Build MSIX package
dotnet publish -c Release -r win-x64
非打包部署
非打包模式无需 MSIX。应用以标准 Win32 可执行文件的形式运行,且没有应用身份。
<PropertyGroup>
<WindowsPackageType>None</WindowsPackageType>
</PropertyGroup>
权衡:
| 功能 | MSIX 打包 | 非打包 |
|---|---|---|
| 应用身份 | 是 | 否 |
| 干净的安装和卸载 | 是(添加/删除程序) | 手动 |
| 自动更新 | 是(Store、App Installer) | 手动 |
| 后台任务 | 完整支持 | 有限支持 |
| Toast 通知 | 完整支持 | 需要 COM 注册 |
| 小组件(Windows 11) | 是 | 否 |
| 文件类型关联 | 通过清单 | 通过注册表 |
| 分发方式 | Store、旁加载、App Installer | xcopy、安装程序(MSI/EXE) |
| 启动时间 | 稍慢(需要验证包) | 更快 |
适合选择非打包模式的情况:
- 已有部署基础设施的企业内部工具
- 需要通过 xcopy 部署或与现有 MSI/EXE 安装程序集成的应用
- 无需打包开销的快速原型
- 不需要 Windows 身份功能的应用
Windows 集成
应用生命周期
WinUI 3 应用使用 Windows App SDK 激活和生命周期模型,与 UWP 的 CoreApplication 不同。
// Handle activation kinds (protocol, file, toast, etc.)
public partial class App : Application
{
protected override void OnLaunched(LaunchActivatedEventArgs args)
{
// Check for specific activation
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:
// Normal launch
break;
}
}
}
通知
Toast 通知需要使用 Windows App SDK 通知 API:
using Microsoft.Windows.AppNotifications;
using Microsoft.Windows.AppNotifications.Builder;
// Register for notification activation
var notificationManager = AppNotificationManager.Default;
notificationManager.NotificationInvoked += OnNotificationInvoked;
notificationManager.Register();
// Send a toast notification
var builder = new AppNotificationBuilder()
.AddText("Order Shipped")
.AddText("Your order #12345 has shipped.")
.AddButton(new AppNotificationButton("Track")
.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 互操作。与拥有托管 TaskbarManager 的 UWP 不同,WinUI 3 不提供托管包装器。
// Taskbar progress requires COM interop in WinUI 3
// Use CsWin32 source generator or manual P/Invoke for ITaskbarList3
// 1. Add CsWin32: <PackageReference Include="Microsoft.Windows.CsWin32" Version="0.3.*" />
// 2. Add to NativeMethods.txt: ITaskbarList3
// See: 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 |
迁移步骤
- 使用 Windows App SDK 模板创建新的 WinUI 3 项目
- 复制源文件并更新命名空间(将
Windows.UI.Xaml改为Microsoft.UI.Xaml) - 更新所有
.xaml文件中的 XAML 命名空间 - 替换已弃用的 API(见上表)
- 将打包格式从
.appxmanifestUWP 格式迁移为 Windows App SDK 格式 - 将 NuGet 包更新到兼容 Windows App SDK 的版本
- 测试 Windows 集成功能(通知、后台任务、文件关联)
如需了解跨框架的完整迁移路径指南,请参阅 [skill:dotnet-wpf-migration]。
UWP .NET 9 预览版路径: Microsoft 宣布在预览版中支持 UWP 使用 .NET 9。这使 UWP 应用无需迁移到 WinUI 3,也能使用现代 .NET。如果完整迁移到 WinUI 的成本过高,但又需要现代 .NET 运行时功能,请评估此路径。
Agent 注意事项
- 不要混淆 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是 WinUI 3 中不可用的 UWP API。 - 不要认为必须使用 MSIX。 WinUI 3 支持通过
<WindowsPackageType>None</WindowsPackageType>进行非打包部署。只有在需要应用身份、Store 分发或需要应用身份的 Windows 集成功能时才使用 MSIX。 - 不要忘记
x:Bind默认值为OneTime。 与默认值为OneWay的{Binding}不同,x:Bind默认值为OneTime。对于初始绑定后会发生变化的属性,始终指定Mode=OneWay或Mode=TwoWay。 - 不要将目标设为低于 19041 的 Windows 10 版本。 Windows App SDK 1.6+ 要求最低版本为 build 19041(version 2004)。目标版本低于此版本会导致运行时故障。
- 不要在非打包应用中使用 Widgets 或 Mica。 这些功能要求通过 MSIX 打包部署并具有应用身份。在非打包模式下尝试使用这些功能会静默失败或引发异常。
- 不要将 CommunityToolkit.Mvvm 与手动实现的 INotifyPropertyChanged 混用。 始终统一使用
[ObservableProperty]。混用源生成和手写实现会导致细微的绑定错误。 - 不要忘记 Host 构建器的生命周期。 在
OnLaunched中调用_host.StartAsync(),并在窗口关闭时调用_host.StopAsync()。忘记管理生命周期会导致通过 DI 注册的IHostedService实例无法启动或停止。
前置条件
- .NET 8.0+,并安装 Windows 桌面工作负载
- Windows App SDK 1.6+(通过
UseWinUI自动引用) - Windows 10 版本 2004(build 19041)或更高版本
- Visual Studio 2022+(安装 Windows App SDK 工作负载),或使用 C# Dev Kit 的 VS Code
- 使用小组件时:Windows 11(build 22000+)