mcpField
D
dotnet-wpf-modern

dotnet-wpf-modern

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

关于 dotnet-wpf-modern

dotnet-wpf-modern 是面向编码 Agent 和开发者的 Skill,介绍 .NET 8+ WPF 的现代开发模式。它覆盖 Host builder、CommunityToolkit.Mvvm、性能、现代 C# 和 .NET 9+ Fluent 主题,并说明常见陷阱。

主要功能

  1. 01.NET 8+ Host builder 与依赖注入
  2. 02CommunityToolkit.Mvvm 源生成器
  3. 03渲染、启动和内存优化信息
  4. 04现代 C# 编码模式
  5. 05.NET 9+ Fluent 主题与系统主题

适用场景

  1. 01为 WPF 应用配置 Host builder
  2. 02使用 MVVM Toolkit 减少样板代码
  3. 03设置 Fluent 主题和系统深浅色模式
  4. 04检查修剪发布及异步代码的常见问题

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/wshaddix/dotnet-skills --skill dotnet-wpf-modern

安装后在 Claude Code 中输入/dotnet-wpf-modern确认已加载。

详细介绍

dotnet-wpf-modern

.NET 8+ 中的 WPF:Host builder 和依赖注入、使用 CommunityToolkit.Mvvm 源生成器实现 MVVM、硬件加速渲染改进、现代 C# 模式(记录、主构造函数、模式匹配)、Fluent 主题(.NET 9+)、系统主题检测,以及与 .NET Framework WPF 相比的变化。

**版本假设:**以 .NET 8.0+ 为基线(当前 LTS)。TFM 为 net8.0-windows。.NET 9 功能(Fluent 主题)会明确标注。

**范围边界:**本技能负责现代 .NET 上的 WPF 模式:Host builder、MVVM Toolkit、性能、现代 C#、主题。从 .NET Framework 迁移到 .NET 8+ 由 [skill:dotnet-wpf-migration] 负责。桌面测试由 [skill:dotnet-ui-testing-core] 负责。

**不在范围内:**WPF .NET Framework 模式(旧版)——本技能仅涵盖 .NET 8+。迁移指南——参见 [skill:dotnet-wpf-migration]。桌面测试——参见 [skill:dotnet-ui-testing-core]。通用 Native AOT 模式——参见 [skill:dotnet-native-aot]。UI 框架选择——参见 [skill:dotnet-ui-chooser]。

交叉引用:[skill:dotnet-ui-testing-core](桌面测试)、[skill:dotnet-winui](WinUI 3 模式)、[skill:dotnet-wpf-migration](迁移指南)、[skill:dotnet-native-aot](通用 AOT)、[skill:dotnet-ui-chooser](框架选择)、[skill:dotnet-accessibility](无障碍模式:AutomationProperties、AutomationPeer、UI Automation)。


.NET 8+ 的差异

.NET 8+ 中的 WPF 相较于 .NET Framework WPF 有了显著的现代化改进。项目格式、DI 模式、语言功能和运行时行为都发生了变化。

新建项目模板

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <OutputType>WinExe</OutputType>
    <TargetFramework>net8.0-windows</TargetFramework>
    <UseWPF>true</UseWPF>
    <Nullable>enable</Nullable>
    <ImplicitUsings>enable</ImplicitUsings>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="CommunityToolkit.Mvvm" Version="8.*" />
    <PackageReference Include="Microsoft.Extensions.Hosting" Version="8.*" />
  </ItemGroup>
</Project>

与 .NET Framework WPF 的主要差异:

  • SDK 样式的 .csproj(没有 packages.config,也没有 AssemblyInfo.cs)
  • 默认启用可空引用类型
  • 启用隐式 using
  • 使用 NuGet PackageReference 格式(而非 packages.config)
  • DI 不再使用 App.config——改用 Host builder
  • dotnet publish 会生成单个部署产物
  • 支持并行安装多个 .NET 版本(不依赖计算机范围的框架)
Host Builder 模式

现代 WPF 应用使用通用主机进行依赖注入、配置和日志记录,取代旧版 ServiceLocator 或手动 DI 方式。

// App.xaml.cs
public partial class App : Application
{
    private readonly IHost _host;

    public App()
    {
        _host = Host.CreateDefaultBuilder()
            .ConfigureAppConfiguration((context, config) =>
            {
                config.AddJsonFile("appsettings.json", optional: true);
            })
            .ConfigureServices((context, services) =>
            {
                // Services
                services.AddSingleton<INavigationService, NavigationService>();
                services.AddSingleton<IProductService, ProductService>();
                services.AddSingleton<ISettingsService, SettingsService>();

                // HTTP client
                services.AddHttpClient("api", client =>
                {
                    client.BaseAddress = new Uri(
                        context.Configuration["ApiBaseUrl"] ?? "https://api.example.com");
                });

                // ViewModels
                services.AddTransient<MainViewModel>();
                services.AddTransient<ProductListViewModel>();
                services.AddTransient<SettingsViewModel>();

                // Windows and pages
                services.AddSingleton<MainWindow>();
            })
            .Build();
    }

    protected override async void OnStartup(StartupEventArgs e)
    {
        await _host.StartAsync();

        var mainWindow = _host.Services.GetRequiredService<MainWindow>();
        mainWindow.Show();

        base.OnStartup(e);
    }

    protected override async void OnExit(ExitEventArgs e)
    {
        await _host.StopAsync();
        _host.Dispose();

        base.OnExit(e);
    }

    public static T GetService<T>() where T : class
    {
        var app = (App)Application.Current;
        return app._host.Services.GetRequiredService<T>();
    }
}

MVVM Toolkit

CommunityToolkit.Mvvm(Microsoft MVVM Toolkit)是现代 WPF 推荐使用的 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<Product> _products = [];

    [ObservableProperty]
    private bool _isLoading;

    [RelayCommand]
    private async Task LoadProductsAsync(CancellationToken ct)
    {
        IsLoading = true;
        try
        {
            var items = await _productService.GetProductsAsync(ct);
            Products = new ObservableCollection<Product>(items);
        }
        finally
        {
            IsLoading = false;
        }
    }

    [RelayCommand(CanExecute = nameof(CanSearch))]
    private async Task SearchAsync(CancellationToken ct)
    {
        var results = await _productService.SearchAsync(SearchTerm, ct);
        Products = new ObservableCollection<Product>(results);
    }

    private bool CanSearch() => !string.IsNullOrWhiteSpace(SearchTerm);
}
使用 MVVM Toolkit 进行 XAML 绑定
<Window x:Class="MyApp.Views.ProductListWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:vm="clr-namespace:MyApp.ViewModels"
        d:DataContext="{d:DesignInstance vm:ProductListViewModel}">

    <DockPanel>
        <StackPanel DockPanel.Dock="Top" Orientation="Horizontal" Margin="16">
            <TextBox Text="{Binding SearchTerm, UpdateSourceTrigger=PropertyChanged}"
                     Width="300" Margin="0,0,8,0" />
            <Button Content="Search" Command="{Binding SearchCommand}" />
        </StackPanel>

        <ListBox ItemsSource="{Binding Products}" Margin="16">
            <ListBox.ItemTemplate>
                <DataTemplate>
                    <StackPanel Orientation="Horizontal" Margin="4">
                        <TextBlock Text="{Binding Name}" FontWeight="Bold" Margin="0,0,12,0" />
                        <TextBlock Text="{Binding Price, StringFormat='{}{0:C}'}" Foreground="Gray" />
                    </StackPanel>
                </DataTemplate>
            </ListBox.ItemTemplate>
        </ListBox>
    </DockPanel>
</Window>

主要源生成器特性:

  • [ObservableProperty]——从后备字段生成带有 INotifyPropertyChanged 的属性
  • [RelayCommand]——从方法生成 ICommand(支持异步、取消和 CanExecute)
  • [NotifyPropertyChangedFor]——为依赖属性引发 PropertyChanged
  • [NotifyCanExecuteChangedFor]——属性变更时重新评估命令的 CanExecute

性能

.NET 8+ 中的 WPF 相较于 .NET Framework WPF,性能有了显著提升。

硬件加速渲染
  • DirectX 11 渲染路径是 .NET 8+ 的默认路径(.NET Framework 使用 DirectX 9)
  • GPU 加速的文本渲染可提升文本清晰度,并降低文本密集型 UI 的 CPU 使用率
  • 运行时改进(动态 PGO、栈上替换)可减少 GC 压力
启动时间
  • ReadyToRun(R2R)——预编译程序集可减少启动时的 JIT 开销
  • 分层编译——快速启动并逐步优化
  • 修剪就绪——.NET 8+ WPF 支持 IL 修剪,可缩小部署体积

<PropertyGroup>
  <PublishTrimmed>true</PublishTrimmed>
  <TrimMode>partial</TrimMode>
  
</PropertyGroup>

**修剪注意事项:**WPF 高度依赖 XAML 反射来进行数据绑定和资源解析。请使用 TrimMode=partial(而非 full),并进行全面测试。与基于字符串的绑定相比,已编译绑定和 x:Type 引用更适合修剪。

内存与 GC
  • 冻结对象堆(.NET 8)——静态字符串和单例分配会放置在不会进行垃圾回收的堆段上
  • 动态 PGO——运行时配置文件会指导 JIT 针对热点路径进行优化
  • 更小的工作集——与 .NET Framework CLR 相比,.NET 8 运行时使用的基准内存更少
预期改进

与 .NET Framework 4.8 相比,WPF on .NET 8 在关键指标上有可衡量的改进。具体数值取决于工作负载、硬件和应用复杂度——请务必针对自己的场景进行基准测试:

  • 冷启动——得益于 ReadyToRun、分层编译以及框架初始化开销降低,启动速度显著加快
  • UI 虚拟化——改进后的渲染管线和 GC 可减少大型 ItemsControl(ListBox、DataGrid)的处理时间
  • GC 暂停——受益于 .NET 8 GC 的改进(动态 PGO、冻结对象堆、固定对象堆),Gen2 回收的暂停时间更短、发生频率更低
  • 内存占用——与 .NET Framework CLR 相比,基准工作集更小

现代 C#

.NET 8+ WPF 项目可以使用最新的 C# 语言特性。这些模式可以减少样板代码并提高代码清晰度。

数据模型使用记录
// Immutable data models
public record Product(string Name, decimal Price, string Category);

// Records with computed properties
public record ProductViewModel(Product Product)
{
    public string DisplayPrice => Product.Price.ToString("C");
    public string Summary => $"{Product.Name} - {DisplayPrice}";
}
服务中的主构造函数
// Service with primary constructor (C# 12)
public class ProductService(HttpClient httpClient, ILogger<ProductService> logger)
    : IProductService
{
    public async Task<IReadOnlyList<Product>> GetProductsAsync(CancellationToken ct)
    {
        logger.LogInformation("Fetching products");
        var response = await httpClient.GetAsync("/products", ct);
        response.EnsureSuccessStatusCode();
        return await response.Content.ReadFromJsonAsync<List<Product>>(ct) ?? [];
    }
}
转换器中的模式匹配
// Modern converter using pattern matching (C# 11+)
public class StatusToColorConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        return value switch
        {
            OrderStatus.Pending => Brushes.Orange,
            OrderStatus.Processing => Brushes.Blue,
            OrderStatus.Shipped => Brushes.Green,
            OrderStatus.Cancelled => Brushes.Red,
            _ => Brushes.Gray
        };
    }

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        => throw new NotSupportedException();
}
集合表达式
// C# 12 collection expressions
[ObservableProperty]
private ObservableCollection<Product> _products = [];

// In methods
List<string> categories = ["Electronics", "Clothing", "Books"];

主题

Fluent 主题(.NET 9+)

.NET 9 为 WPF 引入了 Fluent 主题,提供现代化的 Windows 11 风格视觉效果。它采用圆角、更新后的控件模板,并支持 Mica/Acrylic 背景效果。


<Application x:Class="MyApp.App"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             ThemeMode="System"
             StartupUri="MainWindow.xaml">
</Application>

或者在代码隐藏中使用:

// App.xaml.cs: set theme programmatically (.NET 9+)
Application.Current.ThemeMode = ThemeMode.System; // or ThemeMode.Light / ThemeMode.Dark

// Per-window theming is also supported
mainWindow.ThemeMode = ThemeMode.Dark;

ThemeMode 的值:

  • None —— 经典 WPF 外观(不使用 Fluent 样式)
  • Light —— 使用浅色配色的 Fluent 主题
  • Dark —— 使用深色配色的 Fluent 主题
  • System —— 遵循 Windows 系统的浅色/深色主题设置

Fluent 主题包含:

  • 按钮、文本框和列表项采用圆角
  • 与 Windows 11 设计语言一致的更新版调色板
  • 支持 Mica 和 Acrylic 背景效果(Windows 11)
  • 与 Windows 系统设置中的强调色集成
  • 根据系统主题使用深色/浅色模式
检测系统主题

检测并响应 Windows 系统的浅色/深色主题:

// Detect system theme
public static bool IsDarkTheme()
{
    using var key = Microsoft.Win32.Registry.CurrentUser.OpenSubKey(
        @"Software\Microsoft\Windows\CurrentVersion\Themes\Personalize");
    var value = key?.GetValue("AppsUseLightTheme");
    return value is int i && i == 0;
}

// Listen for theme changes
SystemEvents.UserPreferenceChanged += (sender, args) =>
{
    if (args.Category == UserPreferenceCategory.General)
    {
        // Theme may have changed; re-read and apply
        ApplyTheme(IsDarkTheme() ? AppTheme.Dark : AppTheme.Light);
    }
};
自定义主题

对于 .NET 9 之前的应用或自定义品牌样式,请使用资源字典:


<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
    <SolidColorBrush x:Key="WindowBackground" Color="#1E1E1E" />
    <SolidColorBrush x:Key="TextForeground" Color="#FFFFFF" />
    <SolidColorBrush x:Key="AccentBrush" Color="#0078D7" />
</ResourceDictionary>
// Switch themes at runtime
public void ApplyTheme(AppTheme theme)
{
    var themeUri = theme switch
    {
        AppTheme.Dark => new Uri("Themes/DarkTheme.xaml", UriKind.Relative),
        AppTheme.Light => new Uri("Themes/LightTheme.xaml", UriKind.Relative),
        _ => throw new ArgumentOutOfRangeException(nameof(theme))
    };

    Application.Current.Resources.MergedDictionaries.Clear();
    Application.Current.Resources.MergedDictionaries.Add(
        new ResourceDictionary { Source = themeUri });
}

Agent 注意事项

  1. 不要在 .NET 8+ 项目中使用 .NET Framework WPF 模式。 避免使用 App.config 配置 DI(改用 Host builder)、packages.config(改用 PackageReference)、ServiceLocator 模式(改用构造函数注入)和 AssemblyInfo.cs(改用 <PropertyGroup> 属性)。
  2. 不要使用已弃用的 WPF API。 BitmapEffect(已由 Effect/ShaderEffect 取代)、DrawingContext.PushEffect(已移除),以及在禁用硬件加速时使用 VisualBrush 平铺模式均已过时。
  3. 使用 MVVM Toolkit 时,不要混用 {Binding} 和手动实现的 INotifyPropertyChanged。 请始终使用 [ObservableProperty] 源生成器。混用这些方式会导致隐蔽的绑定更新问题。
  4. 不要在异步代码中使用 Dispatcher.Invoke。 在异步方法中,await 会自动切回 UI 线程(默认的 ConfigureAwait(true) 行为)。在非异步上下文中(计时器、COM 回调、本机互操作)仍适合使用 Dispatcher.Invoke/BeginInvoke。
  5. 不要为 WPF 应用设置 TrimMode=full。 WPF 广泛使用 XAML 反射。请使用 TrimMode=partial,并在修剪后测试所有视图,以发现缺失的类型。
  6. 不要忘记 Host builder 的生命周期。 在 OnStartup 中调用 _host.StartAsync(),并在 OnExit 中调用 _host.StopAsync()。忘记管理生命周期会导致通过 DI 注册的 IHostedService 实例始终无法启动或停止。
  7. 使用 Fluent 主题时,不要将颜色硬编码。 请引用主题资源({DynamicResource SystemAccentColor}),以确保与浅色/深色模式及系统强调色变化兼容。

前置条件

  • .NET 8.0+,并安装 Windows 桌面工作负载
  • TFM:net8.0-windows(WPF 无需指定 Windows SDK 版本)
  • Visual Studio 2022+、带有 C# Dev Kit 的 VS Code,或 JetBrains Rider
  • 使用 Fluent 主题:需要 .NET 9+

参考资料

评论

支持 Markdown