Reputation: 194
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;
}
Upvotes: 5
Views: 2076
Reputation: 66
I had this exact error after and solved it by implementing Qt's moveEvent
to detect screen changes and then call SetWindowPos
to redraw the window in the same position.
An example would be:
class MainWindow : public QMainWindow
{
// rest of the class
private:
QScreen* current_screen = nullptr;
void moveEvent(QMoveEvent* event)
{
if (current_screen == nullptr)
{
current_screen = screen();
}
else if (current_screen != screen())
{
current_screen = screen();
SetWindowPos((HWND) winId(), NULL, 0, 0, 0, 0,
SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER |
SWP_NOOWNERZORDER | SWP_FRAMECHANGED | SWP_NOACTIVATE);
}
}
};
I'm not sure if the current_screen == nullptr
clause is strictly needed, but I had trouble with multiple windows/dialogs without it (sometimes they would become unresponsive). I also do not know which flags for SetWindowPos
are necessary.
I also cannot say whether this method is good practice, and it may cause problems elsewhere. Hopefully someone else can comment on this.
Either way it worked for me in a simple situation. Hopefully it provides a solution, or at least a starting point to try and solve this.
Upvotes: 5
Reputation: 2881
The problem is that Qt does not handle the drawing of the window correctly when WM_NCCALCSIZE
is modified!
I realized that you already have a work in progress, but i developed a parallel work that achieves your goals in a simple way that might interest you, it handles all the low level APIs of window for a border less window, while you can just use the setCentralWidget
API and your widget will fill the whole window!
The header of ExampleMinimal
(the clean example) is just:
#include <QGoodWindow>
class MainWindow : public QGoodWindow
{
Q_OBJECT
public:
MainWindow(QWidget *parent = nullptr);
~MainWindow();
};
See: https://github.com/antonypro/QGoodWindow
Upvotes: 0
Reputation: 4869
Check out this answer: How to integrate the Qt frameless window in Windows composer? (system shortcut doesnt works)
I had the exact same issue with snapping between screens, as mentioned in the 3rd paragraph of that answer:
I ran into some painting issues when snapping from one screen to another, but worked around that with a mask (notes in code comments). Would be nice to find a cleaner solution (it may qualify as a Qt bug actually).
And the workaround is at the bottom of the FramelessWidget
code block in the paintEvent()
method, after the long comment. I'll paste it here as well with a little context:
QStyleOption opt;
opt.initFrom(this);
// be sure to use the full frame size, not the default rect() which is inside frame.
opt.rect.setSize(frameSize());
.....
// Setting a mask works around an issue with artifacts when switching screens with Win+arrow
// keys. I don't think it's the actual mask which does it, rather it triggers the region
// around the widget to be polished but I'm not sure. As support for my theory, the mask
// doesn't even have to follow the border radius.
setMask(QRegion(opt.rect));
I don't see a paintEvent()
in your code but perhaps you can do something similar from elsewhere.
BTW this may also give you a hint about why QWidget::geometry()
isn't right... you probably want QWidget::frameGeometry()
.
Upvotes: 0