dotnet-wpf-modern
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
Chinesedotnet-wpf-modern
dotnet-wpf-modern
WPF on .NET 8+: Host builder and dependency injection, MVVM with CommunityToolkit.Mvvm source generators, hardware-accelerated rendering improvements, modern C# patterns (records, primary constructors, pattern matching), Fluent theme (.NET 9+), system theme detection, and what changed from .NET Framework WPF.
Version assumptions: .NET 8.0+ baseline (current LTS). TFM . .NET 9 features (Fluent theme) explicitly marked.
net8.0-windows.NET 8+上的WPF:主机生成器与依赖注入、基于CommunityToolkit.Mvvm源代码生成器的MVVM实现、硬件加速渲染优化、现代C#模式(记录类型、主构造函数、模式匹配)、Fluent主题(.NET 9+)、系统主题检测,以及与.NET Framework WPF的差异。
版本说明:以.NET 8.0+为基准(当前长期支持版本),目标框架为。.NET 9新增功能(如Fluent主题)会明确标注。
net8.0-windowsScope
适用范围
- WPF .NET 8+ project setup (SDK-style)
- Host builder and dependency injection
- MVVM with CommunityToolkit.Mvvm source generators
- Fluent theme (.NET 9+) and system theme detection
- Hardware-accelerated rendering improvements
- Modern C# patterns (records, primary constructors, pattern matching)
- .NET 8+ WPF项目搭建(SDK风格)
- 主机生成器与依赖注入
- 基于CommunityToolkit.Mvvm源代码生成器的MVVM实现
- Fluent主题(.NET 9+)与系统主题检测
- 硬件加速渲染优化
- 现代C#模式(记录类型、主构造函数、模式匹配)
Out of scope
不包含内容
- WPF .NET Framework patterns (legacy)
- Migration guidance -- see [skill:dotnet-wpf-migration]
- Desktop testing -- see [skill:dotnet-ui-testing-core]
- General Native AOT patterns -- see [skill:dotnet-native-aot]
- UI framework selection -- see [skill:dotnet-ui-chooser]
Cross-references: [skill:dotnet-ui-testing-core] for desktop testing, [skill:dotnet-winui] for WinUI 3 patterns, [skill:dotnet-wpf-migration] for migration guidance, [skill:dotnet-native-aot] for general AOT, [skill:dotnet-ui-chooser] for framework selection, [skill:dotnet-accessibility] for accessibility patterns (AutomationProperties, AutomationPeer, UI Automation).
- .NET Framework WPF的传统开发模式
- 迁移指南——请参考[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],WinUI 3开发模式请参考[skill:dotnet-winui],迁移指南请参考[skill:dotnet-wpf-migration],通用AOT请参考[skill:dotnet-native-aot],框架选择请参考[skill:dotnet-ui-chooser],无障碍模式请参考[skill:dotnet-accessibility](AutomationProperties、AutomationPeer、UI自动化)。
.NET 8+ Differences
.NET 8+的差异
WPF on .NET 8+ is a significant modernization from .NET Framework WPF. The project format, DI pattern, language features, and runtime behavior have all changed.
.NET 8+上的WPF相比.NET Framework WPF有重大现代化改进,项目格式、依赖注入模式、语言特性及运行时行为均有变化。
New Project Template
新项目模板
xml
<!-- MyWpfApp.csproj (SDK-style) -->
<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>Key differences from .NET Framework WPF:
- SDK-style (no
.csproj, nopackages.config)AssemblyInfo.cs - Nullable reference types enabled by default
- Implicit usings enabled
- NuGet format (not
PackageReference)packages.config - No for DI -- use Host builder
App.config - produces a single deployment artifact
dotnet publish - Side-by-side .NET installation (no machine-wide framework dependency)
xml
<!-- MyWpfApp.csproj (SDK-style) -->
<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 - 生成单一部署产物
dotnet publish - 支持.NET并行安装(无全局框架依赖)
Host Builder Pattern
主机生成器模式
Modern WPF apps use the generic host for dependency injection, configuration, and logging -- replacing the legacy or manual DI approaches.
ServiceLocatorcsharp
// 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>();
}
}现代WPF应用使用通用主机实现依赖注入、配置及日志功能,替代传统的或手动依赖注入方式。
ServiceLocatorcsharp
// 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");
});
// 视图模型
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 Toolkit
MVVM工具包
CommunityToolkit.Mvvm (Microsoft MVVM Toolkit) is the recommended MVVM framework for modern WPF. It uses source generators to eliminate boilerplate.
csharp
// 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);
}CommunityToolkit.Mvvm(Microsoft MVVM工具包)是现代WPF推荐使用的MVVM框架,通过源代码生成器消除样板代码。
csharp
// 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 Binding with MVVM Toolkit
结合MVVM工具包的XAML绑定
xml
<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>Key source generator attributes:
- -- generates property with
[ObservableProperty]from a backing fieldINotifyPropertyChanged - -- generates
[RelayCommand]from a method (supports async, cancellation,ICommand)CanExecute - -- raises
[NotifyPropertyChangedFor]for dependent propertiesPropertyChanged - -- re-evaluates command
[NotifyCanExecuteChangedFor]when property changesCanExecute
xml
<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
Performance
性能优化
WPF on .NET 8+ delivers significant performance improvements over .NET Framework WPF.
.NET 8+上的WPF相比.NET Framework WPF有显著的性能提升。
Hardware-Accelerated Rendering
硬件加速渲染
- DirectX 11 rendering path is the default on .NET 8+ (up from DirectX 9 on .NET Framework)
- GPU-accelerated text rendering improves text clarity and reduces CPU usage for text-heavy UIs
- Reduced GC pressure from runtime improvements (dynamic PGO, on-stack replacement)
- .NET 8+默认使用DirectX 11渲染路径(.NET Framework使用DirectX 9)
- GPU加速文本渲染提升了文本清晰度,降低了文本密集型UI的CPU占用
- 运行时优化(动态PGO、栈上替换)减少了GC压力
Startup Time
启动时间
- ReadyToRun (R2R) -- pre-compiled assemblies reduce JIT overhead at startup
- Tiered compilation -- fast startup with progressive optimization
- Trimming readiness -- WPF supports IL trimming for smaller deployment size
.NET 8+
xml
<!-- Enable trimming for smaller deployment -->
<PropertyGroup>
<PublishTrimmed>true</PublishTrimmed>
<TrimMode>partial</TrimMode>
<!-- WPF apps need partial trim mode due to reflection usage -->
</PropertyGroup>Trimming caveat: WPF relies heavily on XAML reflection for data binding and resource resolution. Use (not ) and test thoroughly. Compiled bindings and references are safer than string-based bindings for trimming.
TrimMode=partialfullx:Type- ReadyToRun(R2R):预编译程序集减少启动时的JIT开销
- 分层编译:快速启动并逐步优化
- 支持裁剪:.NET 8+ WPF支持IL裁剪以减小部署体积
xml
<!-- 启用裁剪以减小部署体积 -->
<PropertyGroup>
<PublishTrimmed>true</PublishTrimmed>
<TrimMode>partial</TrimMode>
<!-- WPF应用需要使用partial裁剪模式,因存在反射使用场景 -->
</PropertyGroup>裁剪注意事项:WPF大量依赖XAML反射实现数据绑定和资源解析。请使用(而非),并在裁剪后测试所有视图以避免类型缺失问题。编译绑定和引用比字符串绑定更适合裁剪场景。
TrimMode=partialfullx:TypeMemory and GC
内存与GC
- Frozen object heap (.NET 8) -- static strings and singleton allocations placed on non-collected heap segments
- Dynamic PGO -- runtime profiles guide JIT optimizations for hot paths
- Reduced working set -- .NET 8 runtime uses less baseline memory than .NET Framework CLR
- 冻结对象堆(.NET 8):静态字符串和单例对象分配在非回收堆段
- 动态PGO:运行时配置文件指导JIT优化热点路径
- 降低工作集:.NET 8运行时的基线内存占用低于.NET Framework CLR
Expected Improvements
预期提升效果
WPF on .NET 8 delivers measurable improvements over .NET Framework 4.8 across key metrics. Exact numbers depend on workload, hardware, and application complexity -- always benchmark your own scenarios:
- Cold startup -- significantly faster due to ReadyToRun, tiered compilation, and reduced framework initialization overhead
- UI virtualization -- improved rendering pipeline and GC reduce time for large ItemsControls (ListBox, DataGrid)
- GC pauses -- shorter and less frequent Gen2 collections from .NET 8 GC improvements (Dynamic PGO, frozen object heap, pinned object heap)
- Memory footprint -- lower baseline working set compared to .NET Framework CLR
.NET 8+上的WPF在关键指标上相比.NET Framework 4.8有可衡量的提升,具体数值取决于工作负载、硬件及应用复杂度,请始终针对自身场景进行基准测试:
- 冷启动:ReadyToRun、分层编译及框架初始化开销减少,启动速度显著提升
- UI虚拟化:改进的渲染管道和GC优化减少了大型ItemsControls(ListBox、DataGrid)的处理时间
- GC停顿:.NET 8 GC优化(动态PGO、冻结对象堆、固定对象堆)缩短了Gen2垃圾回收停顿时间并降低频率
- 内存占用:基线工作集低于.NET Framework CLR
Modern C#
现代C#特性
.NET 8+ WPF projects can use the latest C# language features. These patterns reduce boilerplate and improve code clarity.
.NET 8+ WPF项目可使用最新C#语言特性,这些模式减少了样板代码并提升了代码可读性。
Records for Data Models
记录类型作为数据模型
csharp
// 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}";
}csharp
// 不可变数据模型
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}";
}Primary Constructors in Services
服务中的主构造函数
csharp
// 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) ?? [];
}
}csharp
// 使用主构造函数的服务(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) ?? [];
}
}Pattern Matching in Converters
转换器中的模式匹配
csharp
// 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();
}csharp
// 使用模式匹配的现代转换器(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();
}Collection Expressions
集合表达式
csharp
// C# 12 collection expressions
[ObservableProperty]
private ObservableCollection<Product> _products = [];
// In methods
List<string> categories = ["Electronics", "Clothing", "Books"];csharp
// C# 12集合表达式
[ObservableProperty]
private ObservableCollection<Product> _products = [];
// 方法中使用
List<string> categories = ["Electronics", "Clothing", "Books"];Theming
主题设置
Fluent Theme (.NET 9+)
Fluent主题(.NET 9+)
.NET 9 introduces the Fluent theme for WPF, providing modern Windows 11-style visuals. It applies rounded corners, updated control templates, and Mica/Acrylic backdrop support.
xml
<!-- App.xaml: enable Fluent theme (.NET 9+) via ThemeMode property -->
<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>Or in code-behind:
csharp
// 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 values:
- -- classic WPF look (no Fluent styling)
None - -- Fluent theme with light colors
Light - -- Fluent theme with dark colors
Dark - -- follow Windows system light/dark theme setting
System
Fluent theme includes:
- Rounded corners on buttons, text boxes, and list items
- Updated color palette aligned with Windows 11 design language
- Mica and Acrylic backdrop support (Windows 11)
- Accent color integration with Windows system settings
- Dark/light mode following system theme
.NET 9为WPF引入了Fluent主题,提供现代化的Windows 11风格视觉效果,包括圆角、更新的控件模板及Mica/Acrylic背景支持。
xml
<!-- 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>或在代码后台设置:
csharp
// App.xaml.cs:以编程方式设置主题(.NET 9+)
Application.Current.ThemeMode = ThemeMode.System; // 或ThemeMode.Light / ThemeMode.Dark
// 也支持按窗口设置主题
mainWindow.ThemeMode = ThemeMode.Dark;ThemeMode取值:
- :经典WPF外观(无Fluent样式)
None - :Fluent主题浅色模式
Light - :Fluent主题深色模式
Dark - :跟随Windows系统的明暗主题设置
System
Fluent主题包含:
- 按钮、文本框及列表项的圆角设计
- 与Windows 11设计语言对齐的更新调色板
- 支持Mica和Acrylic背景(Windows 11)
- 与Windows系统设置集成的强调色
- 跟随系统主题的明暗模式切换
System Theme Detection
系统主题检测
Detect and respond to the Windows system light/dark theme:
csharp
// 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);
}
};检测并响应Windows系统的明暗主题变化:
csharp
// 检测系统主题
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);
}
};Custom Themes
自定义主题
For pre-.NET 9 apps or custom branding, use resource dictionaries:
xml
<!-- 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>csharp
// 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 });
}对于.NET 9之前的版本或需要自定义品牌风格的应用,可使用资源字典:
xml
<!-- 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>csharp
// 运行时切换主题
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 Gotchas
常见误区
- Do not use .NET Framework WPF patterns in .NET 8+ projects. Avoid for DI (use Host builder),
App.config(usepackages.config),PackageReferencepattern (use constructor injection), andServiceLocator(useAssemblyInfo.csproperties).<PropertyGroup> - Do not use deprecated WPF APIs. (replaced by
BitmapEffect/Effect),ShaderEffect(removed), andDrawingContext.PushEffecttile modes with hardware acceleration disabled are obsolete.VisualBrush - Do not mix and manual
{Binding}when using MVVM Toolkit. UseINotifyPropertyChangedsource generators consistently. Mixing approaches causes subtle binding update bugs.[ObservableProperty] - Do not use from async code. In async methods,
Dispatcher.Invokeautomatically marshals back to the UI thread (the defaultawaitbehavior).ConfigureAwait(true)/Dispatcher.Invokeis still appropriate from non-async contexts (timers, COM callbacks, native interop).BeginInvoke - Do not set for WPF apps. WPF uses XAML reflection extensively. Use
TrimMode=fulland test all views after trimming to catch missing types.TrimMode=partial - Do not forget the Host builder lifecycle. Call in
_host.StartAsync()andOnStartupin_host.StopAsync(). Forgetting lifecycle management causes DI-registeredOnExitinstances to never start or stop.IHostedService - Do not hardcode colors when using Fluent theme. Reference theme resources () to maintain compatibility with light/dark mode and system accent color changes.
{DynamicResource SystemAccentColor}
- 不要在.NET 8+项目中使用.NET Framework WPF模式:避免使用进行依赖注入(改用主机生成器)、
App.config(改用packages.config)、PackageReference模式(改用构造函数注入)及ServiceLocator(改用AssemblyInfo.cs属性)。<PropertyGroup> - 不要使用已废弃的WPF API:(已被
BitmapEffect/Effect替代)、ShaderEffect(已移除)、硬件加速禁用的DrawingContext.PushEffect平铺模式均已过时。VisualBrush - 使用MVVM工具包时不要混合和手动
{Binding}:请始终使用INotifyPropertyChanged源代码生成器,混合使用会导致微妙的绑定更新错误。[ObservableProperty] - 不要在异步代码中使用:异步方法中,
Dispatcher.Invoke会自动封送回UI线程(默认await行为)。ConfigureAwait(true)/Dispatcher.Invoke仅适用于非异步上下文(定时器、COM回调、原生互操作)。BeginInvoke - 不要为WPF应用设置:WPF大量使用XAML反射,请使用
TrimMode=full并在裁剪后测试所有视图以避免类型缺失。TrimMode=partial - 不要忘记主机生成器的生命周期管理:在中调用
OnStartup,在_host.StartAsync()中调用OnExit。忽略生命周期管理会导致依赖注入注册的_host.StopAsync()实例无法启动或停止。IHostedService - 使用Fluent主题时不要硬编码颜色:请引用主题资源(如)以保持与明暗模式及系统强调色变化的兼容性。
{DynamicResource SystemAccentColor}
Prerequisites
前置条件
- .NET 8.0+ with Windows desktop workload
- TFM: (no Windows SDK version needed for WPF)
net8.0-windows - Visual Studio 2022+, VS Code with C# Dev Kit, or JetBrains Rider
- For Fluent theme: .NET 9+
- 安装.NET 8.0+及Windows桌面工作负载
- 目标框架:(WPF无需指定Windows SDK版本)
net8.0-windows - Visual Studio 2022+、安装C# Dev Kit的VS Code或JetBrains Rider
- 使用Fluent主题需安装.NET 9+