windows 10 1703+ 应用 getdpiforwindow 获取窗口级 dpi 并除以 96 得缩放因子,需确保 hwnd 有效且进程已声明高 dpi 感知;macos 应通过 [window screen].backingscalefactor 获取当前屏缩放值;linux 则优先读 _net_scale_factor 或 qt_scale_factor 环境变量。

Windows 上用 GetDpiForWindow 获取缩放因子
Windows 10 1703+ 的推荐方式是调用 GetDpiForWindow,它返回每英寸点数(DPI),除以 96 就是 Qt 风格的缩放因子(例如 144 → 1.5)。这个函数比旧的 GetDeviceCaps 更可靠,能正确反映每个窗口的 DPI 设置,尤其在多显示器混合缩放场景下。
常见错误是传入无效窗口句柄或忽略系统版本兼容性:
- 必须确保窗口已创建且
HWND有效;创建窗口前调用会返回 96(即 1.0) - 低于 Windows 10 1703 时该函数不存在,需用
LoadLibrary+GetProcAddress动态加载,或 fallback 到GetDeviceCaps(HORZRES)和GetDeviceCaps(LOGPIXELSX)组合估算 - 如果应用未声明高 DPI 感知(manifest 中缺少
dpiAware=true或dpiAwareness),系统会强制按主屏缩放并虚拟化坐标,GetDpiForWindow仍返回真实 DPI,但窗口内容可能被拉伸——这和 Qt 行为不一致,容易误判
macOS 上读取 NSMainScreen 的 backingScaleFactor
macOS 没有“每个窗口独立 DPI”的概念,而是按屏幕(NSScreen)提供 backingScaleFactor,通常为 1.0(标准)或 2.0(Retina),Qt 也直接映射为缩放因子。关键在于:不能只查主屏,而要查当前窗口所在屏。
实操中容易漏掉两点:
立即学习“C++免费学习笔记(深入)”;
-
[NSScreen screens]返回所有活跃屏幕,需用[window screen]获取窗口归属屏,再取其backingScaleFactor;否则多屏不同缩放时会错用主屏值 - C++ 代码需混编 Objective-C(.mm 文件),且链接
AppKit.framework;纯 C++ 编译会报Use of undeclared identifier 'NSScreen' - macOS 10.14+ 支持
displayScaleFactor(更细粒度),但 Qt 目前仍用backingScaleFactor,保持一致即可,不必升级
Linux X11 下解析 _NET_SCALE_FACTOR 和 scale 环境变量
Linux 没有统一 API,Qt 自身依赖 _NET_SCALE_FACTOR(EWMH 属性)或环境变量 QT_SCALE_FACTOR、GDK_SCALE。C++ 程序若想模拟 Qt 行为,优先读取窗口管理器设置的 _NET_SCALE_FACTOR,fallback 到环境变量。
注意几个实际坑点:
-
XGetWindowProperty查询_NET_SCALE_FACTOR前,必须先确认根窗口存在且属性已设置;GNOME/KDE 通常设,但 i3/sway 等平铺 WM 可能不设,此时返回 0 或失败 -
QT_SCALE_FACTOR是字符串,需strtod转换,且它可能被用户手动设为非整数(如 1.25),而_NET_SCALE_FACTOR通常是整数(1/2) - Wayland 下 X11 兼容层(Xwayland)不传递
_NET_SCALE_FACTOR,此时只能靠QT_SCALE_FACTOR或硬编码 fallback(如检测WAYLAND_DISPLAY后默认 1.0)
跨平台封装时绕不开的 DPI 感知初始化顺序
缩放因子不是静态值——窗口创建后可能因拖到另一屏、系统设置变更或用户缩放调整而改变。Qt 通过 QEvent::PlatformSurface 和 QEvent::ApplicationStateChange 监听,C++ 原生实现必须同步处理。
最容易被忽略的是初始化时机:
- 在
main()开头就调用SetProcessDpiAwarenessContext(Windows)或NSHighResolutionCapable(macOS Info.plist)——晚于窗口创建会导致首次获取值错误 - Linux 下不能假设
DISPLAY环境变量存在(headless 场景),需先getenv("DISPLAY")判空,再决定是否走 X11 分支 - 没有“全局缩放因子”:Qt 的
qApp->devicePixelRatio()实际是主屏值,而QWidget::devicePixelRatio()才是窗口级;C++ 封装时若只缓存一次结果,多屏拖拽后就会失效
缩放因子本身不难拿,难的是它随时可能变,而且各平台连“变”的触发条件都不一样——Windows 有 DPI changed 消息,macOS 有 NSApplicationDidChangeScreenParametersNotification,Linux 几乎全靠轮询或外部信号。真要稳,得按目标平台特性做最小闭环监听,而不是写个函数一劳永逸。










