.NETWPF现代化开发 dotnet-wpf-modern

本技能专注于在.NET 8+平台上现代化WPF桌面应用程序开发,涵盖主机构建器、依赖注入、MVVM模式、Fluent主题、性能优化和现代C#特性,旨在提升开发效率和应用程序性能。关键词:.NET, WPF, 现代化, MVVM, 依赖注入, 性能优化, C# 现代特性。

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

name: .NET WPF 现代开发 description: “在 .NET 8+ 上构建 WPF。主机构建器、MVVM 工具包、Fluent 主题、性能、现代 C# 模式。” user-invocable: false

.NET WPF 现代开发

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

版本假设: .NET 8.0+ 基线(当前 LTS)。TFM net8.0-windows。.NET 9 特性(Fluent 主题)明确标记。

范围

  • WPF .NET 8+ 项目设置(SDK 风格)
  • 主机构建器和依赖注入
  • 使用 CommunityToolkit.Mvvm 源生成器的 MVVM
  • Fluent 主题(.NET 9+)和系统主题检测
  • 硬件加速渲染改进
  • 现代 C# 模式(记录、主构造函数、模式匹配)

超出范围

  • WPF .NET Framework 模式(遗留)
  • 迁移指导 – 参见 [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 自动化)。


.NET 8+ 差异

WPF 在 .NET 8+ 上是从 .NET Framework WPF 的显著现代化。项目格式、DI 模式、语言特性和运行时行为都已改变。

新项目模板

<!-- MyWpfApp.csproj (SDK 风格) -->
<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
  • App.config 用于 DI – 使用主机构建器
  • dotnet publish 产生单一部署工件
  • 并排 .NET 安装(无机器范围框架依赖)

主机构建器模式

现代 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.AddSingleton<INavigationService, NavigationService>();
                services.AddSingleton<IProductService, ProductService>();
                services.AddSingleton<ISettingsService, SettingsService>();

                // HTTP 客户端
                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>();

                // 窗口和页面
                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 工具包

CommunityToolkit.Mvvm(Microsoft MVVM 工具包)是现代 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);
}

XAML 绑定与 MVVM 工具包

<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="搜索" 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

性能

WPF 在 .NET 8+ 上相比 .NET Framework WPF 提供了显著的性能改进。

硬件加速渲染

  • DirectX 11 渲染路径 在 .NET 8+ 上默认(从 .NET Framework 的 DirectX 9 升级)
  • GPU 加速文本渲染 提高文本清晰度并减少文本密集型 UI 的 CPU 使用
  • 减少 GC 压力 来自运行时改进(动态 PGO、栈上替换)

启动时间

  • ReadyToRun (R2R) – 预编译程序集减少启动时的 JIT 开销
  • 分层编译 – 快速启动并逐步优化
  • 修剪准备.NET 8+ WPF 支持 IL 修剪以减小部署大小
<!-- 启用修剪以减小部署 -->
<PropertyGroup>
  <PublishTrimmed>true</PublishTrimmed>
  <TrimMode>partial</TrimMode>
  <!-- WPF 应用由于反射使用需要部分修剪模式 -->
</PropertyGroup>

修剪注意事项: WPF 大量依赖 XAML 反射进行数据绑定和资源解析。使用 TrimMode=partial(非 full)并彻底测试。编译绑定和 x:Type 引用比基于字符串的绑定更安全。

内存和 GC

  • 冻结对象堆 (.NET 8) – 静态字符串和单例分配放置在非收集堆段
  • 动态 PGO – 运行时配置文件指导热路径的 JIT 优化
  • 减少工作集 – .NET 8 运行时比 .NET Framework CLR 使用更少的基础内存

预期改进

WPF 在 .NET 8 上相比 .NET Framework 4.8 在关键指标上提供了可测量的改进。具体数字取决于工作负载、硬件和应用程序复杂性 – 始终对您自己的场景进行基准测试:

  • 冷启动 – 由于 ReadyToRun、分层编译和减少的框架初始化开销,显著更快
  • UI 虚拟化 – 改进的渲染管道和 GC 减少了大型 ItemsControls(ListBox、DataGrid)的时间
  • GC 暂停 – 由于 .NET 8 GC 改进(动态 PGO、冻结对象堆、固定对象堆),Gen2 收集更短且更少频繁
  • 内存占用 – 相比 .NET Framework CLR,更低的基础工作集

现代 C#

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

记录用于数据模型

// 不可变数据模型
public record Product(string Name, decimal Price, string Category);

// 具有计算属性的记录
public record ProductViewModel(Product Product)
{
    public string DisplayPrice => Product.Price.ToString("C");
    public string Summary => $"{Product.Name} - {DisplayPrice}";
}

服务中的主构造函数

// 具有主构造函数的服务(C# 12)
public class ProductService(HttpClient httpClient, ILogger<ProductService> logger)
    : IProductService
{
    public async Task<IReadOnlyList<Product>> GetProductsAsync(CancellationToken ct)
    {
        logger.LogInformation("获取产品");
        var response = await httpClient.GetAsync("/products", ct);
        response.EnsureSuccessStatusCode();
        return await response.Content.ReadFromJsonAsync<List<Product>>(ct) ?? [];
    }
}

转换器中的模式匹配

// 使用模式匹配的现代转换器(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 集合表达式
[ObservableProperty]
private ObservableCollection<Product> _products = [];

// 在方法中
List<string> categories = ["电子产品", "服装", "书籍"];

主题

Fluent 主题 (.NET 9+)

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

<!-- App.xaml: 通过 ThemeMode 属性启用 Fluent 主题 (.NET 9+) -->
<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: 以编程方式设置主题 (.NET 9+)
Application.Current.ThemeMode = ThemeMode.System; // 或 ThemeMode.Light / ThemeMode.Dark

// 每个窗口的主题也支持
mainWindow.ThemeMode = ThemeMode.Dark;

ThemeMode 值:

  • None – 经典 WPF 外观(无 Fluent 样式)
  • Light – 具有浅色的 Fluent 主题
  • Dark – 具有深色的 Fluent 主题
  • System – 跟随 Windows 系统浅色/深色主题设置

Fluent 主题包括:

  • 按钮、文本框和列表项的圆角
  • 与 Windows 11 设计语言对齐的更新调色板
  • Mica 和 Acrylic 背景支持(Windows 11)
  • 与 Windows 系统设置集成的强调色
  • 跟随系统主题的深色/浅色模式

系统主题检测

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

// 检测系统主题
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;
}

// 监听主题更改
SystemEvents.UserPreferenceChanged += (sender, args) =>
{
    if (args.Category == UserPreferenceCategory.General)
    {
        // 主题可能已更改;重新读取并应用
        ApplyTheme(IsDarkTheme() ? AppTheme.Dark : AppTheme.Light);
    }
};

自定义主题

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

<!-- Themes/DarkTheme.xaml -->
<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>
// 在运行时切换主题
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 });
}

代理注意事项

  1. 不要在 .NET 8+ 项目中使用 .NET Framework WPF 模式。 避免使用 App.config 进行 DI(使用主机构建器)、packages.config(使用 PackageReference)、ServiceLocator 模式(使用构造函数注入)和 AssemblyInfo.cs(使用 <PropertyGroup> 属性)。
  2. 不要使用已弃用的 WPF API。 BitmapEffect(替换为 Effect/ShaderEffect)、DrawingContext.PushEffect(已移除)以及禁用硬件加速的 VisualBrush 平铺模式已过时。
  3. 在使用 MVVM 工具包时,不要混合 {Binding} 和手动 INotifyPropertyChanged 一致使用 [ObservableProperty] 源生成器。混合方法会导致微妙的绑定更新错误。
  4. 不要在异步代码中使用 Dispatcher.Invoke 在异步方法中,await 会自动切换回 UI 线程(默认 ConfigureAwait(true) 行为)。Dispatcher.Invoke/BeginInvoke 仍适用于非异步上下文(计时器、COM 回调、本机互操作)。
  5. 不要为 WPF 应用设置 TrimMode=full WPF 大量使用 XAML 反射。使用 TrimMode=partial 并在修剪后测试所有视图以捕获缺失的类型。
  6. 不要忘记主机构建器的生命周期。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+、VS Code 带有 C# Dev Kit 或 JetBrains Rider
  • 对于 Fluent 主题:.NET 9+

参考