I worked on Windows platform. If I used native frameless window flags like:
::SetWindowLongPtr((HWND)winId(), GWL_STYLE, WS_POPUP | WS_SYSMENU | WS_THICKFRAME | WS_MAXIMIZEBOX | WS_MINIMIZEBOX);
I recieve frameless window that correctly works with default Windows windows composer - it correctly change the state when I press "WIN" key + arrows.
When I try to use Qt library with following frameless window flags:
setWindowFlags(Qt::Popup | Qt::FramelessWindowHint | Qt::WindowSystemMenuHint | Qt::WindowMinMaxButtonsHint | Qt::CustomizeWindowHint);
I recieve the window that doesnt responce on the "WIN" key + arrows. So it doesnt works with default Windows window composer.
Which compbination of Qt window flags would have similar behavior like native flags above?
So, indeed this has little to do with Qt, it seems. Having anything but a full frame on a Windows window seems to disable the "snap" shortcuts, even if the window can still be resized or moved with keyboard arrows (from the Alt+Space system menu).
The workaround is actually pretty simple. Basically to implement the QWidget::nativeEvent()
virtual method and ignore the WM_NCCALCSIZE
message.
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).
As a bonus this also allows for rounded corners. The painting/styling is based on a rounded message box I made for another answer, and the painting code is documented more fully over there.
The system menu and all interactions with keys (move/resize/snap/etc) work. Mouse handling could be added by implementing the WM_NCHITTEST
message in nativeEvent()
(see references below).
Only tested on Win7, would be curious how it acts on Win10.
FramelessWidget
#include <QPainter>
#include <QPalette>
#include <QStyle>
#include <QStyleOption>
#include <QWidget>
#include <qt_windows.h>
class FramelessWidget : public QWidget
{
Q_OBJECT
public:
explicit FramelessWidget(QWidget *p = nullptr, Qt::WindowFlags f = Qt::Window) :
// the flags set here should "match" the GWL_STYLE flags set below
QWidget(p, f | Qt::WindowMinMaxButtonsHint | Qt::WindowSystemMenuHint | Qt::WindowCloseButtonHint | Qt::FramelessWindowHint)
{
setAttribute(Qt::WA_TranslucentBackground); // for rounded corners
// set flags which will override what Qt does, especially with the Qt::FramelessWindowHint which essentially disables WS_SIZEBOX/WS_THICKFRAME
SetWindowLongPtr(HWND(winId()), GWL_STYLE, WS_POPUPWINDOW | WS_CAPTION | WS_SIZEBOX | WS_MAXIMIZEBOX | WS_MINIMIZEBOX | WS_CLIPCHILDREN);
}
// these settings are only used when no styleSheet is set
qreal radius = 0.0; // desired radius in absolute pixels
qreal borderWidth = -1.0; // -1: use style hint frame width; 0: no border; > 0: use this width.
protected:
bool nativeEvent(const QByteArray &eventType, void *message, long *result) override
{
MSG *msg = static_cast<MSG*>(message);
if (msg && msg->message == WM_NCCALCSIZE) {
// Just return 0 and mark event as handled. This will draw the widget contents
// into the full size of the frame, instead of leaving room for it.
*result = 0;
return true;
}
return QWidget::nativeEvent(eventType, message, result);
}
// Override paint event because of transparent background.
// Can be styled using CSS or QPalette with backgroundRole()/foregroundRole().
void paintEvent(QPaintEvent *) override
{
QPainter p(this);
p.setRenderHints(QPainter::Antialiasing);
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());
if (testAttribute(Qt::WA_StyleSheetTarget)) {
style()->drawPrimitive(QStyle::PE_Widget, &opt, &p, this);
setMask(QRegion(opt.rect)); // see notes below
return;
}
QRectF rect(opt.rect);
qreal penWidth = borderWidth;
if (penWidth < 0.0)
penWidth = style()->pixelMetric(QStyle::PM_DefaultFrameWidth, &opt, this);
if (penWidth > 0.0) {
p.setPen(QPen(palette().brush(foregroundRole()), penWidth));
const qreal dlta = (penWidth * 0.5);
rect.adjust(dlta, dlta, -dlta, -dlta);
}
else {
p.setPen(Qt::NoPen);
}
p.setBrush(palette().brush(backgroundRole()));
if (radius > 0.0)
p.drawRoundedRect(rect, radius, radius, Qt::AbsoluteSize);
else
p.drawRect(rect);
// 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));
}
}; // FramelessWidget
Test implementation
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
//QLoggingCategory::setFilterRules(QStringLiteral("qt.qpa.windows = true\n"));
FramelessWidget msgBox;
msgBox.setWindowTitle("Frameless window test");
msgBox.setLayout(new QVBoxLayout);
msgBox.layout()->addWidget(new QLabel(QStringLiteral("<h3>Frameless rounded widget.</h3>"), &msgBox));
QLabel *lbl = new QLabel(QStringLiteral(
"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean fermentum erat rhoncus, "
"scelerisque eros ac, hendrerit metus. Nunc ac lorem id tortor porttitor mollis. Nunc "
"tristique orci vel risus convallis, non hendrerit sapien condimentum. Phasellus lorem tortor, "
"mollis luctus efficitur id, consequat eget nulla. Nam ac magna quis elit tristique hendrerit id "
"at erat. Integer id tortor elementum, dictum urna sed, tincidunt metus. Proin ultrices tempus "
"lacinia. Integer sit amet fringilla nunc."
), &msgBox);
lbl->setWordWrap(true);
msgBox.layout()->addWidget(lbl);
QPushButton *pb = new QPushButton(QStringLiteral("Close"), &msgBox);
QObject::connect(pb, &QPushButton::clicked, qApp, &QApplication::quit);
msgBox.layout()->addItem(new QSpacerItem(1,1, QSizePolicy::Expanding, QSizePolicy::Expanding));
msgBox.layout()->addWidget(pb);
msgBox.setStyleSheet(QStringLiteral(
"FramelessWidget { "
"border-radius: 12px; "
"border: 3px solid palette(shadow); "
"background: qlineargradient(x1: 0, y1: 0, x2: 1, y2: 1, stop: 0 #ffeb7f, stop: 1 #d09d1e); "
"}"
));
msgBox.show();
return a.exec();
}
References:
WM_NCHITTEST
implementation)WM_NCHITTEST
version)qwindowsintegration
, qwindowswindow
, qwindowscontext
)