必须在manifest中声明PerMonitorV2,否则Windows将程序视为DPI不感知,强制位图拉伸导致文字发虚、控件错位、自绘变形;PerMonitorV2是唯一支持跨屏动态DPI切换的模式,需显式配置且嵌入可执行文件。

必须在 manifest 中声明 PerMonitorV2,否则所有 GDI 绘图、控件尺寸、字体渲染都会糊成一片——哪怕你调用了 SetProcessDpiAwareness 或设置了兼容性选项,也毫无作用。
manifest 必须显式配置 PerMonitorV2
这是整个高 DPI 适配的起点,也是最容易被跳过的一步。没有它,Windows 就当你的程序是“旧版 DPI 不感知应用”,强制用位图拉伸方式缩放整个窗口,结果就是文字发虚、按钮错位、自绘图形变形。
-
PerMonitorV2是唯一支持跨显示器动态切换 DPI 的模式;true/PM(即 PerMonitor)已过时,不支持字体重排和WM_DPICHANGED消息 - manifest 必须嵌入到可执行文件中(VS 项目属性 → 清单工具 → 输入和输出 → 使用清单文件 = 是),不能仅靠外置
.exe.manifest文件 - 典型片段要完整包含两个字段:
<dpiaware>true/PM</dpiaware>和<dpiawareness>PerMonitorV2</dpiawareness>,缺一不可 - 验证是否生效:运行时调用
GetThreadDpiAwarenessContext(),返回值应为DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2
OnDpiChanged 必须重载并重设所有 GDI 坐标与尺寸
MFC 默认不响应 WM_DPICHANGED,你得自己接管。这个消息不是“通知你 DPI 变了”,而是“窗口已被系统移到新 DPI 屏幕,现在你有 10ms 时间重算一切,否则下一帧就糊”。
- 在主框架类(如
CMainFrame)或对话框类中添加ON_WM_DPICHANGED()消息映射,并实现OnDpiChanged - 不要只重设字体——GDI 绘图里的
MoveToEx、LineTo、Rectangle所有坐标都得按新 DPI 缩放比重新计算;CRect成员变量(比如自绘控件的边框缓存)也要更新 - 常见错误:只调用
SetWindowPos调整窗口大小,但没重算客户区内的控件位置,导致按钮跑出边界或重叠 - 缩放比不是固定值,要用
GetDpiForWindow(m_hWnd)实时获取,再除以 96.0 得到浮点比例(如 144 → 1.5)
GDI 绘图必须用物理像素校验,不能依赖逻辑坐标
MFC 的 CDC 默认走 GDI 逻辑坐标体系,而高 DPI 下 Windows 已经把“一个逻辑单位”悄悄变成了多个物理像素。如果你直接拿 GetClientRect 返回的尺寸去画线,线条粗细、间距、对齐全会偏移。
立即学习“C++免费学习笔记(深入)”;
- 绘制前先调用
cdc.SetMapMode(MM_ANISOTROPIC),再用cdc.SetWindowExt(96, 96)和cdc.SetViewportExt(dpiX, dpiY)显式绑定逻辑单位到物理像素 - 更稳妥的做法:所有绘图坐标全部基于
GetClientRect+GetDpiForWindow计算后的整数像素值,绕过 GDI 的自动缩放逻辑 - 双缓冲绘图(
CMemDC)时,内存 DC 的位图尺寸也必须用物理像素创建,否则BitBlt会引入二次缩放模糊 - 加载位图资源时,避免用
LoadImage直接加载IDB_XXX,优先用CBitmap::LoadBitmap+ 零依赖位图(BMP 文件),并在OnDpiChanged中按比例重载不同 DPI 版本
CMFCPropertyGridCtrl 等高级控件需手动同步 DPI 行为
这类控件内部封装了大量 GDI 绘图和布局逻辑,但它们不自动监听 WM_DPICHANGED,也不会主动重算行高列宽。你得在父窗口的 OnDpiChanged 里显式干预。
-
CMFCPropertyGridCtrl::SetRowHeight的参数是逻辑像素,必须乘以当前 DPI 缩放比后传入;否则在 200% 缩放下,行高仍按 96 DPI 计算,内容被垂直压缩 - 列宽同理:
SetColumnWidth设置的是标签列宽度,但其内部文本测量仍用默认字体高度,需先调用SetFont加载 DPI 适配后的字体,再重设列宽 - 控件自身不触发重绘,设置完必须手动调用
Invalidate(),否则界面卡在旧尺寸 - 若使用自绘风格(如重写
OnDraw),所有坐标、字体、图标尺寸都得走同一套物理像素校验流程,和主窗口保持一致
真正难的不是加几行代码,而是所有坐标、尺寸、字体、位图、消息响应都得在同一套 DPI 计算链条里闭环——漏掉任意一环,比如忘了重设某处 CRect 缓存,或者某个子窗口没重载 OnDpiChanged,整个界面就会在跨屏拖动时突然错位或模糊。这不是一次配置能解决的事,是贯穿整个 UI 层的约束体系。


















