WinForms高DPI适配需三步:设AutoScaleMode为Dpi(InitializeComponent后)、manifest中启用PerMonitorV2 DPI Awareness、自绘时用GetDpiForWindow或DeviceDpi换算尺寸与字体。

WinForms高DPI支持必须设AutoScaleMode为Dpi或Font
默认情况下,WinForms窗体的AutoScaleMode是Font,但它在高DPI下往往缩放不准——尤其当系统缩放不是100%或125%这种整数倍时。真正适配高DPI的关键是显式设为Dpi:
this.AutoScaleMode = AutoScaleMode.Dpi;这个设置要放在
InitializeComponent()之后、Show()之前,否则可能被设计器重置。注意:如果项目启用了Per-Monitor DPI Awareness(见下文),Dpi模式才真正生效;仅设此项但没开启高DPI感知,Windows仍会走虚拟化缩放(即“模糊放大”)。
必须在app.manifest中启用PerMonitorV2 DPI Awareness
仅靠代码设置不够。.NET Framework 4.7+ 和 .NET Core 3.0+ 都要求在应用清单(app.manifest)里声明高DPI感知级别。关键配置段如下:
<application xmlns="urn:schemas-microsoft-com:asm.v3">
<windowsSettings>
<dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2</dpiAwareness>
</windowsSettings>
</application>缺了这句,即使AutoScaleMode设对了,系统也只按主显示器DPI缩放一次,副屏切换时UI会错位、文字发虚。另外,确保项目属性 → “应用程序” → “启用Visual Studio生成清单文件”已勾选,否则manifest不会打包进exe。
手动处理缩放因子时优先用GetDpiForWindow而非Graphics.DpiX
有些控件或自绘逻辑需要读取当前DPI值。别用Graphics.FromHwnd(Handle).DpiX——它返回的是GDI缩放后的逻辑DPI,在PerMonitorV2下不可靠。应调用Windows API:
[DllImport("user32.dll")]
static extern uint GetDpiForWindow(IntPtr hwnd);
// 使用
uint dpi = GetDpiForWindow(this.Handle); // 返回实际物理DPI,如144、192这个值可直接换算成缩放比例:(double)dpi / 96。特别注意:该API在Windows 10 1607+才可用,旧系统需降级 fallback 到GetDeviceCaps + LOGPIXELSX。
第三方控件和OwnerDraw容易破坏DPI适配
很多老控件(尤其是自绘的ListView、TreeView或第三方UI库)在OnPaint里硬编码像素尺寸,比如e.Graphics.DrawLine(Pens.Black, 0, 20, 100, 20)。这类代码在高DPI下会变细、错位。修复方式包括:
- 所有坐标/尺寸计算前乘以
this.DeviceDpi / 96.0(WinForms 4.8+ 可用this.DeviceDpi属性) - 禁用控件的
DoubleBuffered并重写OnPaintBackground,避免GDI+缩放叠加 - 对
ImageList等资源,按DPI提供多套图标(如@1x、@2x),运行时根据DeviceDpi选载
new Font("Segoe UI", 9),改用SystemFonts.MessageBoxFont或基于DeviceDpi动态算字号。










