How to round the corners of a frameless QWidget, when also applying acrylic effects?
-
Hello everyone! I like to make a dock in Qt for Windows. I have the general idea of how to do it, and I would first like to focus on making the window look pretty. Now I would like to provide the user with an option to change the corner radius of the dock, and also allow for Acrylic or Mica (introduced in W11.) Basically, I want to achieve custom border radii on a frameless window with acrylic.
Now, I attempted some stuff:
- Set stylesheet with border radius property.
// inside the constructor setStyleSheet("border-radius: 32px;");- Add a custom mask
Dock::Dock(QWidget *parent):QWidget(parent) { // after other methods are called. roundZeCorners(); } void Dock::roundZeCorners(int radius) { QPainterPath path; path.addRoundedRect(rect(), radius, radius); setMask(QRegion(path.toFillPolygon().toPolygon())); } void Dock::paintEvent(QPaintEvent *event) { QPainter painter(this); painter.setRenderHint(QPainter::Antialiasing); QPainterPath path; path.addRoundedRect(rect(), border_radius, border_radius); painter.setClipPath(path); painter.fillPath(path, Qt::transparent); }These are the only two solutions I came across to make custom window as described, but neither work. The stylesheet was obvious, because I read that qwidget not support styling border radius, and besides, it is top level window.
Here is how I apply my window effects:
#ifdef Q_OS_WIN void Dock::applyWindowEffect(uint tint_color) { const HINSTANCE hModule = LoadLibraryA("user32.dll"); if (hModule) { struct ACCENTPOLICY { int nAccentState; int nFlags; DWORD nColor; int nAnimationId; }; struct WINCOMPATTRDATA { int nAttribute; PVOID pData; ULONG ulDataSize; }; typedef BOOL(WINAPI* pSetWindowCompositionAttribute)(HWND, WINCOMPATTRDATA*); const auto SetWindowCompositionAttribute = (pSetWindowCompositionAttribute)GetProcAddress(hModule, "SetWindowCompositionAttribute"); if (SetWindowCompositionAttribute) { ACCENTPOLICY policy = { 4, 0, tint_color, 0 }; // 4 = ACCENT_ENABLE_ACRYLICBLURBEHIND WINCOMPATTRDATA data = { 19, &policy, sizeof(ACCENTPOLICY) }; // 19 = WCA_ACCENT_POLICY SetWindowCompositionAttribute((HWND)winId(), &data); } FreeLibrary(hModule); } } #endifHow would I add the rounded corners? Sorry if this is a stupid post.
-
Hello everyone! I like to make a dock in Qt for Windows. I have the general idea of how to do it, and I would first like to focus on making the window look pretty. Now I would like to provide the user with an option to change the corner radius of the dock, and also allow for Acrylic or Mica (introduced in W11.) Basically, I want to achieve custom border radii on a frameless window with acrylic.
Now, I attempted some stuff:
- Set stylesheet with border radius property.
// inside the constructor setStyleSheet("border-radius: 32px;");- Add a custom mask
Dock::Dock(QWidget *parent):QWidget(parent) { // after other methods are called. roundZeCorners(); } void Dock::roundZeCorners(int radius) { QPainterPath path; path.addRoundedRect(rect(), radius, radius); setMask(QRegion(path.toFillPolygon().toPolygon())); } void Dock::paintEvent(QPaintEvent *event) { QPainter painter(this); painter.setRenderHint(QPainter::Antialiasing); QPainterPath path; path.addRoundedRect(rect(), border_radius, border_radius); painter.setClipPath(path); painter.fillPath(path, Qt::transparent); }These are the only two solutions I came across to make custom window as described, but neither work. The stylesheet was obvious, because I read that qwidget not support styling border radius, and besides, it is top level window.
Here is how I apply my window effects:
#ifdef Q_OS_WIN void Dock::applyWindowEffect(uint tint_color) { const HINSTANCE hModule = LoadLibraryA("user32.dll"); if (hModule) { struct ACCENTPOLICY { int nAccentState; int nFlags; DWORD nColor; int nAnimationId; }; struct WINCOMPATTRDATA { int nAttribute; PVOID pData; ULONG ulDataSize; }; typedef BOOL(WINAPI* pSetWindowCompositionAttribute)(HWND, WINCOMPATTRDATA*); const auto SetWindowCompositionAttribute = (pSetWindowCompositionAttribute)GetProcAddress(hModule, "SetWindowCompositionAttribute"); if (SetWindowCompositionAttribute) { ACCENTPOLICY policy = { 4, 0, tint_color, 0 }; // 4 = ACCENT_ENABLE_ACRYLICBLURBEHIND WINCOMPATTRDATA data = { 19, &policy, sizeof(ACCENTPOLICY) }; // 19 = WCA_ACCENT_POLICY SetWindowCompositionAttribute((HWND)winId(), &data); } FreeLibrary(hModule); } } #endifHow would I add the rounded corners? Sorry if this is a stupid post.
@grainyblob
Why doesaddRoundedRectnot work? What's the desired and actual outcome?
Have you tried to draw it directly withQPainter::drawRoundedRect()?
Maybe also set an explicit brush, just for testing. -
Please try to call 'setMask' to round borders in 'resizeEvent' and 'changeEvent -> QEvent::WindowStateChange event' too.
-
@grainyblob
Why doesaddRoundedRectnot work? What's the desired and actual outcome?
Have you tried to draw it directly withQPainter::drawRoundedRect()?
Maybe also set an explicit brush, just for testing.@Axel-Spoerl
The desired outcome was to make a acrylic window with a custom border radius. (See attached image). Once I add a roundedRect to the path I set a mask from the path. Now in a normal QWidget, it will round the corners, but my guess is that because of the DWM code I'm using, it's overriding the composition and displaying a rect.

see above for desired result ↑Once again, sorry if my explaination of my issue is bad, I really struggle to explain things most of the time.
Edit: I forgot to ask, what do you mean by "explicit brush"?
-
@Axel-Spoerl
The desired outcome was to make a acrylic window with a custom border radius. (See attached image). Once I add a roundedRect to the path I set a mask from the path. Now in a normal QWidget, it will round the corners, but my guess is that because of the DWM code I'm using, it's overriding the composition and displaying a rect.

see above for desired result ↑Once again, sorry if my explaination of my issue is bad, I really struggle to explain things most of the time.
Edit: I forgot to ask, what do you mean by "explicit brush"?
@grainyblob said in How to round the corners of a frameless QWidget, when also applying acrylic effects?:
I forgot to ask, what do you mean by "explicit brush"?
Your own QBrush instance which you pass to the painter. https://doc.qt.io/qt-6/qbrush.html
-
Please try to call 'setMask' to round borders in 'resizeEvent' and 'changeEvent -> QEvent::WindowStateChange event' too.
@Vitalii777
have tried to callsetMask()in all events, none of them caused any change. -
@grainyblob said in How to round the corners of a frameless QWidget, when also applying acrylic effects?:
I forgot to ask, what do you mean by "explicit brush"?
Your own QBrush instance which you pass to the painter. https://doc.qt.io/qt-6/qbrush.html
Yes in that case, it works. The corners of the window are rounded as expected. It is only with my
applyWindowEffects()method which fails to round corners in this method.Alternatively, since it might be a DWM level issue, I'm considering trying to replicate what I do with masking but with native DWM API.
-
You can use my logic of making rounded corners QWidget:
#include "RoundedBox.h"
#include <QPainter>
#include <QPainterPath>
#include <QBitmap>
#include <QFontMetrics>RoundedBox::RoundedBox(const QString &txt, QWidget *parent)
: QWidget(nullptr), isDarkMode(false), text(txt), useAsToolTip(false)
{
setWindowFlags(Qt::FramelessWindowHint | Qt::NoDropShadowWindowHint);
setAttribute(Qt::WA_TranslucentBackground);
setAsToolTip(false);
}void RoundedBox::enableDarkMode(bool value)
{
isDarkMode = value;
}void RoundedBox::setAsToolTip(bool value) {
useAsToolTip = value;
if (useAsToolTip) {
setWindowFlag(Qt::ToolTip);
updateSizeForText();
} else {
setWindowFlag(Qt::Popup);
}
update();
}void RoundedBox::updateSizeForText() {
resize(sizeHint());
}QSize RoundedBox::sizeHint() const {
QFont font;
font.setPointSize(9);
font.setFamily("Segoe UI");QFontMetrics fm(font); int MAX_W = 400; QSize s = fm.boundingRect(0, 0, MAX_W, 0, Qt::TextWordWrap, text).size(); return QSize(s.width() + 24, s.height() + 12);}
void RoundedBox::paintEvent(QPaintEvent *event)
{
Q_UNUSED(event);// Rounded mask QBitmap bitmap(width(), height()); bitmap.fill(Qt::color0); QPainter maskPainter(&bitmap); maskPainter.setRenderHints(QPainter::Antialiasing); QPainterPath maskPath; maskPath.addRoundedRect(rect(), 6, 6); maskPainter.fillPath(maskPath, Qt::color1); setMask(bitmap); // Colors QColor BG = isDarkMode ? QColor("#2D2D2D") : QColor("#FFFFFF"); QColor BR = isDarkMode ? QColor("#4D4D4D") : QColor("#BDBDBD"); QPainter painter(this); painter.setRenderHints(QPainter::Antialiasing); painter.setBrush(BG); QPen pen(BR); pen.setWidth(1); painter.setPen(pen); QPainterPath path; path.addRoundedRect(rect().adjusted(1.5, 1.5, -1.5, -1.5), 6, 6); painter.drawPath(path); // Text if (useAsToolTip) { QFont font; font.setPointSize(9); font.setFamily("Segoe UI"); painter.setFont(font); painter.setPen(isDarkMode ? QColor("#F0F0F0") : QColor("#000000")); QRect text_area(12, 0, width() - 24, height()); painter.drawText(text_area, Qt::AlignCenter | Qt::TextWordWrap, text); }}
-
Please format your code!
-
I encountered the same problem, and after spending over 6 hours on this, I'm so proud to say that I have a solution! Anyway, there's also an limitation, I'll mention it later.
well, in breif, this is it:
visualEffect.h:
#include <dwmapi.h> // note that you may have to use msvc for this // More friendly names enum EffectType : int { EffectType_Default = DWMSBT_AUTO, EffectType_None = DWMSBT_NONE, EffectType_Mica = DWMSBT_MAINWINDOW, EffectType_Acrylic = DWMSBT_TRANSIENTWINDOW, EffectType_MicaAlt = DWMSBT_TABBEDWINDOW }; enum CornerPreference : int { Corner_Default = DWMWCP_DEFAULT, Corner_NoRound = DWMWCP_DONOTROUND, Corner_Round = DWMWCP_ROUND, Corner_RoundSmall = DWMWCP_ROUNDSMALL }; bool SetAcrylicEffect(HWND hwnd, EffectType type = EffectType_Mica, CornerPreference corner = Corner_Round);visualEffect.cpp
#include "visualEffect.h" bool SetAcrylicEffect(HWND hwnd, EffectType type, CornerPreference corner) { if(!hwnd) return false; HRESULT hr1, hr2; DWM_SYSTEMBACKDROP_TYPE backdrop = static_cast<DWM_SYSTEMBACKDROP_TYPE>(type); hr1 = DwmSetWindowAttribute(hwnd, DWMWA_SYSTEMBACKDROP_TYPE, &backdrop, sizeof(backdrop)); // set rounded corner preference if(corner != Corner_Default) { // when it's not default DWM_WINDOW_CORNER_PREFERENCE cp = static_cast<DWM_WINDOW_CORNER_PREFERENCE>(corner); hr2 = DwmSetWindowAttribute(hwnd, DWMWA_WINDOW_CORNER_PREFERENCE, &cp, sizeof(cp)); } else { hr2 = S_OK; // regard as success } return SUCCEEDED(hr1) && SUCCEEDED(hr2); }You need to link dwmapi like this in your CMakeLists.txt:
target_link_libraries(YourApplication PRIVATE dwmapi)Some important things you need to know:
- Corner_Round is fixed 8px radius, and Corner_RoundSmall is fixed 4px. You can not change this, because this is builtin for dwm. There values are usually good enough, so if you really want it you should just set your window to 8px radius.
- after using
setAttribute(Qt::WA_Translucentbackground), the transparent part for your window is not regarded as the window region, so it acts like non-client area. And if you use the stylesheet or paintEvent on your centralwidget or something, the effect looks broken.
So the best approach on Qt, is set another overlay widget at the bottom, and put everything else on top of it.
The overlay widget should have something like this:#overlay { background: rgba(255, 255, 255, 2); /*1 makes it not transparent at all, and 0 does not work*/ }and this:
// something here { ui->setupUi(this); setAttribute(Qt::WA_TranslucentBackground); if(!SetAcrylicEffect((HWND)this->winId(), EffectType_Acrylic, Corner_Round)) { qWarning() << "Failed to launch Acrylic Effect, fallback to default transparency" << GetLastError(); } }also this:

And you're done!
You can find the full version on my github, but the above one should be completely usable already.