UPDATE: Here is a much better version. The pixelated corners were bugging me, among other things. This one looks smooth as silk and can be styled with either CSS or C++. It does require subclassing the QWidget
to be rounded (unlike the first version), but it's worth it. Again I'm using a QMessageBox
as the base widget here for simplicity (no layouts/etc), but it would work with any QWidget
(may need a Qt::Dialog
window flag added).
The message box implementation:
#include <QtWidgets>
class RoundedMessageBox : public QMessageBox
{
Q_OBJECT
public:
explicit RoundedMessageBox(QWidget *parent = nullptr) :
QMessageBox(parent)
{
// The FramelessWindowHint flag and WA_TranslucentBackground attribute are vital.
setWindowFlags(windowFlags() | Qt::FramelessWindowHint | Qt::WindowSystemMenuHint);
setAttribute(Qt::WA_TranslucentBackground);
}
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:
void paintEvent(QPaintEvent *ev) override
{
// If the transparency flag/hint aren't set then just use the default paint event.
if (!(windowFlags() & Qt::FramelessWindowHint) && !testAttribute(Qt::WA_TranslucentBackground)) {
QMessageBox::paintEvent(ev);
return;
}
// Initialize the painter.
QPainter p(this);
p.setRenderHint(QPainter::Antialiasing);
// Have style sheet?
if (testAttribute(Qt::WA_StyleSheetTarget)) {
// Let QStylesheetStyle have its way with us.
QStyleOption opt;
opt.initFrom(this);
style()->drawPrimitive(QStyle::PE_Widget, &opt, &p, this);
p.end();
return;
}
// Paint thyself.
QRectF rect(QPointF(0, 0), size());
// Check for a border size.
qreal penWidth = borderWidth;
if (penWidth < 0.0) {
// Look up the default border (frame) width for the current style.
QStyleOption opt;
opt.initFrom(this);
penWidth = style()->pixelMetric(QStyle::PM_DefaultFrameWidth, &opt, this);
}
// Got pen?
if (penWidth > 0.0) {
p.setPen(QPen(palette().brush(foregroundRole()), penWidth));
// Ensure border fits inside the available space.
const qreal dlta = penWidth * 0.5;
rect.adjust(dlta, dlta, -dlta, -dlta);
}
else {
// QPainter comes with a default 1px pen when initialized on a QWidget.
p.setPen(Qt::NoPen);
}
// Set the brush from palette role.
p.setBrush(palette().brush(backgroundRole()));
// Got radius? Otherwise draw a quicker rect.
if (radius > 0.0)
p.drawRoundedRect(rect, radius, radius, Qt::AbsoluteSize);
else
p.drawRect(rect);
// C'est finí
p.end();
}
};
Example usage showing both CSS and C++ styling options:
int main(int argc, char *argv[])
{
//QApplication::setStyle("Fusion");
QApplication app(argc, argv);
// Dialog setup
RoundedMessageBox *msgBox = new RoundedMessageBox();
msgBox->setAttribute(Qt::WA_DeleteOnClose);
msgBox->setMinimumSize(300, 300);
msgBox->setWindowTitle("Frameless window test");
msgBox->setText("<h3>Frameless rounded message box.</h3>");
msgBox->setInformativeText("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.");
if (1) {
// Use QSS style
app.setStyleSheet(QStringLiteral(
"QDialog { "
"border-radius: 12px; "
"border: 3.5px solid; "
"border-color: qlineargradient(x1: 1, y1: 1, x2: 0, y2: 0, stop: 0 #ffeb7f, stop: 1 #d09d1e); "
"background: qlineargradient(x1: 0, y1: 0, x2: 1, y2: 1, stop: 0 #ffeb7f, stop: 1 #d09d1e); "
"color: #003200; "
"}"
));
}
else {
// Use "native" styling
msgBox->radius = 12.0;
msgBox->borderWidth = 3.5;
QLinearGradient bgGrad(0, 0, 1, 1);
bgGrad.setCoordinateMode(QGradient::ObjectMode);
bgGrad.setColorAt(0.0, QColor("gold").lighter());
bgGrad.setColorAt(1.0, QColor("goldenrod").darker(105));
QLinearGradient fgGrad(bgGrad);
fgGrad.setStart(bgGrad.finalStop());
fgGrad.setFinalStop(bgGrad.start());
QPalette pal;
pal.setBrush(QPalette::Window, QBrush(bgGrad));
pal.setBrush(QPalette::Mid, QBrush(fgGrad));
pal.setBrush(QPalette::WindowText, QColor("darkgreen").darker());
msgBox->setPalette(pal);
msgBox->setForegroundRole(QPalette::Mid); // default is WindowText
msgBox->setBackgroundRole(QPalette::Window); // this is actually the default already
}
// Drop shadow doesn't work.
// QGraphicsDropShadowEffect* effect = new QGraphicsDropShadowEffect();
// effect->setBlurRadius(2);
// msgBox->setGraphicsEffect(effect);
msgBox->show();
return app.exec();
}

ORIGINAL: using a generic QMessageBox and setting a mask on it.
Turned out I could use a simple frameless dialog for an "about" message box in a new app... so here you go. This is the simplest method I can think of w/out re-implementing the widget painting process altogether (like in the Qt clock example). But there's clearly limits to this implementation, and I haven't tried it on a Mac yet.
Also, the drop shadow came in very handy... although you can't actually see it, it does a great job smoothing out the corners. Great idea, even if that wasn't the original intention. :)
#include <QtWidgets>
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
// Dialog setup. Actually use a QMessageBox for a shorter example.
QMessageBox *msgBox = new QMessageBox();
msgBox->setAttribute(Qt::WA_DeleteOnClose);
msgBox->setMinimumSize(300, 300);
msgBox->setWindowTitle("Frameless window test"); // might still be visible eg. in a taskbar
msgBox->setText("<h3>Frameless rounded message box.</h3>");
msgBox->setInformativeText("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.");
// Here come the styling bits... First need the frameless window flag hint
msgBox->setWindowFlags(msgBox->windowFlags() | Qt::FramelessWindowHint | Qt::WindowSystemMenuHint);
// The desired border radius;
const int radius = 12;
// Style the box with CSS. Set the border radius here.
// The border style helps blend the corners, but could be omitted.
// The background is optional... could add other styling here too.
msgBox->setStyleSheet(QString(
"QDialog { "
"border-radius: %1px; "
"border: 2px solid palette(shadow); "
"background-color: palette(base); "
"}"
).arg(radius));
// The effect will not be actually visible outside the rounded window,
// but it does help get rid of the pixelated rounded corners.
QGraphicsDropShadowEffect* effect = new QGraphicsDropShadowEffect();
// The color should match the border color set in CSS.
effect->setColor(QApplication::palette().color(QPalette::Shadow));
effect->setBlurRadius(5);
msgBox->setGraphicsEffect(effect);
// Need to show the box before we can get its proper dimensions.
msgBox->show();
// Here we draw the mask to cover the "cut off" corners, otherwise they show through.
// The mask is sized based on the current window geometry. If the window were resizable (somehow)
// then the mask would need to be set in resizeEvent().
const QRect rect(QPoint(0,0), msgBox->geometry().size());
QBitmap b(rect.size());
b.fill(QColor(Qt::color0));
QPainter painter(&b);
painter.setRenderHint(QPainter::Antialiasing);
painter.setBrush(Qt::color1);
// this radius should match the CSS radius
painter.drawRoundedRect(rect, radius, radius, Qt::AbsoluteSize);
painter.end();
msgBox->setMask(b);
return app.exec();
}
