WinUI3开发 dotnet-winui

这个技能专注于使用 .NET 和 WinUI 3 开发 Windows 桌面应用程序,涵盖项目设置、XAML 编译绑定、MVVM 架构、MSIX 和非打包部署、Windows 集成功能以及 UWP 迁移指南。关键词:WinUI 3、.NET、Windows App SDK、XAML、MVVM、MSIX、UWP 迁移、桌面应用开发。

前端开发 0 次安装 0 次浏览 更新于 3/6/2026

名称: 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 应用程序通常使用 NavigationViewFrame 进行页面导航:

<!-- 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 清单中注册。

关键步骤:

  1. 实现 IWidgetProvider 接口(方法:CreateWidgetDeleteWidgetOnActionInvokedOnWidgetContextChangedOnCustomizationRequestedActivateDeactivate
  2. 在 MSIX 清单中将提供程序注册为 COM 类
  3. 使用 Adaptive Cards JSON 格式定义小组件模板
  4. 从提供程序方法返回更新的小组件内容

参阅 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.StorageWindows.NetworkingWindows.SecurityWindows.ApplicationModelWindows.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

迁移步骤

  1. 创建新的 WinUI 3 项目 使用 Windows App SDK 模板
  2. 复制源文件 并更新命名空间(Windows.UI.XamlMicrosoft.UI.Xaml
  3. 更新所有 .xaml 文件中的 XAML 命名空间
  4. 替换已弃用的 API(见上表)
  5. 迁移打包.appxmanifest UWP 格式到 Windows App SDK 格式
  6. 更新 NuGet 包 到 Windows App SDK 兼容版本
  7. 测试 Windows 集成 功能(通知、后台任务、文件关联)

有关跨框架的全面迁移路径指南,请参阅 [技能:dotnet-wpf-migration]。

UWP .NET 9 预览路径: 微软宣布了 .NET 9 上的 UWP 支持作为预览。这允许 UWP 应用程序使用现代 .NET 而无需迁移到 WinUI 3。如果完整 WinUI 迁移成本过高,但需要现代 .NET 运行时功能,请评估此路径。


代理陷阱

  1. 不要混淆 UWP XAML 与 WinUI 3 XAML。 根命名空间从 Windows.UI.Xaml 更改为 Microsoft.UI.Xaml。使用 Windows.UI.Xaml.* 类型的代码在 WinUI 3 项目中无法编译。
  2. 不要使用 Window.Current WinUI 3 没有静态 Window.Current 属性。在 App 类中手动跟踪窗口引用,并通过 DI 或静态属性传递。
  3. 不要使用 CoreDispatcherCoreDispatcher.RunAsync() 替换为 DispatcherQueue.TryEnqueue()CoreDispatcher 是 UWP API,在 WinUI 3 中不可用。
  4. 不要假设 MSIX 是必需的。 WinUI 3 支持通过 <WindowsPackageType>None</WindowsPackageType> 进行非打包部署。仅当需要应用程序身份、商店分发或需要它的 Windows 集成功能时使用 MSIX。
  5. 不要忘记 x:Bind 默认为 OneTime{Binding} 默认为 OneWay 不同,x:Bind 默认为 OneTime。对于初始绑定后更改的属性,始终指定 Mode=OneWayMode=TwoWay
  6. 不要目标低于 19041 的 Windows 10 构建。 Windows App SDK 1.6+ 需要最低构建 19041(版本 2004)。目标更低构建会导致运行时失败。
  7. 不要在非打包应用程序中使用小组件或 Mica。 这些功能需要具有应用程序身份的 MSIX 打包部署。在非打包模式下尝试使用它们会静默失败或抛出异常。
  8. 不要混合 CommunityToolkit.Mvvm 与手动 INotifyPropertyChanged。 一致使用 [ObservableProperty]。混合源生成和手写实现会导致微妙的绑定错误。
  9. 不要忘记主机构建器生命周期。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+)

参考