SetWindowPos设置全屏窗口尺寸小于显示器问题咨询
WM_DISPLAYCHANGE的触发时机早于DWM(桌面窗口管理器)完成显示模式切换后的全量状态更新,此时调用EnumDisplayMonitors获取的显示器矩形、SystemParametersInfo获取的工作区尺寸多为临时中间值,直接调用SetWindowPos自然无法匹配正确的全屏尺寸。SWP_NOSENDCHANGING标记仅用于阻止窗口位置变动时发送WM_WINDOWPOSCHANGING消息,完全解决不了系统状态未就绪的时序问题,加了也不会有效果。
1. 延后尺寸调整的执行时机
不要在收到WM_DISPLAYCHANGE时立刻执行调整逻辑,通过PostMessage向自身窗口队列投递自定义调整消息,或设置100~300ms的单次定时器,等系统显示切换流程完全跑完后再执行尺寸调整,这是兼容性最高的方案。
// WM_DISPLAYCHANGE消息处理:仅投递调整消息,不直接执行操作 case WM_DISPLAYCHANGE: PostMessage(hWnd, WM_USER_FULLSCREEN_ADJUST, 0, 0); return 0; // 自定义调整消息中执行实际逻辑 case WM_USER_FULLSCREEN_ADJUST: AdjustWindowToFullscreen(hWnd); // 兜底:设置短定时器重试,覆盖外接显示器热插拔、多屏切换等系统响应慢的场景 KillTimer(hWnd, IDT_FULLSCREEN_RETRY); SetTimer(hWnd, IDT_FULLSCREEN_RETRY, 200, NULL); return 0; // 定时器触发时做校验重试 case WM_TIMER: if(wParam == IDT_FULLSCREEN_RETRY){ if(CheckWindowIsFullscreen(hWnd)){ KillTimer(hWnd, IDT_FULLSCREEN_RETRY); }else{ AdjustWindowToFullscreen(hWnd); } } return 0;
2. 修正全屏尺寸的计算逻辑
EnumDisplayMonitors枚举返回的MONITORINFO结构中,rcMonitor是显示器物理像素矩形,使用时要避开两个常见坑:
- 程序必须配置PerMonitorV2级别的DPI感知(在程序manifest中声明即可),否则系统会对API返回值做DPI虚拟化,拿到的尺寸和实际物理尺寸存在缩放偏差
- 真全屏需要覆盖任务栏时不要用
rcWork工作区尺寸,要使用rcMonitor值,同时调用AdjustWindowRectEx根据窗口样式反推包含非客户区(边框、标题栏)的实际窗口矩形,避免窗口边框占用像素导致客户区显示尺寸小于屏幕
// 正确获取全屏矩形的示例 HMONITOR hMonitor = MonitorFromWindow(hWnd, MONITOR_DEFAULTTONEAREST); MONITORINFO mi = {sizeof(MONITORINFO)}; GetMonitorInfo(hMonitor, &mi); RECT rcTarget = mi.rcMonitor; // 根据窗口样式反推实际窗口矩形,消除边框带来的尺寸偏差 AdjustWindowRectEx(&rcTarget, GetWindowLongPtr(hWnd, GWL_STYLE), FALSE, GetWindowLongPtr(hWnd, GWL_EXSTYLE));
3. SetWindowPos传参补全必要标记
不要仅传SWP_NOSENDCHANGING,配合以下标记可以避免窗口样式缓存、Z序问题导致的尺寸异常:
SetWindowPos( hWnd, HWND_TOPMOST, rcTarget.left, rcTarget.top, rcTarget.right - rcTarget.left, rcTarget.bottom - rcTarget.top, SWP_FRAMECHANGED | SWP_SHOWWINDOW );
- 加
SWP_FRAMECHANGED强制系统重新计算窗口非客户区尺寸,清除之前的窗口边框样式缓存 - 不需要窗口置顶的场景可以把
HWND_TOPMOST替换为HWND_NOTOPMOST
4. 增加校验兜底逻辑
每次执行完调整后,调用GetWindowRect获取窗口当前实际尺寸,和目标显示器的rcMonitor做比对,如果差值超过1像素就触发重试,最多重试3次即可覆盖绝大多数系统响应慢的极端场景。
不要在WM_DISPLAYCHANGE触发时调用SystemParametersInfo拿SPI_GETWORKAREA值,这个值的更新时机晚于显示器信息更新,大概率会拿到切换前的旧值。多屏场景下不要默认取主显示器尺寸,要通过MonitorFromWindow获取窗口当前所在显示器的对应矩形。
内容的提问来源于stack exchange,提问作者chenxu

