Frameless (Qt + WinAPI) maximized window size is bigger than the availableGeometry()
-
Hello, guys!
I'm struggling to solve the following problem. My window top left corner has negative coordinates when maximized (both x and y are -9), and it's size is actually bigger than the available area.
I'm using WinAPI to get some platform specific features like Aero snap, so it becomes complicated to understand why exactly this happens.
My window has the following styles and attributes set:setFlags(Qt::Window | Qt::FramelessWindowHint); SetWindowLongPtr((HWND)winId(), GWL_STYLE, WS_POPUPWINDOW | WS_CAPTION | WS_SIZEBOX | WS_MAXIMIZEBOX | WS_MINIMIZEBOX | WS_CLIPCHILDREN);
WS_CAPTION
is same asWS_BORDER | WS_DLGFRAME
, so if I removeWS_DLGFRAME
, maximized size is ok, and I see that the native maximizing animation disappears — but I get the flickering white border when resize because ofWS_SIZEBOX
(same asWS_THICKFRAME
). WithoutWS_SIZEBOX
I loose aero snap.
I thought maybe I can do something with maximixed size innativeEvent
, so I tried the following:bool FramelessWindow::nativeEvent(const QByteArray& eventType, void* message, qintptr* result) { if (auto* msg = static_cast<MSG*>(message); msg->message == WM_NCCALCSIZE) { NCCALCSIZE_PARAMS& params = *reinterpret_cast<NCCALCSIZE_PARAMS*>(msg->lParam); if (params.rgrc[0].top != 0) { --params.rgrc[0].top; } *result = WVR_REDRAW; return true; //this removes the native border and title } else if (msg->message == WM_GETMINMAXINFO) { if (IsMaximized((HWND)winId())) { MINMAXINFO* mmi = (MINMAXINFO*)msg->lParam; auto g = screen()->availableGeometry(); mmi->ptMaxTrackSize.x = g.width() * devicePixelRatio(); mmi->ptMaxTrackSize.y = g.height() * devicePixelRatio(); *result = 0; return true; } } return QQuickWindow::nativeEvent(eventType, message, result); }
For some reason,
MINMAXINFO
overloading does nothing until I setmmi->ptMaxTrackSize.x = g.width() * devicePixelRatio() - 1;
. In this case, the size is almost nice, except that there is a 1 px empty gap between the right border of the screen and the window. So there is no way I can obtain the correct size with changingMINMAXINFO
, because the real correct size should be actually same as in the above code. The initial ptMaxTrackSize is pretty strange: 3462, 1462 inmmi
vs 3440, 1390 inavailableGeometry
).I can do something like that, but I'd like to keep QWindow::Maximized state, which is reset when I call
setGeometry
.bool FramelessWindow::eventFilter(QObject* watched, QEvent* event) { if (event->type() == QEvent::WindowStateChange && QWindow::visibility() == QWindow::Maximized) { setGeometry(screen()->availableGeometry()); //the window is no longer maximized return true; } return QQuickWindow::eventFilter(watched, event); }
Since I'm not able to use
QWindow::Maximized
insetGeometry
approach, this approach equals to implementing my own maximize, normalize and so on, what is undesirable.I also tried to add margins in maximized state, but creating a layout causes ugly flickering when resize, since the instance of my window is created in QML. And I'm unable to use
setLayout
, because I inheritedQQuickWindow
.Am I missing something?
-
I have found out that this problem is mainly related to WinAPI + framelessness, not to Qt. I didn't manage to find any working WinAPI solution, for example, I've tried this one: melak47's solution. So I've chosen Qt way. This approach is not as concise as I expected from the WinAPI approach, but it works.
Here is a code snippet describing only the necessary parts..hpp
class FramelessWindow : public QQuickWindow { Q_OBJECT QML_ELEMENT Q_PROPERTY(bool isMaximized READ isMaximized NOTIFY isMaximizedChanged) signals: void isMaximizedChanged(); public: FramelessWindow() noexcept; Q_INVOKABLE void showNormal() noexcept; Q_INVOKABLE void showMaximized() noexcept; bool isMaximized() const noexcept; private: bool eventFilter(QObject* watched, QEvent* event) override; bool nativeEvent(const QByteArray& eventType, void* message, qintptr* result) override; QRect restoredGeometry_; bool isMaximized_; };
.cpp
FramelessWindow::FramelessWindow() noexcept : isMaximized_ { false } { setFlags(Qt::Window | Qt::FramelessWindowHint | Qt::WindowSystemMenuHint | Qt::WindowMaximizeButtonHint); installEventFilter(this); SetWindowLongPtr((HWND)winId(), GWL_STYLE, WS_POPUP | WS_THICKFRAME | WS_CAPTION | WS_SYSMENU | WS_MAXIMIZEBOX | WS_MINIMIZEBOX); } void FramelessWindow::showNormal() noexcept { setGeometry(restoredGeometry_); isMaximized_ = false; emit isMaximizedChanged(); } void FramelessWindow::showMaximized() noexcept { restoredGeometry_ = geometry(); setGeometry(screen()->availableGeometry()); isMaximized_ = true; emit isMaximizedChanged(); } bool FramelessWindow::isMaximized() const noexcept { return isMaximized_; } bool FramelessWindow::eventFilter(QObject* watched, QEvent* event) { QPoint cursorPos = QCursor::pos(); qreal dpr = devicePixelRatio(); QRect draggingArea = geometry(); draggingArea.setHeight(32 * dpr); draggingArea.setY(draggingArea.y() + dpr * ResizeBorderWidth); if (draggingArea.contains(cursorPos)) { if (event->type() == QEvent::MouseButtonPress) { if (isMaximized_) { restoredGeometry_.moveTo({ QCursor::pos().x() - restoredGeometry_.width() / 2, QCursor::pos().y() - 10 }); showNormal(); } startSystemMove(); return true; } else if (isResizable_ && event->type() == QEvent::MouseButtonDblClick) { if (draggingArea.contains(cursorPos)) { if (isMaximized_) { showNormal(); } else { showMaximized(); } return true; } } else if (event->type() == QEvent::WindowStateChange && QWindow::visibility() == QWindow::Maximized) { setGeometry(screen()->availableGeometry()); isMaximized_ = true; emit isMaximizedChanged(); return true; } } return QQuickWindow::eventFilter(watched, event); } bool FramelessWindow::nativeEvent(const QByteArray& eventType, void* message, qintptr* result) { if (auto* msg = static_cast<MSG*>(message); msg->message == WM_NCCALCSIZE) { NCCALCSIZE_PARAMS& params = *reinterpret_cast<NCCALCSIZE_PARAMS*>(msg->lParam); if (params.rgrc[0].top != 0) { --params.rgrc[0].top; } *result = 0; return true; } return QQuickWindow::nativeEvent(eventType, message, result); }
-