WinForms 的高DPI支持 - 4K显示器下模糊、错位的原因与实用对策

· · WinForms, 高DPI, Windows, .NET, C#, .NET Framework, 遗留系统维护, UI, 技术咨询

“换了新笔记本电脑之后,业务应用中的文字变得模糊难以阅读”“接上4K显示器后,按钮和标签重叠在一起”“只有在客户端125%的环境下,报表预览会错位”。这几年,每到更换电脑的时期,这类咨询就会大量增加。超越Full HD的高分辨率显示器,以及125~200%的显示缩放,如今在办公电脑上也已成为标准配置,于是在96 DPI、100%显示时代开发的 WinForms 应用,就无法直接保持良好的显示效果。可以说,在旧版 WinForms 应用的维护咨询中,这是目前出现频率最高的话题。

麻烦的是,症状看起来五花八门,有“模糊”“错乱”“只有一部分变小”等各种表现。实际上原因可以归纳为几种类型,只要弄清楚哪种症状对应哪种原因,就能判断该怎么修,也能判断该做到什么程度。本文以 Windows 的 DPI 缩放机制与 DPI 感知模式为基础,从实用角度整理 WinForms 应用(.NET/.NET Framework 均适用)的高DPI支持,涵盖配置方法、常见陷阱直到分阶段的应对策略。

1. 先说结论

  • 在高DPI环境下,旧版应用显示模糊并不是缺陷,而是操作系统的补救措施(DPI 虚拟化)。Windows 让不支持 DPI 的应用以为自己在96 DPI下绘制,再把结果以位图放大,因此布局不会错乱,但画面会模糊。1
  • 反过来,“画面清晰却错乱”是已经声明支持 DPI,但布局没能跟上的状态。模糊与错乱的原因正好相反,动手处理之前,请先分清是哪一种。
  • 应对的第一步,是弄清楚自己的应用目前是以哪种 DPI 感知模式(Unaware/System Aware/Per-Monitor V2)在运行。请确认是通过 manifest、app.config、API 调用中的哪一种(或完全没有)进行声明。2
  • 配置方法因版本而异。.NET(Core 3.1~.NET 8)可用项目文件或 Application.SetHighDpiMode;.NET Framework 4.7 及以上用 app.config;4.6 及以下现实中只能靠 manifest 声明到 System Aware 为止34
  • 设计器一定要在100%(96 DPI)的显示器上打开。在150%环境下打开表单并保存,会改写 AutoScaleDimensions,变成让整个团队的布局全部错乱的经典事故。5
  • 全面支持(Per-Monitor V2)需要不少工作量。以“先用 System Aware 让主显示器清晰”为第一阶段,PMv2 完全支持为第二阶段的两阶段做法,根据应用的使用年限与使用环境投入相应的资源,比较现实。
  • 如果公司内部无法自行改造或时间不够,作为权宜之计,也值得了解从 exe 的属性→兼容性可由用户端重写的“高DPI设置”,这能让处理客服咨询时轻松不少。6

2. 为什么会模糊 - DPI 虚拟化的原理

Windows 的显示缩放,是以96 DPI为100%的倍率来表示的。125% = 120 DPI,150% = 144 DPI,200% = 192 DPI。若在4K(3840×2160)的27英寸显示器上以100%使用,文字会太小,因此 Windows 默认会推荐150%左右。也就是说,高分辨率显示器的普及,直接意味着“以非96 DPI运行的环境”的普及。

问题在于,许多旧版桌面应用都是在“屏幕永远是96 DPI”的前提下编写的。坐标、字体、图标都直接写死为像素值,若在150%环境下直接绘制,所有内容看起来都会变成物理尺寸的2/3大小。因此,Windows 会对没有声明支持 DPI 的应用谎称“屏幕是96 DPI”,让应用以为自己在96 DPI下绘制,再把结果以位图放大显示。这就是 DPI 虚拟化(位图拉伸)。1

理解了这个机制之后,症状与原因就能清晰地对应起来。请用下表进行初步判断。

症状 原因 状态
整体均匀地模糊、失焦,布局不会错乱 DPI 虚拟化(操作系统在放大位图) 不支持 DPI(Unaware)。安全但不美观
文字清晰,但控件重叠或被裁剪 已声明支持 DPI,但布局没能跟上 支持不完整。这是改造的对象
主显示器上清晰,移到副显示器就模糊 System Aware(DPI 固定为启动时主显示器的值) 按规格运行。是否要改用 PMv2 需要判断
文字大小没问题,但图标、图片偏小或粗糙 位图资源仍是96 DPI用的 图片资源尚未支持(第6章)
只有特定画面、控件错乱 固定坐标布局、自绘内容、第三方控件 单独改造的对象(第6章)

重点在于,“显示模糊”的应用其实并没有出问题。操作系统的补救措施正在正常发挥作用。所谓的改造,就是切断这个补救措施,声明“缩放由我自己处理”(也就是提升 DPI 感知模式),因此声明的那一刻起,布局问题就全部变成自己的责任。“先试着改成 PMv2,结果反而变得更糟”这类咨询,正是在这种结构下发生的。

3. 梳理 DPI 感知模式

应用以进程(更准确地说,从 Windows 10 起是以顶层窗口)为单位,向操作系统声明自己能处理DPI到什么程度。模式实质上有4种。16

模式 引入版本 应用看到的 DPI 跨显示器移动、DPI 变更时 在 WinForms 中的实际定位
Unaware ── 始终是96 操作系统放大位图(模糊) 没有任何声明时的默认值。模糊但不会错乱
System Aware Vista 登录时固定为主显示器的 DPI 主显示器以外或变更后由操作系统放大(模糊) 所有版本均可使用。务实方案的主力
Per-Monitor (V1) 8.1 窗口所在显示器的 DPI 只通知顶层窗口,缩放全部要自行处理 缺乏框架支持,实用性低。不建议选用
Per-Monitor V2 10 (1703) 窗口所在显示器的 DPI 连子窗口都会收到通知,非客户区由操作系统自动跟踪 完整支持的主力。可用于 .NET Framework 4.7+ / .NET

Per-Monitor V1 与 V2 之间的差异,在实务中具有决定性。V1 是“只给通知,剩下全部自己处理”的原生 Win32 机制,没有理由在 WinForms 中使用。V2 则会由操作系统自动缩放标题栏、滚动条、菜单等非客户区,WinForms 端的框架支持(后文详述)也是以 V2 为前提构建的。若要选择 Per-Monitor,V2 是唯一的选择。6

此外还有一种模式叫做GDI 缩放(DPI_AWARENESS_CONTEXT_UNAWARE_GDISCALED,Windows 10 1809~)。应用本身仍保持 Unaware,但由 GDI 绘制的文本或图形,会由操作系统在矢量级别放大,是位图放大的改进版本。6 因为有可能在完全不修改代码的情况下减轻文字模糊的程度,值得记住作为无法改造应用的临时方案(4.3节)。

另外,从 Windows 10 1607 起,还有以顶层窗口为单位混用不同模式的 Mixed-Mode,Win32 层面支持“主画面用 PMv2,还没修完的对话框保持 Unaware 让操作系统放大”这种分阶段迁移的做法。7 虽然这不是能从 WinForms 轻松使用的功能,但“不必一次修完所有画面”的设计理念,会延伸到第7章的分阶段策略。

4. 配置方法 - 按版本给出的正确做法

根据自己的应用属于哪个版本,声明 DPI 感知模式的方式也不同。这里如果搞错,就会在“配置了却没生效”上浪费时间,因此以下按版本分别说明。

4.1 .NET(Core 3.1~.NET 8)的 WinForms

.NET 提供了 Application.SetHighDpiMode,模板生成的 ApplicationConfiguration.Initialize()(.NET 6 及以上)会调用它。默认值是 SystemAware3 建议把配置写在项目文件中,Visual Studio 的设计器也会读取这个配置。

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <OutputType>WinExe</OutputType>
    <TargetFramework>net8.0-windows</TargetFramework>
    <UseWindowsForms>true</UseWindowsForms>
    <!-- SystemAware(默认)/ PerMonitorV2 / DpiUnaware / DpiUnawareGdiScaled -->
    <ApplicationHighDpiMode>PerMonitorV2</ApplicationHighDpiMode>
  </PropertyGroup>
</Project>

需要注意的是,项目文件中的 ApplicationHighDpiModeApplicationConfiguration.Initialize().NET 6 才引入的机制3 在 .NET Core 3.1/.NET 5 项目中,即使写了这个属性也不会生效,因此要像下面这样直接在代码中调用 Application.SetHighDpiMode(不使用 ApplicationConfiguration.Initialize() 的旧式 Main 也是同样做法)。无论哪种情况,都必须在创建任何一个窗口之前调用。

[STAThread]
static void Main()
{
    Application.SetHighDpiMode(HighDpiMode.PerMonitorV2);
    Application.EnableVisualStyles();
    Application.SetCompatibleTextRenderingDefault(false);
    Application.Run(new MainForm());
}

.NET 6 之后,PMv2 场景下容器控件与 MDI 子窗口的缩放已有所改进,“从200%显示器移到100%显示器时控件会错位”这类直到 .NET 5 都存在的问题已大幅解决。3 若要认真投入 PMv2 支持,个人的实际体会是相比 .NET Framework,用更新的 .NET 明显更容易应对。关于是否要从 .NET Framework 迁移的参考资料,整理在“.NET Framework 迁移到 .NET 前的检查清单”中。

4.2 .NET Framework 4.7 及以上

.NET Framework 在4.7版大幅强化了高DPI支持,加入了控件缩放改进、DPI 变更事件(DpiChanged 系列)、DeviceDpi 属性等。不过这是选择性启用(opt-in)的功能,必须同时配置以下两点才会生效。4

首先,在 manifest 中声明 Windows 10 兼容性(没有这个声明,4.7 的高DPI功能本身就不会启用)。

<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
  <application>
    <!-- Windows 10 兼容性声明 -->
    <supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" />
  </application>
</compatibility>

接着,在 app.config 的 System.Windows.Forms.ApplicationConfigurationSection 中声明 DPI 感知模式。

<configuration>
  <System.Windows.Forms.ApplicationConfigurationSection>
    <add key="DpiAwareness" value="PerMonitorV2" />
    <!-- 若已有画面自行处理过缩放,可以单独关闭这项功能 -->
    <!-- <add key="EnableWindowsFormsHighDpiAutoResizing" value="false" /> -->
  </System.Windows.Forms.ApplicationConfigurationSection>
</configuration>

同时请确认 Main 的开头调用了 Application.EnableVisualStyles()。这里有一点要注意:以前那种在 manifest 中写 <dpiAware> / <dpiAwareness> 的方式,会覆盖 app.config 的设置,因此官方建议在4.7的 WinForms 中不要同时使用两者4 如果过去为了改成 System Aware,曾在 manifest 中写入 <dpiAware>true</dpiAware> 的应用,要升级到4.7+PMv2 时,得先从整理 manifest 端的声明开始。“明明写在 app.config 却没生效”的原因,多半就是这个。

4.3 .NET Framework 4.6 及以下・VB6/MFC 混合

4.6 及以下的 WinForms 没有支持 PMv2 的框架代码,即使强行声明,也得靠自己处理所有布局错乱的问题。这一版本的现实解法,是做到用 manifest 声明 System Aware 为止。操作系统层面的声明,官方推荐使用 manifest;同时写上 <dpiAware>(Vista~)与 <dpiAwareness>(Windows 10 1607~),可以让新旧操作系统都按预期解读。2

<asmv3:application xmlns:asmv3="urn:schemas-microsoft-com:asm.v3">
  <asmv3:windowsSettings>
    <dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true</dpiAware>
    <dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">system</dpiAwareness>
  </asmv3:windowsSettings>
</asmv3:application>

改为 System Aware 后,WinForms 的自动缩放机制(第5章)会按启动时主显示器的 DPI 运行一次,让主显示器上的显示保持清晰。如果表单正确设置了 AutoScaleMode.Font,仅凭这一声明,通常就能得到相当可接受的显示效果。另外,虽然也可以用 SetProcessDpiAwareness 之类的 API 在运行期声明,但窗口创建后就无法更改,官方仍推荐通过 manifest 声明。8 对于混用 VB6 或 MFC 代码的应用,思路也是一样的:exe 的 manifest 决定整个进程的模式(MFC 本身没有自动缩放的支持,因此声明 System Aware 后,逐一检查画面是必需的。关于这一版本资产的处理方式,也可以参考“Windows 的 MFC 是什么”)。

也介绍一下无法改造时用户端的临时方案。在 exe 上右键→属性→兼容性选项卡→“更改高 DPI 设置”,可以重写该应用的 DPI 缩放行为。将“替代高 DPI 缩放行为”设为“系统”,会强制启用 DPI 虚拟化(=不会错乱但会模糊);设为“系统(增强)”,则会应用第3章提到的 GDI 缩放,只有 GDI 绘制的文本会变清晰。6 这并非万能方案,但作为“客户现场今天就想解决”时可以在电话中指导的方法,值得写入支持手册。

5. AutoScaleMode 与设计器的陷阱

WinForms 除了 DPI 感知模式之外,表单本身还有一套自动缩放机制。如果不理解这一点,即使改成 System Aware,也无法正确缩放。

原理是这样的:在设计阶段,每个表单(ContainerControl)会把缩放基准记录在 AutoScaleMode 中,并把设计时环境的基准值记录在 AutoScaleDimensions。运行时会与当前环境的值(CurrentAutoScaleDimensions)比较,若有差异,就一并缩放所有子控件。9 也就是说,这是用来吸收“设计时与运行时环境差异”的机制,基准值会写入代码(Designer.cs)中。

AutoScaleMode 的选项实质上有2种。

AutoScaleMode 基准 特点
Font(推荐・默认) 表单字体的尺寸 DPI 升高时系统字体的实际尺寸也会变化,因此兼具跟踪 DPI 的效果。也会跟踪用户的字体设置变更
Dpi 屏幕的 DPI 只与 DPI 成比例。适合以图形为主的画面
None ── 禁用自动缩放。直接写死96 DPI值的应用多半是这个状态

若不确定,就选 Font。需要注意的是,官方文档也明确指出,在基类表单与派生表单之间混用不同模式,会产生意外的结果9 若应用使用了表单继承,第一步就是先梳理并统一所有表单的模式。

接下来就是重点陷阱了。由于 AutoScaleDimensions 是用来记录“设计时环境的值”的机制,在150%的显示器上打开 Visual Studio 设计器并保存表单时,Designer.cs 的 AutoScaleDimensions 就会被改写成150%的数值(在 Font 模式下,例如6F、12F会变成9F、18F)。坐标与大小也会按1.5倍保存。100%环境的成员构建并运行这个表单时,整体会缩小显示,代码审查时也会看到所有控件坐标都发生变化的巨大差异。“分发了高DPI笔记本电脑的新成员改动了一次表单,结果整个仓库的布局都错乱了”,这是高DPI咨询中的经典事故。

对策原则上只有一个:设计器一定要在100%(96 DPI)下打开。Visual Studio 本身是支持 DPI 的应用,但 WinForms 设计器(针对 .NET Framework)并不支持 DPI,因此在高DPI显示器上打开时,会出现黄色信息栏,提示“以100%缩放重启 Visual Studio”。5 请按照这条信息栏以不支持 DPI 的模式重启,再编辑表单,完成后再切回正常模式,并将这套流程定为团队规范。也可以用命令行 devenv /noScale 启动。另外,在 .NET 6 及以上的项目中,若使用 Visual Studio 2022 17.8 及以上,可在项目文件中设置 <ForceDesignerDPIUnaware>true</ForceDesignerDPIUnaware>,这样无需重启整个 VS,只让设计器标签页以不支持 DPI 的方式运行(.NET Framework 项目无法使用)。5

若要把检查机制化,“用 CI 或 pre-commit 拦截 Designer.cs 的 AutoScaleDimensions 出现异常数值的差异”是既简单又有效的做法。比起事故发生后再修复,在提交入口就拦截下来成本更低。

6. 常见的错乱模式与修复方法

提升(或准备提升)DPI 感知模式时会错乱的地方,大致可以归纳为下面几种模式。

错乱的内容 原因 修复方法
控件重叠、被裁剪 固定坐标、固定大小的布局 改用 Anchor/Dock、TableLayoutPanel/FlowLayoutPanel。善用 AutoSize
图标、图片偏小或粗糙 直接使用96 DPI用的位图 准备多种分辨率的图片并按 DPI 切换。可行的话从一张较大的原图缩小
自绘内容(图表、图纸、报表预览) 直接把像素值写在 Graphics 中 DeviceDpi 为基准缩放坐标、线宽、字体
DataGridView 的行变得拥挤 RowHeight 等以像素指定 使用 AutoSizeRowsMode,或设置按 DPI 缩放后的值
ToolStrip 的图标变得很小 固定为16×16 按 DPI 设置 ImageScalingSize
只有特定的第三方/ActiveX 控件错乱 控件本身不支持 DPI 更新为厂商支持的版本。若没有,那就是支持程度的上限

替换布局是工作量的主要部分。反过来说,原本就用 TableLayoutPanel 和 Dock 构建的画面,即使提升 DPI 感知模式,也几乎不需要额外处理。只要规定今后的新画面从一开始就用布局面板来构建,就能减少未来的技术债。

自绘内容的缩放,基本做法是以 Control.DeviceDpi(.NET Framework 4.7+ / .NET)为基准,换算96 DPI对应的数值。

public partial class ChartPanel : Panel
{
    // 将以96 DPI为基准的设计值换算为当前 DPI
    private int Scale(int value96) => value96 * DeviceDpi / 96;

    protected override void OnPaint(PaintEventArgs e)
    {
        using var pen = new Pen(Color.Navy, Scale(2));
        e.Graphics.DrawRectangle(pen,
            Scale(16), Scale(16), Scale(320), Scale(120));
    }

    // PMv2 下跨显示器移动时 DPI 会改变,因此触发重新绘制
    protected override void OnDpiChangedAfterParent(EventArgs e)
    {
        base.OnDpiChangedAfterParent(e);
        Invalidate();
    }
}

做到 System Aware 为止,DPI 在启动时就固定了,只需引入 Scale 即可;但在 PMv2 下,每次跨显示器移动 DeviceDpi 就会改变,因此需要设计成在 DpiChanged 系列事件中重新构建缓存的字体、图片、布局值。4 “有多少个自绘画面”会直接影响 PMv2 支持的工作量估算。

第三方控件与 ActiveX/OCX 是决定这次改造上限的因素。如果控件本身不支持 DPI,无论主机端怎么努力,那个画面都无法做到完美。请先确认厂商的支持情况,对于不太可能更新的 ActiveX,用“保留、封装还是替换 ActiveX/OCX”的判断表决定处理方式,再设定高DPI支持的目标。

7. 分阶段的应对策略 - 做到什么程度的判断表

综合以上内容,可以把应对水平整理为3个阶段。将所有应用都做到 PMv2,是技术上的理想,但考虑改造预算与应用的使用年限,很多情况下适度取舍才是正确答案。

  (1) 什么都不做(Unaware) (2) 改为 System Aware (3) PMv2 完全支持
显示效果 所有环境都模糊(不会错乱) 主显示器清晰。副显示器、DPI 变更时模糊 所有显示器都清晰
主要工作 声明 manifest/配置 + 检查 AutoScaleMode + 确认所有画面的显示 (2) + 全面检查布局 + 自绘内容・图片资源的 DPI 支持 + 处理 DpiChanged
工作量 小~中(主要是与画面数成比例的确认工作) 大(主要是修复错乱;第三方控件会决定上限)
适合情况 几年内即将淘汰。用户可以接受 以固定桌面机、单一显示器为主的内部应用。作为第一阶段 笔记本电脑+外接显示器混用。长期使用的主力应用。要分发给客户的产品

判断的维度有三个:应用的使用年限(还会用几年)、使用环境(如果大家都用同样的固定桌面机,用 System Aware 实质上就足够了;如果笔记本电脑+外接显示器的组合较多,每次移动显示器都会模糊的 System Aware,就会让人不太满意),以及改造预算。建议的做法是,先把 (2) 作为所有应用的标准来实施,再根据过程中发现的错乱数量与第三方控件的限制,来决定是否要推进到 (3),或者只针对哪些画面推进。(2) 以“声明+检查”为主,失败风险小,同时也能大幅改善用户的体验。

也简单谈一下测试。高DPI的问题,很多时候在开发机上无法重现,因此要刻意搭建以下环境来确认。1

  • 连接两台缩放比例不同的显示器(例如:150%的笔记本电脑内置屏幕+100%的外接显示器),让窗口在两者之间来回移动
  • 更换主显示器之后重新登录再启动(System DPI 会在登录时固定,若不重新登录就不会切换)
  • 在应用运行期间变更缩放设置
  • 从高DPI的客户端通过远程桌面连接(因为 RDP 会带入客户端的 DPI,“服务器控制台正常,但用 RDP 连接就错乱”是常见的报告)

如果收到“只有在125%下才会错乱”的报告,实际重现那个缩放比例来观察,终究是最快的方法,这是经验法则。虚拟机也能变更缩放设置,因此提前准备好100/125/150/200%的验证环境,可以加快问题排查的速度。

8. 总结

高DPI环境下的“模糊”是操作系统的补救措施(DPI 虚拟化),“错乱”则是支持不完整的 DPI 支持——只要掌握这个对应关系,就能从症状反推原因与对策。配置方法因版本而定:.NET 用项目文件的 ApplicationHighDpiMode;.NET Framework 4.7及以上用 app.config(不与 manifest 的 dpiAware 一起使用);4.6及以下则靠 manifest 声明到 System Aware 为止。再加上统一使用 AutoScaleMode.Font,以及设计器一定要在100%下打开的操作规范,虽然不起眼,但最能减少事故。

在此基础上,该做到什么程度,可以从第7章的3个阶段中选择。先通过 System Aware 让主显示器变清晰,若使用环境与应用的使用年限合适,再进到 PMv2——这样的两阶段做法,是本公司在实际改造项目中实际推荐的推进方式。如果已经到了该重新审视 UI 框架本身的时机(WPF 从设计之初就对 DPI 有较强的支持),也请把“如何选择 WinForms/WPF/WinUI”纳入参考。如果不确定手头的应用能修到哪个阶段,我们可以从盘点画面结构与控件结构开始协助。

相关文章

相关咨询领域

合同会社小村软件承接旧版 WinForms/MFC 应用的高DPI支持(现状调查、支持程度判断、布局改造),以及因更换电脑而产生的显示异常原因调查、UI 更新咨询等业务。

参考链接

  1. Microsoft Learn, High DPI Desktop Application Development on Windows. 说明 DPI 缩放的背景、各 DPI 感知模式的行为、不支持 DPI 的应用如何被位图放大,以及混合 DPI 环境下的测试思路。  2 3 4

  2. Microsoft Learn, Setting the default DPI awareness for a process. 说明通过 manifest 的 dpiAware/dpiAwareness 元素声明的方式、两者的优先级关系,以及为何不推荐通过 API 声明。  2

  3. Microsoft Learn, What’s new in Windows Forms .NET 6. 说明通过 ApplicationConfiguration.Initialize 的启动流程、项目文件的 ApplicationHighDpiMode 设置(默认 SystemAware),以及 .NET 6 中 PerMonitorV2 缩放的改进内容。  2 3 4

  4. Microsoft Learn, High DPI support - Windows Forms. 说明 .NET Framework 4.7 的高DPI强化内容、app.config 的 System.Windows.Forms.ApplicationConfigurationSection(DpiAwareness=PerMonitorV2)、manifest 声明会覆盖 app.config 因而不推荐同时使用的原因,以及 DpiChanged 系列事件与 DeviceDpi。  2 3 4

  5. Microsoft Learn, Fix DPI display issues in Windows Form Designer. 说明 WinForms 设计器不支持 DPI、在高DPI显示器上显示的信息栏可用来以100%缩放重启、devenv /noScale,以及针对 .NET 6+ 的 ForceDesignerDPIUnaware。  2 3

  6. Microsoft Learn, DPI_AWARENESS_CONTEXT handle. 说明 Unaware/System Aware/Per-Monitor/Per-Monitor V2/UNAWARE_GDISCALED(GDI 缩放,Windows 10 1809~)各种上下文的定义与行为。  2 3 4 5

  7. Microsoft Learn, Mixed-Mode DPI Scaling and DPI-aware APIs. 说明通过 SetThreadDpiAwarenessContext 在顶层窗口层面混用 DPI 感知模式,以及 GetDpiForWindow 等支持 DPI 的 API。 

  8. Microsoft Learn, SetProcessDpiAwareness function. 说明通过 API 设置进程默认 DPI 感知的方式、官方推荐改用 manifest 声明的原因,以及一旦设置后无法更改这一点。 

  9. Microsoft Learn, Automatic form scaling - Windows Forms. 说明 AutoScaleMode/AutoScaleDimensions/CurrentAutoScaleDimensions 的自动缩放行为,以及 Font 与 Dpi 模式混用不受支持这一点。  2

共享相同标签的最新文章。可以围绕相近的主题进一步加深理解。

常见问题

汇总了咨询这一主题时常见的问题。

为什么 WinForms 应用在4K显示器上会显示模糊?
这不是缺陷,而是操作系统的补救措施(DPI 虚拟化)。对于没有声明 DPI 感知的应用,Windows 会谎称“屏幕是 96 DPI”,让应用以为自己在 96 DPI 下绘制,再把结果以位图放大显示,因此布局不会错乱,但画面会模糊。反过来,“画面清晰却错乱”则是已经声明了 DPI 感知,但布局没能跟上的状态,原因正好相反。动手处理之前,先分清是哪一种症状,是第一步。
WinForms 的高DPI支持该怎么配置?
方法因版本而异。.NET 6 及以上可在项目文件中设置 ApplicationHighDpiMode 属性(默认是 SystemAware);.NET Core 3.1/.NET 5 则要在创建窗口之前调用 Application.SetHighDpiMode。.NET Framework 4.7 及以上需先在 manifest 中声明 Windows 10 兼容性,再用 app.config 设置 DpiAwareness,但 manifest 中的 dpiAware 写法会覆盖 app.config 的设置,官方建议不要同时使用两者。4.6 及以下现实中只能靠 manifest 声明到 System Aware 为止。
System Aware 与 Per-Monitor V2 该选哪一个?
建议采用两阶段做法。第一阶段是改为 System Aware,会按启动时主显示器的 DPI 缩放,主显示器上显示清晰。这一阶段以声明与检查为主,失败风险小,如果是以固定桌面机为主的内部应用,实务上做到这一步就足够了。Per-Monitor V2 可以在跨显示器移动时也保持全画面清晰,但需要全面检查布局、让自绘内容与图片资源支持 DPI、处理 DpiChanged 事件,工作量较大,若第三方控件不支持,这里就会成为上限。要根据应用的使用年限、使用环境和改造预算来判断。
为什么在设计器中保存表单后,布局就错乱了?
在150%等高DPI显示器上打开 Visual Studio 的 WinForms 设计器并保存表单时,Designer.cs 中的 AutoScaleDimensions 会被改写成150%的数值,坐标和大小也会按1.5倍保存。100%环境的成员构建这个表单时,画面就会整体缩小显示。对策是始终在100%(96 DPI)下打开设计器:在高DPI显示器上会出现黄色信息栏,可以据此以100%缩放重启。在.NET 6及以上的项目中,也可以使用 ForceDesignerDPIUnaware 设置。用 CI 或 pre-commit 拦截 AutoScaleDimensions 出现异常差异的提交,也是有效的机制化做法。

作者简介

本文作者的个人简介页面。

Go Komura

小村软件有限公司 代表

以 Windows 软件开发、技术咨询与故障排查为中心,擅长难以复现的故障调查,以及既有资产仍在运行的项目。

返回博客列表