getdpiforwindow是windows 10 1607+/win11下获取窗口实时dpi最可靠的api,需配合permonitorawarev2感知模式及wm_dpichanged消息处理,否则易因多屏缩放差异导致ui异常。

Windows平台下用GetDpiForWindow获取实时DPI值最可靠
在Windows 10 1607+(含Win11),GetDpiForWindow是唯一能准确反映当前窗口实际DPI缩放比例的API,它不依赖全局系统设置,而是按窗口所在显示器、运行时DPI感知模式动态返回。很多开发者误用GetDeviceCaps(hdc, LOGPIXELSX)或GetSystemMetricsForDpi(SM_CXSCREEN, dpi),结果在多显示器不同缩放场景下返回错误值——比如主屏125%、副屏150%,旧方法常固定返回主屏DPI。
使用前需确保窗口已启用DPI感知:
– 在manifest中声明dpiAware=true或dpiAwareness="system";
– 或调用SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2)(推荐V2,支持缩放变更通知);
– 否则GetDpiForWindow可能降级为系统默认DPI(通常是96)。
示例调用:
int dpi = GetDpiForWindow(hwnd); // 返回如120、144、168等整数值 float scale = dpi / 96.0f; // 得到1.25、1.5、1.75等缩放比例
监听DPI变更需处理WM_DPICHANGED消息
用户拖动窗口到另一台缩放比例不同的显示器,或手动调整系统缩放设置时,窗口不会自动重绘——必须主动响应WM_DPICHANGED。忽略该消息会导致UI模糊、布局错位、字体发虚。
关键点:
– lParam低32位是新DPI矩形(RECT*),用于调整窗口大小以适配新DPI;
– 必须调用SetWindowPos配合SWP_NOZORDER | SWP_NOACTIVATE重设窗口尺寸,否则系统可能拒绝后续DPI更新;
– 在此消息中重新计算字体大小、间距、图像资源加载路径(如选icon_120.png而非icon.png);
– 不要在该消息里直接调用GetDpiForWindow——此时DPI尚未生效,应使用lParam解包得到的DPI值。
Linux/X11下靠_NET_WORKAREA和XRandR间接推算不靠谱
Linux没有统一DPI API,Qt/GTK应用通常读取QT_SCALE_FACTOR或GDK_SCALE环境变量,但这只是启动时快照,无法反映运行时显示器热插拔或缩放变更。真正可行的做法是监听RRScreenChangeNotify事件,再调用XRRGetScreenResourcesCurrent + XRRGetOutputInfo查每个输出的物理尺寸与像素尺寸:
int mm_width = output_info->mm_width; int px_width = mode->width; float dpi = (px_width * 25.4f) / mm_width;
但问题在于:
– 多数笔记本外接显示器报告的mm_width不准(厂商填0或胡乱填);
– Wayland会屏蔽X11 RandR接口,gdk_monitor_get_scale_factor()才是Wayland原生方案;
– 即使拿到DPI,C++应用也得自己做坐标转换(如scale_x = x * scale),无系统级自动缩放。
macOS用[NSScreen backingScaleFactor]但注意时机
macOS下每个NSScreen对象都有backingScaleFactor属性,返回1.0(@1x)、2.0(@2x)或更高(如Mac Studio Display的3.0)。但它只在屏幕配置变更时才刷新,且C++代码需通过Objective-C桥接调用。
常见陷阱:
– 直接在NSApplicationMain前获取,可能拿到旧值;
– 没注册NSApplicationDidChangeScreenParametersNotification通知,导致热插拔显示器后未更新;
– 对HiDPI窗口使用CGDisplayCreateImageForRect截图时,若没用CGDisplayBounds而用CGDisplayPixelsWide,坐标会错乱;
– C++层调用ObjC方法需确保-fobjc-arc编译选项开启,否则NSScreen *可能提前释放。
真正稳定的写法是:在窗口viewDidMoveToWindow或收到屏幕变更通知后,重新查询当前[[NSScreen screens] objectAtIndex:0]的backingScaleFactor。
跨平台应用别指望一次写完就跑通——Windows靠API、macOS靠通知、Linux靠猜,连“实时”二字在各平台含义都不同:Windows是毫秒级同步,macOS有100ms延迟,X11基本靠轮询。最易被忽略的是DPI变更后资源重载的原子性:字体、图标、布局参数必须全部更新完毕再触发重绘,否则出现混合缩放的UI撕裂。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











