Windows Forms 中 Screen.PrimaryScreen.Bounds 返回逻辑像素而非物理像素,需结合 DPI 缩放因子(如 Graphics.FromHwnd(IntPtr.Zero).DpiX / 96.0)校正;须在启动早期调用 Application.SetHighDpiMode(HighDpiMode.PerMonitorV2),并在 OnPaint 中设置 Graphics.ScaleTransform(scale, scale) 及缩放字体大小。

Windows Forms 应用里 Screen.PrimaryScreen.Bounds 返回的尺寸不准?
这是高 DPI 下最典型的「假像素」问题:系统报告的屏幕宽高(比如 1920×1080)是逻辑像素,不是物理像素。当缩放设为 125% 时,实际渲染用了 2400×1350 物理像素,但 Bounds 还是返回 1920×1080 —— 你按它布局,控件会模糊或错位。
真正该用的是 Screen.PrimaryScreen.Bounds 配合 DPI 缩放因子,而不是直接信它。
- 必须在程序启动早期调用
Application.SetHighDpiMode(HighDpiMode.PerMonitorV2)(.NET 5+),否则Graphics.DpiX可能始终返回 96 - 获取真实缩放比例:用
Graphics.FromHwnd(IntPtr.Zero).DpiX / 96.0,比查注册表或GetDpiForSystem更可靠(尤其多屏不同缩放时) - 注意:
DpiX是每英寸点数,96 是 100% 缩放基准值,所以 125% 对应 120,计算得120 / 96.0 == 1.25
.NET 6+ WPF 中 VisualTreeHelper.GetDpi(this) 总是返回 96?
WPF 默认不启用 Per-Monitor DPI 感知,即使你改了 manifest 或设置了 AppContext.SetSwitch,GetDpi 仍可能卡在系统 DPI(通常是主屏 100% 值)。
根本原因是 WPF 窗口没声明 DPI 感知级别,或者没触发 DPI 更改事件。
- 确保
app.manifest包含<dpiaware>true/PM</dpiaware>和<dpiawareness>PerMonitorV2</dpiawareness> - 在
MainWindow构造函数后加this.SourceInitialized += (s, e) => { var dpi = VisualTreeHelper.GetDpi(this); }—— 必须等窗口初始化完成才能取准 - 监听
DpiChanged事件处理缩放切换,别只在启动时读一次
WinForms 自绘控件文字模糊、线条发虚?
不是抗锯齿开错了,是没告诉 GDI+ 当前缩放比例,导致它用 1:1 像素绘制,再被系统拉伸模糊。
核心动作就一个:把 Graphics 的 PageScale 设对。
- 在
OnPaint里先调e.Graphics.ResetTransform(),再设e.Graphics.ScaleTransform(scale, scale)(scale来自DpiX / 96.0) - 字体大小别硬写
new Font("...", 12),改用new Font("...", 12 * scale),否则 125% 下文字太小 - 避免用
Control.CreateGraphics()绘图 —— 它绕过 Paint 流程,无法自动适配 DPI
C# 调用 GetDpiForWindow 返回 0?
这个 Win32 API 在 .NET 中需要手动 P/Invoke,但返回 0 通常不是代码写错,而是窗口还没完成 DPI 关联。
常见于窗体刚 Show() 就立刻调用,此时 HWND 已存在但系统尚未分配 DPI 上下文。
- 必须等
HandleCreated事件之后再调,或者用this.Handle != IntPtr.Zero && IsHandleCreated双重判断 - 推荐替代方案:用
Graphics.FromHwnd(this.Handle).DpiX,更稳定,且自动 fallback 到屏幕 DPI - 如果用
GetDpiForWindow,记得[DllImport("user32.dll")]加SetLastError = true,出错时查Marshal.GetLastWin32Error()
高 DPI 适配最麻烦的从来不是算比例,而是时机——缩放因子在窗口生命周期里会变多次,而很多 API 只在特定阶段才返回有效值。别假设“启动时读一次就够了”,尤其是多显示器拖拽场景。


















