Fix:pinning-induced window position drift on Windows - #241
Conversation
There was a problem hiding this comment.
Pull request overview
This PR updates the Widgets MainWindow example’s “Pin” (stay-on-top) behavior on Windows to avoid repeated top-level flag changes that can recreate the native window handle and cause gradual frame position drift.
Changes:
- On Windows, implement pin/unpin by changing native z-order via
SetWindowPos(HWND_TOPMOST/HWND_NOTOPMOST)instead of togglingQt::WindowStaysOnTopHint+show(). - Cache the requested stay-on-top state and reapply it on
QEvent::WinIdChangeto survive native handle recreation. - Keep a geometry-preserving Qt-based fallback for non-Windows platforms and improve UI synchronization when pin requests can’t be applied.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
| examples/mainwindow/mainwindow.h | Adds helper API + state flags to track and reapply stay-on-top behavior. |
| examples/mainwindow/mainwindow.cpp | Implements Windows z-order pinning, WinIdChange reapply, and updated pin button handling. |
Comments suppressed due to low confidence (2)
examples/mainwindow/mainwindow.cpp:141
- This Windows-only event handling is guarded by
Q_OS_WINDOWS, but other Windows-specific blocks in this file useQ_OS_WIN. Using a single Windows macro consistently within the file avoids platform-guard mismatches.
#ifdef Q_OS_WINDOWS
case QEvent::WinIdChange:
if (!applyingStayOnTop) {
setStayOnTop(stayOnTop);
}
break;
#endif
examples/mainwindow/mainwindow.cpp:476
setStayOnTop()is currently compiled underQ_OS_WINDOWSwhile other Windows-specific logic in this file is underQ_OS_WIN. Consider using the same macro consistently to prevent any platform-guard mismatches.
bool MainWindow::setStayOnTop(bool enabled) {
#ifdef Q_OS_WINDOWS
if (applyingStayOnTop) {
return false;
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
|
看起来有道理,但我觉得应该是Qt内部有bug,你这样实际上只是从表层进行workaround吧? |
|
@wangwenx190 是的,这个改动属于 example 层的 workaround。问题发生在一个已经显示的顶层 QWidget 上调用 setWindowFlag(Qt::WindowStaysOnTopHint, ...) 并重新 show() 时;该过程可能重建 HWND。在 QWindowKit 自定义 non-client frame 的场景下,重建前后的 frame/client 坐标换算会导致窗口位置发生偏移,这部分可能是 Qt Windows platform plugin 的问题 |
|
你这个PR其实改了不少地方,我有点不确定为了workaround示例程序的问题,引入这种改动是否有必要 |
|
造成窗口整体32px的偏移的具体原因如下:
从调试日志中得出窗口整体偏移32px的来源. titleBarHeight=31
visibleFrameBorder=1 |
|
感谢你的分析!这个问题我觉得应该在QWK层面进行workaround,而不是在示例程序那边,不知道你有没有什么好办法呢 |
|
具体流程如下:
只差 1px,这是 Win11 visible frame border。 点击pin后调用了 setWindowFlag(Qt::WindowStaysOnTopHint, pin);
show();从日志中得出 在 QWK hook 尚未重新完整接管时,系统/Qt 处理了一次普通窗口的 WM_NCCALCSIZE: Qt 接下来把这个带标题栏偏移的 client 坐标当成 QWidget 的 geometry,后续又把窗口移动到这个位置: 这也就造成了后续的持续累加. |
|
或许我们应该用QWindow句柄作为一个窗口的唯一标志,然后观察HWND的变化 |
我觉得我们可以在agent中新增 windowAgent->setWindowAttribute("stay-on-top", true);
windowAgent->setWindowAttribute("stay-on-top", false);Windows下实现为 SetWindowPos(hwnd,
enabled ? HWND_TOPMOST : HWND_NOTOPMOST,
0, 0, 0, 0,
SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE | SWP_NOOWNERZORDER);这样应用层仍然只依赖 QWK,不需要碰 HWND,而且 QWK 避开了 Qt 的 |
|
只修复pin的话其实治标不治本,看起来只要Qt重建native窗口,就会触发问题 |
但是此种方法好像改动太大 |
或许 |
|
这个问题如果要在QWK里根治其实有点麻烦,因为我们可能要一直存储窗口的位置,在原生窗口重建后re-apply一下 |
|
我可以试着让AI帮忙搞个方法看看,如果你那边有更好的办法也可以提出来。改动大点暂时是可以接受的,毕竟也是为了修复QWK导致的问题 |
对的.而且我觉得我们的agent安装是不稳定的. |
不稳定?这个倒没考虑过,你是有什么看法呢 |
准确的来说是它对 HWND 生命周期的接管存在一个空窗期.在 old HWND 移除、新 HWND 完整接管之间,会有一段消息流不经过 QWK hook. |
|
|
#243 已经合了,这个PR应该没必要了吧? |
好的,感谢您的合并。 |
Fix pinning-induced window position drift on Windows
Summary
Fix the Widgets MainWindow example moving downward when the Pin button is toggled repeatedly on Windows.
The Pin action now changes only the native window z-order instead of changing
Qt::WindowStaysOnTopHintand showing the window again. The selected Pin state is cached and restored if Qt recreates the native window handle.Problem
The previous Pin callback used:
Changing a top-level window flag can recreate its native
HWND. For a frameless window with custom non-client handling, recreating and showing the window can alter the conversion between client and frame coordinates. Repeated Pin/Unpin operations therefore caused the window to drift downward by approximately one title-bar height per toggle.Changes
SetWindowPos()withHWND_TOPMOSTorHWND_NOTOPMOSTon Windows.SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE | SWP_NOOWNERZORDERso the operation changes only z-order.internalWinId()to avoid creating a native handle as a side effect.QEvent::WinIdChangeso pinning survives unrelated native handle recreation.This change is limited to the Widgets example and does not modify QWindowKit's public API.
Verification
QWKExample_MainWindowin Release mode with Qt 6.8.3 and MSVC.Scope
No core WindowAgent behavior or public API is changed. The fix only replaces the example application's Pin policy implementation.