QWindow doesn't seem to respect resized ClientRect (windows)
-
We have QQuickView-derived windows, from which we subtract the nonclient-area window frames by handling the various WM_NCxx messages. Basically it's like the Spotify window; looks frameless but with the behavior of a window with caption (for dragging and snapping) and borders (for sizing).
That in itself works fine. The problem we're having is that the Qt contents of the window appear to occupy a rectangle that seems disconnected from the actual client rectangle. Native drawing direct into the window doesn't have this problem so the recaclulated client rectangle itself seems correct. That is to say, if I handle WM_PAINT to fill the client rect myself, the contents of the window will correctly occupy the window, like so:
However, if I instead fill the window with some simple Qt content, like so
MyWindow { id: rootWindow visible: true Rectangle { anchors.fill: parent color: "blue" } }
What we see is this:
It seems clear that some part of Qt is filling the window as if the nonclient features (titlebar, borders) were still there; that black area shouldn't be seen. Perhaps it's padding its own copy of the client rect with the dimensions of the system caption bar and borders whether or not those features are actually there.
My question is, what can I do to get Qt to see that the clientRect isn't what it thinks it is?
-
I thought to track down the errant Qt source by looking for where it obtains the system metrics for the size of a caption bar, border, etc. Unfortunately I can only find references to those metrics in widget styles and chromium. How does a Quick-type window even know those values?
What I mean is this: Qt seems to think the client area is y-offset by the height of a caption bar (and probably one border), and x-offset by the size of a border x2. That accounts for the incorrect black area in the second image. I'm just not certain where to find that in the source if it's not asking the system for those dimensions. Given the compile time I'm not anxious to do much exploratory surgery, so a nudge toward the correct region would be helpful.
Preemptive strike: Qt::framelessWindowHint is no help here. Setting that flag sets the underlying native style to WS_POPUP -- which is fine, unless you need snapping. Although you can still make "virtual" sizing edges work, you lose the snapping behavior with that window style.
In a regular native Windows app this is a trivial thing to accomplish, and in fact the same code transplanted to such Just Works. I just need to figure out where and how Qt is wrong about the client area.
-
Given that this has been posted as a bug and discussion topic in the past, here's my solution.
The issue lies in qtbase\src\plugins\platforms\windows\qwindowswindow.cpp. As I suspected, there's a disconnect between the actual client area as it's known to Windows, and the calculated client area as it's known to Qt. That's why WM_PAINTing to the client rect will fill the window correctly, while Qt content is incorrectly offset by the frame dimensions (even if there's no frame). Qt makes many assumptions about the layout of the window based on flags and styles, and those assumptions will become disconnected from reality if you do anything tricky with the client & nonclient areas.
To address this:
- I added a new Qt::WindowFlag in qnamespace.h
- In QWindowsWindow::frameMargins() I return a QMargins of 0,0,0,0 if my flag is set (as it does if Qt::FramelessWidnowHint is set)
- In my QQuickView-derived window, on the first NC_CALCSIZE I set my new flag and also "fix" the window style via SetWindowLong:
else if(msg->message == WM_NCCALCSIZE) { static bool framelessFix = false; if(!framelessFix) { framelessFix = true; Qt::WindowFlags wflags = flags(); wflags |= Qt::GkFrameless; setFlags(wflags); LONG style = GetWindowLong(hWnd, GWL_STYLE); style |= WS_POPUP | WS_TABSTOP | WS_GROUP | WS_THICKFRAME | WS_SYSMENU | WS_DLGFRAME | WS_BORDER | WS_CAPTION | WS_CLIPCHILDREN | WS_CLIPSIBLINGS | WS_VISIBLE; SetWindowLong(hWnd, GWL_STYLE, style); SetWindowPos(hWnd, NULL, 0, 0, 0, 0, SWP_FRAMECHANGED | SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_NOACTIVATE | SWP_NOREDRAW); } RETURNVAL(0); // macro, long* result = 0 NO_DEFAULT_WNDPROC; // macro, returns true, message is handled here }
The result is a window that looks frameless and still has all the frame behavior: sizing borders and snapping (provided you write a handler for NC_HITTEST that calculates whether the mouse is where a sizing border or caption bar would be -- outside the scope of this post).
It looks like someone attempted to fix this already; there's a comment there in QWindowsWindow::frameMargins() that implies you might be able to make this work with FramelessWindowHint and manually resetting the WS_ styles yourself, as I did. However, I felt adding a new WindowFlag was better as there are other references to FramelessWindowHint and I didn't want to alter the way it works.