mcpField
D
dotnet-winui

dotnet-winui

github.com/wshaddix开发8699 次安装35 周前更新
代码仓库

关于 dotnet-winui

使用 Windows App SDK 构建 WinUI 3 应用的开发技能,面向 .NET 开发者,涵盖 XAML、MVVM、部署模式、Windows 集成和 UWP 迁移。

主要功能

  1. 01WinUI 3 项目设置
  2. 02编译绑定与延迟加载 XAML
  3. 03CommunityToolkit.Mvvm 模式
  4. 04MSIX 与非打包部署
  5. 05通知、小组件与生命周期集成
  6. 06UWP 迁移指导

适用场景

  1. 01启动 WinUI 3 应用
  2. 02组织视图与视图模型
  3. 03选择应用部署模式
  4. 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 Installerxcopy、安装程序(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 清单中注册该提供程序。

主要步骤:

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

有关完整的接口约定和清单注册,请参阅 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.XamlMicrosoft.UI.Xaml
Windows.UI.Xaml.ControlsMicrosoft.UI.Xaml.Controls
Windows.UI.Xaml.MediaMicrosoft.UI.Xaml.Media
Windows.UI.Xaml.InputMicrosoft.UI.Xaml.Input
Windows.UI.CompositionMicrosoft.UI.Composition
Windows.UI.TextMicrosoft.UI.Text
Windows.UI.ColorsMicrosoft.UI.Colors

保持不变: Windows.Storage、Windows.Networking、Windows.Security、Windows.ApplicationModel、Windows.Devices——这些 WinRT API 仍位于 Windows.* 命名空间中。

API 替换
UWP APIWinUI 3 替代项
CoreApplication.MainViewApp.MainWindow(自行跟踪窗口引用)
CoreDispatcher.RunAsyncDispatcherQueue.TryEnqueue
Window.Current在 App 类中手动跟踪窗口引用
ApplicationView.Titlewindow.Title = "..."
CoreWindow.GetForCurrentThread不可用;键盘 API 请使用 InputKeyboardSource
SystemNavigationManager.BackRequestedNavigationView.BackRequested
迁移步骤
  1. 使用 Windows App SDK 模板创建新的 WinUI 3 项目
  2. 复制源文件并更新命名空间(将 Windows.UI.Xaml 改为 Microsoft.UI.Xaml)
  3. 更新所有 .xaml 文件中的 XAML 命名空间
  4. 替换已弃用的 API(见上表)
  5. 将打包格式从 .appxmanifest UWP 格式迁移为 Windows App SDK 格式
  6. 将 NuGet 包更新到兼容 Windows App SDK 的版本
  7. 测试 Windows 集成功能(通知、后台任务、文件关联)

如需了解跨框架的完整迁移路径指南,请参阅 [skill:dotnet-wpf-migration]。

UWP .NET 9 预览版路径: Microsoft 宣布在预览版中支持 UWP 使用 .NET 9。这使 UWP 应用无需迁移到 WinUI 3,也能使用现代 .NET。如果完整迁移到 WinUI 的成本过高,但又需要现代 .NET 运行时功能,请评估此路径。


Agent 注意事项

  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. 不要使用 CoreDispatcher。 将 CoreDispatcher.RunAsync() 替换为 DispatcherQueue.TryEnqueue()。CoreDispatcher 是 WinUI 3 中不可用的 UWP API。
  4. 不要认为必须使用 MSIX。 WinUI 3 支持通过 <WindowsPackageType>None</WindowsPackageType> 进行非打包部署。只有在需要应用身份、Store 分发或需要应用身份的 Windows 集成功能时才使用 MSIX。
  5. 不要忘记 x:Bind 默认值为 OneTime。 与默认值为 OneWay 的 {Binding} 不同,x:Bind 默认值为 OneTime。对于初始绑定后会发生变化的属性,始终指定 Mode=OneWay 或 Mode=TwoWay。
  6. 不要将目标设为低于 19041 的 Windows 10 版本。 Windows App SDK 1.6+ 要求最低版本为 build 19041(version 2004)。目标版本低于此版本会导致运行时故障。
  7. 不要在非打包应用中使用 Widgets 或 Mica。 这些功能要求通过 MSIX 打包部署并具有应用身份。在非打包模式下尝试使用这些功能会静默失败或引发异常。
  8. 不要将 CommunityToolkit.Mvvm 与手动实现的 INotifyPropertyChanged 混用。 始终统一使用 [ObservableProperty]。混用源生成和手写实现会导致细微的绑定错误。
  9. 不要忘记 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+)

参考资料

评论

支持 Markdown