WinForms.NET8+开发基础 dotnet-winforms-basics

本技能专注于使用.NET 8+平台上的WinForms开发Windows桌面应用程序,涵盖高DPI支持、暗黑模式(实验性)、依赖注入模式、现代化迁移技巧和最佳实践。关键词:WinForms, .NET 8+, 高DPI, 暗黑模式, 依赖注入, Windows桌面开发, 现代化迁移, 异步编程, 前端UI开发。

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

name: dotnet-winforms-基础 description: “在.NET 8+上构建WinForms。高DPI、暗黑模式(实验性)、DI模式、现代化。” user-invocable: false

dotnet-winforms-基础

在.NET 8+上的WinForms:更新的项目模板包含Host构建器和DI,通过PerMonitorV2支持高DPI,通过Application.SetColorMode支持暗黑模式(在.NET 9中实验性,目标在.NET 11最终化),何时使用WinForms,从.NET Framework迁移的现代化技巧,以及常见代理陷阱。

版本假设: .NET 8.0+ 基础(当前LTS)。TFM net8.0-windows。.NET 9功能(暗黑模式实验性)明确标记。.NET 11最终化目标已注明。

范围

  • WinForms .NET 8+ 项目设置(SDK风格)
  • 通过PerMonitorV2支持高DPI
  • 通过Application.SetColorMode支持暗黑模式(实验性)
  • Host构建器和DI模式
  • 从.NET Framework现代化技巧

范围外

  • WinForms .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-wpf-modern]用于WPF模式,[skill:dotnet-winui]用于WinUI 3模式,[skill:dotnet-wpf-migration]用于迁移指南,[skill:dotnet-native-aot]用于通用AOT,[skill:dotnet-ui-chooser]用于框架选择。


.NET 8+ 差异

在.NET 8+上的WinForms是从.NET Framework WinForms的显著现代化,具有SDK风格的项目格式、DI支持和更新的API。

新项目模板

<!-- MyWinFormsApp.csproj (SDK风格) -->
<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <OutputType>WinExe</OutputType>
    <TargetFramework>net8.0-windows</TargetFramework>
    <UseWindowsForms>true</UseWindowsForms>
    <Nullable>enable</Nullable>
    <ImplicitUsings>enable</ImplicitUsings>
  </PropertyGroup>

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

与.NET Framework WinForms的关键差异:

  • SDK风格 .csproj(无packages.config,无AssemblyInfo.cs
  • 默认启用可为空引用类型
  • 启用隐式usings
  • NuGet PackageReference格式
  • Program.cs使用顶级语句
  • dotnet publish生成单个部署工件
  • 并排.NET安装(无机器范围框架依赖)

Host构建器模式

现代WinForms应用使用泛型主机进行依赖注入:

// Program.cs
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;

ApplicationConfiguration.Initialize();

var host = Host.CreateDefaultBuilder()
    .ConfigureServices((context, services) =>
    {
        // 服务
        services.AddSingleton<IProductService, ProductService>();
        services.AddSingleton<ISettingsService, SettingsService>();

        // HTTP客户端
        services.AddHttpClient("api", client =>
        {
            client.BaseAddress = new Uri("https://api.example.com");
        });

        // 窗体
        services.AddTransient<MainForm>();
        services.AddTransient<ProductDetailForm>();
    })
    .Build();

var mainForm = host.Services.GetRequiredService<MainForm>();
Application.Run(mainForm);
// MainForm.cs — 构造函数注入
public partial class MainForm : Form
{
    private readonly IProductService _productService;
    private readonly IServiceProvider _serviceProvider;

    public MainForm(IProductService productService, IServiceProvider serviceProvider)
    {
        _productService = productService;
        _serviceProvider = serviceProvider;
        InitializeComponent();
    }

    private async void btnLoad_Click(object sender, EventArgs e)
    {
        var products = await _productService.GetProductsAsync();
        dataGridProducts.DataSource = products.ToList();
    }

    private void btnDetails_Click(object sender, EventArgs e)
    {
        var detailForm = _serviceProvider.GetRequiredService<ProductDetailForm>();
        detailForm.ShowDialog();
    }
}

ApplicationConfiguration.Initialize

.NET 8+ WinForms使用ApplicationConfiguration.Initialize()作为入口点,整合了多个旧版配置调用:

// ApplicationConfiguration.Initialize()等价于:
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.SetHighDpiMode(HighDpiMode.SystemAware);  // 默认;下方覆盖为PerMonitorV2

高DPI

在.NET 8+上的WinForms有显著改进的高DPI支持。推荐模式是PerMonitorV2,自动处理每显示器DPI更改。

启用PerMonitorV2

// Program.cs — 在ApplicationConfiguration.Initialize()之前设置
Application.SetHighDpiMode(HighDpiMode.PerMonitorV2);
ApplicationConfiguration.Initialize();
// 注意:在Initialize()之前调用SetHighDpiMode()优先于Initialize()设置的默认SystemAware模式。

或通过runtimeconfig.json配置:

{
  "runtimeOptions": {
    "configProperties": {
      "System.Windows.Forms.ApplicationHighDpiMode": 3
    }
  }
}

高DPI模式:

模式 行为
DpiUnaware 0 无缩放;系统位图拉伸窗口
SystemAware 1 启动时缩放到主显示器DPI(在.NET 8中默认)
PerMonitor 2 移动显示器时调整(基本)
PerMonitorV2 3 完整每显示器缩放,支持非客户端区域 (推荐)
DpiUnawareGdiScaled 4 DPI不感知,但GDI+文本以原生分辨率渲染

DPI不感知设计器模式(.NET 9+)

.NET 9引入了DPI不感知设计器模式,防止Visual Studio WinForms设计器中的布局缩放问题。设计器在96 DPI渲染,无论系统DPI,防止损坏.Designer.cs文件。

<!-- .csproj: 选择加入DPI不感知设计器(.NET 9+) -->
<PropertyGroup>
  <ForceDesignerDPIUnaware>true</ForceDesignerDPIUnaware>
</PropertyGroup>

缩放陷阱

  • 不要使用绝对像素大小控件。 在窗体上使用AutoScaleMode.Dpi,让布局引擎自动缩放控件。
  • 锚定和停靠布局比绝对定位缩放更好。 TableLayoutPanelFlowLayoutPanel处理DPI更改更可靠。
  • 自定义绘制(OnPaint)必须使用DPI感知坐标。OnPaint重写中,通过DeviceDpi / 96.0f缩放绘制坐标。
  • 图像资源需要多分辨率。 提供1x、1.5x和2x版本的图标和图像,或使用SVG渲染。
// DPI感知自定义绘制
protected override void OnPaint(PaintEventArgs e)
{
    base.OnPaint(e);
    float scale = DeviceDpi / 96.0f;
    float fontSize = 12.0f * scale;
    using var font = new Font("Segoe UI", fontSize);
    e.Graphics.DrawString("缩放文本", font, Brushes.Black, 10 * scale, 10 * scale);
}

暗黑模式

WinForms暗黑模式在**.NET 9中为实验性**,目标在.NET 11最终化。它使用Windows暗黑模式API为WinForms控件提供系统集成的暗黑模式。

启用暗黑模式(.NET 9+ 实验性)

// Program.cs — 在ApplicationConfiguration.Initialize()之前设置
Application.SetColorMode(SystemColorMode.Dark);
ApplicationConfiguration.Initialize();

或跟随系统主题:

// 跟随系统亮/暗偏好
Application.SetColorMode(SystemColorMode.System);

SystemColorMode值:

模式 行为
Classic 标准WinForms颜色(无暗黑模式)
System 跟随Windows系统亮/暗主题设置
Dark 强制暗黑模式

暗黑模式注意事项

  • 实验状态: API表面可能在.NET 11最终化前更改。在生产中不要依赖特定颜色值或渲染行为。
  • 控件覆盖: 并非所有控件在.NET 9中支持暗黑模式。标准控件(Button、TextBox、Label、ListBox、DataGridView)有暗黑模式支持。第三方和自定义绘制控件可能无法正确渲染。
  • 所有者绘制控件: 使用DrawMode.OwnerDrawFixed或自定义OnPaint重写的控件必须手动读取SystemColors以响应暗黑模式。它们不自动继承暗黑模式颜色。
  • Windows版本: 暗黑模式需要Windows 10版本1809(构建17763)或更高。
  • .NET 11目标: Microsoft表示WinForms视觉样式(包括暗黑模式)目标在.NET 11最终化。计划在发布后的API稳定性。
// 所有者绘制控件必须使用SystemColors进行暗黑模式兼容性
protected override void OnPaint(PaintEventArgs e)
{
    base.OnPaint(e);
    // 使用SystemColors代替硬编码颜色
    using var textBrush = new SolidBrush(SystemColors.ControlText);
    using var bgBrush = new SolidBrush(SystemColors.Control);
    e.Graphics.FillRectangle(bgBrush, ClientRectangle);
    e.Graphics.DrawString("文本", Font, textBrush, 10, 10);
}

何时使用

WinForms适合特定场景。它不是用于新面向客户应用的通用UI框架。

良好适用

  • 快速原型设计: 拖放设计器,用于快速内部工具和概念验证UI
  • 内部企业工具: 业务表单、数据输入、带有DataGridView的CRUD应用
  • 简单仅Windows实用程序: 系统托盘应用、配置工具、诊断仪表板
  • 现有WinForms维护: 将现有.NET Framework WinForms应用现代化到.NET 8+
  • 数据密集型表格UI: 带有虚拟模式的DataGridView高效处理数百万行

不适用

  • 新面向客户应用: 使用WPF(丰富Windows桌面)、WinUI 3(现代Windows)、MAUI(跨平台)或Blazor(Web)
  • 复杂自定义UI: WinForms控件样式有限;WPF或WinUI提供丰富模板化
  • 跨平台需求: WinForms仅Windows;使用MAUI或Uno Platform
  • 无障碍优先应用: WPF和WinUI有更好的无障碍API和屏幕阅读器支持
  • 触摸优化界面: WinForms为鼠标/键盘设计;WinUI或MAUI更好地处理触摸

决策指导

场景 推荐框架
快速内部工具 WinForms
数据输入表单(Windows) WinForms或WPF
现代Windows桌面应用 WinUI 3或WPF(.NET 9+ Fluent)
跨平台移动 + 桌面 MAUI或Uno Platform
跨平台 + Web Uno Platform或Blazor
现有WinForms现代化 WinForms on .NET 8+

完整框架决策树,见[skill:dotnet-ui-chooser]。


现代化技巧

将现有.NET Framework WinForms应用现代化到.NET 8+的技巧。

添加依赖注入

通过Host构建器(见.NET 8+差异部分)用构造函数注入替换静态引用和单例。

之前(旧版模式):

// 反模式:静态服务引用
public partial class MainForm : Form
{
    private void btnLoad_Click(object sender, EventArgs e)
    {
        var products = ProductService.Instance.GetProducts();
        dataGridProducts.DataSource = products;
    }
}

之后(现代模式):

// 现代:构造函数注入
public partial class MainForm : Form
{
    private readonly IProductService _productService;

    public MainForm(IProductService productService)
    {
        _productService = productService;
        InitializeComponent();
    }

    private async void btnLoad_Click(object sender, EventArgs e)
    {
        var products = await _productService.GetProductsAsync();
        dataGridProducts.DataSource = products.ToList();
    }
}

使用异步模式

用async/await替换同步阻塞调用,保持UI响应性:

// 之前:阻塞UI线程
private void btnSave_Click(object sender, EventArgs e)
{
    var client = new HttpClient();
    var result = client.PostAsync(url, content).Result; // 阻塞UI
    MessageBox.Show("已保存!");
}

// 之后:异步保持UI响应性
private async void btnSave_Click(object sender, EventArgs e)
{
    btnSave.Enabled = false;
    try
    {
        var result = await _httpClient.PostAsync(url, content);
        result.EnsureSuccessStatusCode();
        MessageBox.Show("已保存!");
    }
    catch (HttpRequestException ex)
    {
        MessageBox.Show($"错误:{ex.Message}");
    }
    finally
    {
        btnSave.Enabled = true;
    }
}

转换为.NET 8+

使用.NET Upgrade Assistant进行自动迁移:

# 安装升级助手
dotnet tool install -g upgrade-assistant

# 分析项目
upgrade-assistant analyze MyWinFormsApp.csproj

# 升级项目
upgrade-assistant upgrade MyWinFormsApp.csproj

常见迁移问题:

  • App.config设置需要手动迁移到appsettings.json或Host构建器配置
  • My.SettingsVB.NET)和Settings.settings需要手动迁移
  • 第三方控件可能没有.NET 8兼容版本
  • 设计器生成代码在.Designer.cs文件中通常迁移干净
  • COM互操作(System.Runtime.InteropServices)语法可能不同

采用现代C#功能

// 文件范围命名空间
namespace MyApp.Forms;

// 空条件事件调用
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Value)));

// 集合表达式
var columns = new[] { "名称", "价格", "类别" };

// 服务的主要构造函数(C# 12)
public class ProductService(HttpClient httpClient) : IProductService
{
    public async Task<List<Product>> GetProductsAsync()
        => await httpClient.GetFromJsonAsync<List<Product>>("/products") ?? [];
}

代理陷阱

  1. 不要为新面向客户应用推荐WinForms。 WinForms适合内部工具、快速原型设计和数据密集型实用程序。对于面向客户的应用,根据需求推荐WPF、WinUI 3、MAUI或Blazor。
  2. 不要使用已弃用的WinForms API。 Menu(使用MenuStrip)、MainMenu(使用MenuStrip)、ContextMenu(使用ContextMenuStrip)、StatusBar(使用StatusStrip)、ToolBar(使用ToolStrip)、DataGrid(使用DataGridView)。
  3. 不要假设暗黑模式已生产就绪。 通过Application.SetColorMode的暗黑模式在.NET 9中实验性,目标在.NET 11最终化。API表面和渲染可能更改。
  4. 不要在不测试多显示器场景的情况下使用HighDpiMode.SystemAware 对于在多显示器设置上使用的应用,推荐PerMonitorV2
  5. 不要用同步调用阻塞UI线程。 对事件处理程序使用async void,对所有其他异步方法使用async Task。在UI线程上永远不要使用.Result.Wait()
  6. await足够时,不要使用Control.Invoke 在.NET 8+ WinForms中,await通过SynchronizationContext自动编组回UI线程。手动Invoke/BeginInvoke仅在从非异步代码(计时器、COM回调)调用时需要。
  7. 当启用暗黑模式时,不要硬编码颜色。 在自定义绘制和所有者绘制控件中使用SystemColors属性(例如SystemColors.ControlTextSystemColors.Control)以正确响应主题更改。
  8. 不要忘记在Application.Run之前调用ApplicationConfiguration.Initialize() 省略它会禁用视觉样式和高DPI配置。

先决条件

  • .NET 8.0+ 带有Windows桌面工作负载
  • TFM:net8.0-windows(WinForms不需要Windows SDK版本)
  • Visual Studio 2022+ 带有Windows桌面工作负载(用于设计器支持)
  • 对于暗黑模式:.NET 9+(实验性),Windows 10版本1809+
  • 对于DPI不感知设计器:.NET 9+

参考