dotnet-wpf-modern

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

dotnet-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
net8.0-windows
. .NET 9 features (Fluent theme) explicitly marked.
.NET 8+上的WPF:主机生成器与依赖注入、基于CommunityToolkit.Mvvm源代码生成器的MVVM实现、硬件加速渲染优化、现代C#模式(记录类型、主构造函数、模式匹配)、Fluent主题(.NET 9+)、系统主题检测,以及与.NET Framework WPF的差异。
版本说明:以.NET 8.0+为基准(当前长期支持版本),目标框架为
net8.0-windows
。.NET 9新增功能(如Fluent主题)会明确标注。

Scope

适用范围

  • 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
    .csproj
    (no
    packages.config
    , no
    AssemblyInfo.cs
    )
  • Nullable reference types enabled by default
  • Implicit usings enabled
  • NuGet
    PackageReference
    format (not
    packages.config
    )
  • No
    App.config
    for DI -- use Host builder
  • dotnet publish
    produces a single deployment artifact
  • 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
ServiceLocator
or manual DI approaches.
csharp
// 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应用使用通用主机实现依赖注入、配置及日志功能,替代传统的
ServiceLocator
或手动依赖注入方式。
csharp
// 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:
  • [ObservableProperty]
    -- generates property with
    INotifyPropertyChanged
    from a backing field
  • [RelayCommand]
    -- generates
    ICommand
    from a method (supports async, cancellation,
    CanExecute
    )
  • [NotifyPropertyChangedFor]
    -- raises
    PropertyChanged
    for dependent properties
  • [NotifyCanExecuteChangedFor]
    -- re-evaluates command
    CanExecute
    when property changes

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 --
    .NET 8+
    WPF supports IL trimming for smaller deployment size
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
TrimMode=partial
(not
full
) and test thoroughly. Compiled bindings and
x:Type
references are safer than string-based bindings for trimming.
  • ReadyToRun(R2R):预编译程序集减少启动时的JIT开销
  • 分层编译:快速启动并逐步优化
  • 支持裁剪:.NET 8+ WPF支持IL裁剪以减小部署体积
xml
<!-- 启用裁剪以减小部署体积 -->
<PropertyGroup>
  <PublishTrimmed>true</PublishTrimmed>
  <TrimMode>partial</TrimMode>
  <!-- WPF应用需要使用partial裁剪模式,因存在反射使用场景 -->
</PropertyGroup>
裁剪注意事项:WPF大量依赖XAML反射实现数据绑定和资源解析。请使用
TrimMode=partial
(而非
full
),并在裁剪后测试所有视图以避免类型缺失问题。编译绑定和
x:Type
引用比字符串绑定更适合裁剪场景。

Memory 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:
  • None
    -- classic WPF look (no Fluent styling)
  • Light
    -- Fluent theme with light colors
  • Dark
    -- Fluent theme with dark colors
  • System
    -- follow Windows system light/dark theme setting
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取值
  • None
    :经典WPF外观(无Fluent样式)
  • Light
    :Fluent主题浅色模式
  • Dark
    :Fluent主题深色模式
  • System
    :跟随Windows系统的明暗主题设置
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

常见误区

  1. Do not use .NET Framework WPF patterns in .NET 8+ projects. Avoid
    App.config
    for DI (use Host builder),
    packages.config
    (use
    PackageReference
    ),
    ServiceLocator
    pattern (use constructor injection), and
    AssemblyInfo.cs
    (use
    <PropertyGroup>
    properties).
  2. Do not use deprecated WPF APIs.
    BitmapEffect
    (replaced by
    Effect
    /
    ShaderEffect
    ),
    DrawingContext.PushEffect
    (removed), and
    VisualBrush
    tile modes with hardware acceleration disabled are obsolete.
  3. Do not mix
    {Binding}
    and manual
    INotifyPropertyChanged
    when using MVVM Toolkit.
    Use
    [ObservableProperty]
    source generators consistently. Mixing approaches causes subtle binding update bugs.
  4. Do not use
    Dispatcher.Invoke
    from async code.
    In async methods,
    await
    automatically marshals back to the UI thread (the default
    ConfigureAwait(true)
    behavior).
    Dispatcher.Invoke
    /
    BeginInvoke
    is still appropriate from non-async contexts (timers, COM callbacks, native interop).
  5. Do not set
    TrimMode=full
    for WPF apps.
    WPF uses XAML reflection extensively. Use
    TrimMode=partial
    and test all views after trimming to catch missing types.
  6. Do not forget the Host builder lifecycle. Call
    _host.StartAsync()
    in
    OnStartup
    and
    _host.StopAsync()
    in
    OnExit
    . Forgetting lifecycle management causes DI-registered
    IHostedService
    instances to never start or stop.
  7. Do not hardcode colors when using Fluent theme. Reference theme resources (
    {DynamicResource SystemAccentColor}
    ) to maintain compatibility with light/dark mode and system accent color changes.

  1. 不要在.NET 8+项目中使用.NET Framework WPF模式:避免使用
    App.config
    进行依赖注入(改用主机生成器)、
    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()
    。忽略生命周期管理会导致依赖注入注册的
    IHostedService
    实例无法启动或停止。
  7. 使用Fluent主题时不要硬编码颜色:请引用主题资源(如
    {DynamicResource SystemAccentColor}
    )以保持与明暗模式及系统强调色变化的兼容性。

Prerequisites

前置条件

  • .NET 8.0+ with Windows desktop workload
  • TFM:
    net8.0-windows
    (no Windows SDK version needed for WPF)
  • Visual Studio 2022+, VS Code with C# Dev Kit, or JetBrains Rider
  • For Fluent theme: .NET 9+

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

References

参考资料