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