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 及以上)会调用它。默认值是 SystemAware。3 建议把配置写在项目文件中,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>
需要注意的是,项目文件中的 ApplicationHighDpiMode 与 ApplicationConfiguration.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/WPF/WinUI - 判断表
- 保留、封装还是替换 ActiveX/OCX - 判断表
- .NET Framework 迁移到 .NET 前的检查清单
- Windows 的 MFC 是什么 - 它是什么、现在该如何处理
相关咨询领域
合同会社小村软件承接旧版 WinForms/MFC 应用的高DPI支持(现状调查、支持程度判断、布局改造),以及因更换电脑而产生的显示异常原因调查、UI 更新咨询等业务。
参考链接
-
Microsoft Learn, High DPI Desktop Application Development on Windows. 说明 DPI 缩放的背景、各 DPI 感知模式的行为、不支持 DPI 的应用如何被位图放大,以及混合 DPI 环境下的测试思路。 ↩ ↩2 ↩3 ↩4
-
Microsoft Learn, Setting the default DPI awareness for a process. 说明通过 manifest 的 dpiAware/dpiAwareness 元素声明的方式、两者的优先级关系,以及为何不推荐通过 API 声明。 ↩ ↩2
-
Microsoft Learn, What’s new in Windows Forms .NET 6. 说明通过 ApplicationConfiguration.Initialize 的启动流程、项目文件的 ApplicationHighDpiMode 设置(默认 SystemAware),以及 .NET 6 中 PerMonitorV2 缩放的改进内容。 ↩ ↩2 ↩3 ↩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
-
Microsoft Learn, Fix DPI display issues in Windows Form Designer. 说明 WinForms 设计器不支持 DPI、在高DPI显示器上显示的信息栏可用来以100%缩放重启、devenv /noScale,以及针对 .NET 6+ 的 ForceDesignerDPIUnaware。 ↩ ↩2 ↩3
-
Microsoft Learn, DPI_AWARENESS_CONTEXT handle. 说明 Unaware/System Aware/Per-Monitor/Per-Monitor V2/UNAWARE_GDISCALED(GDI 缩放,Windows 10 1809~)各种上下文的定义与行为。 ↩ ↩2 ↩3 ↩4 ↩5
-
Microsoft Learn, Mixed-Mode DPI Scaling and DPI-aware APIs. 说明通过 SetThreadDpiAwarenessContext 在顶层窗口层面混用 DPI 感知模式,以及 GetDpiForWindow 等支持 DPI 的 API。 ↩
-
Microsoft Learn, SetProcessDpiAwareness function. 说明通过 API 设置进程默认 DPI 感知的方式、官方推荐改用 manifest 声明的原因,以及一旦设置后无法更改这一点。 ↩
-
Microsoft Learn, Automatic form scaling - Windows Forms. 说明 AutoScaleMode/AutoScaleDimensions/CurrentAutoScaleDimensions 的自动缩放行为,以及 Font 与 Dpi 模式混用不受支持这一点。 ↩ ↩2
相关文章
共享相同标签的最新文章。可以围绕相近的主题进一步加深理解。
WPF 高 DPI 支持──「不是应该对 DPI 很强吗」却仍模糊、渗色的原因与对策
WPF 以 DIP(1/96 英寸)进行布局,从一开始就是 System DPI Aware,但移动到 DPI 不同的显示器时整体会渗色,位图与细线也会模糊。本文从实务角度整理原因判断、.NET Framework 4.6.2/.NET 上的 Per-Monitor DPI...
在 WinForms/WPF 应用中集成 Entra ID 认证 —— MSAL.NET 与 WAM Broker 的实务架构
本文以实务视角整理在 WinForms/WPF 桌面应用中集成 Entra ID(原 Azure AD)认证的步骤:公共客户端的思路、ROPC 被弃用的现状、应用注册、MSAL.NET 的 AcquireTokenSilent 模式、WAM Broker、令牌缓存的持久化,...
业务应用的日期时间与时区 ── 从 DateTime 的陷阱到 UTC 存储原则、测试设计
服务器迁移后时间整整差了 9 个小时、只有海外分支机构的日期会变成前一天——本文从 DateTime 的 Kind 与隐式转换梳理日期时间事故的根本原因。内容涵盖与 DateTimeOffset 的取舍、UTC 存储与 ISO 8601 原则、TimeZoneInfo 与夏...
C# 操作 Excel 时 EXCEL.EXE 残留的问题 ── COM 引用释放模式与替换判断
本文从 COM 引用计数与 RCW 的运行原理,整理 C# 通过 Microsoft.Office.Interop.Excel 操作 Excel 时 EXCEL.EXE 进程残留的问题。内容涵盖「两个点规则」的陷阱、Marshal.ReleaseComObject 派与 G...
如何理解 Windows 的会话隔离 ── Session 0・RDP・多用户同时运行
整理 Windows 应用程序开发者容易混淆的「会话」概念。说明服务无法显示 UI 的 Session 0 隔离原因、RDP 连接时会话的行为、命名对象的会话隔离,以及共享 PC・RDS 环境中常见的设计失误,从实务角度解说。
常见问题
汇总了咨询这一主题时常见的问题。
- 为什么 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 软件开发、技术咨询与故障排查为中心,擅长难以复现的故障调查,以及既有资产仍在运行的项目。