For one of my applications, I wanted to have a window customized under the Windows operating system (like Firefox, Avast, Microsoft Word, etc.). So I reimplemented some messages handling (QWidget::nativeEvent ()
) from Win32 API, in order to preserve the functionality of AeroSnap, and others.
Although it works perfectly, when my custom window is transferred from one screen to another (I have two screens), a visual glitch appears (as you can see below). After the glitch appears, resizing the window, correct the bug. Moreover, after some debugging, I noticed that the Qt QWidget::geometry()
is not the same as the geometry returned by Win32 API GetWindowRect()
when the bug appears.
Both of my monitors are HD (1920x1080, so is not the difference of resolutions which cause the bug, but maybe the DPI difference ?). The glitch seems to appear when the window is moving between the two screens (when windows transfer the window to one screen to another ?).
Screenshot of the window without the glitch
Screenshot of the window with the glitch
The geometry reported initially by QWidget::geometry()
is QRect(640,280 800x600)
, by QWidget::frameGeometry()
is QRect(640,280 800x600)
, and by the Win32 GetWindowRect()
is QRect(640,280 800x600)
. So, the same geometry. But, after the window is moved between the two monitors, the geometry reported by QWidget::geometry()
becomes QGeometry(1541,322 784x561)
. The geometry reported by QWidget::frameGeometry()
or GetWindowRect()
is unchanged. Of course, after that, when the window is resized, the geometry is re correctly reported, and the painting issue dissapears. Conclusion, Qt seems to assume that a "appear" frame when the window is moved between two monitors.
The BaseFramelessWindow
class implementation can be found here. This is a Qt QMainWindow subclass re-implementing some Win32 API native events (QWidget::nativeEvent()
).
So someone would be how to avoid this bug? Is this a Qt bug? I have tried many things, and nothing has really worked. And I can't find any mention of this problem on the internet (maybe I'm looking badly?).
Minimal example code:
// main.cpp
#include "MainWindow.h"
#include <QtWidgets/qapplication.h>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
MainWindow w;
w.show();
return a.exec();
}
// MainWindow.h
#pragma once
#include <QtWidgets/qmainwindow.h>
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow(QWidget *parent = Q_NULLPTR);
protected:
void paintEvent(QPaintEvent* event) override;
bool nativeEvent(const QByteArray& eventType, void* message, long* result) override;
};
// MainWindow.cpp
#include "MainWindow.h"
#include <QtGui/qpainter.h>
#include <Windows.h>
#include <Windowsx.h>
MainWindow::MainWindow(QWidget* parent)
: QMainWindow(parent)
{
::SetWindowLongPtr((HWND)winId(), GWL_STYLE, WS_POPUP | WS_THICKFRAME | WS_CAPTION | WS_SYSMENU | WS_MAXIMIZEBOX | WS_MINIMIZEBOX);
}
void MainWindow::paintEvent(QPaintEvent* event)
{
QPainter painter(this);
// Using GetWindowRect() instead of rect() seems to be a valid
// workaround for the painting issue. However many other things
// do not work cause of the bad geometry reported by Qt.
RECT winrect;
GetWindowRect(reinterpret_cast<HWND>(winId()), &winrect);
QRect rect = QRect(0, 0, winrect.right - winrect.left, winrect.bottom - winrect.top);
// Background
painter.fillRect(rect, Qt::red);
// Border
painter.setBrush(Qt::NoBrush);
painter.setPen(QPen(Qt::blue, 1));
painter.drawRect(rect.adjusted(0, 0, -1, -1));
// Title bar
painter.fillRect(QRect(1, 1, rect.width() - 2, 19), Qt::yellow);
}
bool MainWindow::nativeEvent(const QByteArray& eventType, void* message, long* result)
{
MSG* msg = reinterpret_cast<MSG*>(message);
switch (msg->message)
{
case WM_NCCALCSIZE:
*result = 0;
return true;
case WM_NCHITTEST: {
*result = 0;
RECT winrect;
GetWindowRect(reinterpret_cast<HWND>(winId()), &winrect);
// Code allowing to resize the window with the mouse is omitted.
long x = GET_X_LPARAM(msg->lParam);
long y = GET_Y_LPARAM(msg->lParam);
if (x > winrect.left&& x < winrect.right && y > winrect.top&& y < winrect.top + 20) {
// To allow moving the window.
*result = HTCAPTION;
return true;
}
repaint();
return false;
}
default:
break;
}
return false;
}