Is there any way to blur a widget in Qt? For instance, supose I want to create a \'Loading...\' dialog and blur the background (not active window).
You can refer to this article if you want to apply blur effect on an image. After you create your blurred image you can draw it in QWidget::paintEvent()
function.
See QGraphicsBlurEffect Class and QWidget::setGraphicsEffect()
.
This answer is in a series of my overlay-related answers: first, second, third.
It requires some care if you wish for it to work on all platforms. You can't apply effects directly to top-level windows. The hierarchy needs to look as follows:
ContainerWidget
|
+----------+
| |
**Target** Overlay
You apply the effect to the Target
widget (say, a QMainWindow
). The ContainerWidget
is a helper class that keeps the children occupying the full size of the widget. This obviates the need for an explicit zero-margin layout.
The below works, even on a Mac. It wouldn't, had you foregone the ContainerWidget
. This works portably on Qt 5 only, unfortunately. On Qt 4, your "cross platform" support excludes Mac :( It works OK on Windows using either Qt 4 (4.8.5) or Qt 5.
// https://github.com/KubaO/stackoverflown/tree/master/questions/overlay-blur-19383427
#include <QtGui>
#if QT_VERSION >= QT_VERSION_CHECK(5,0,0)
#include <QtWidgets>
#endif
class OverlayWidget : public QWidget {
void newParent() {
if (!parent()) return;
parent()->installEventFilter(this);
raise();
}
public:
explicit OverlayWidget(QWidget *parent = {}) : QWidget(parent) {
setAttribute(Qt::WA_NoSystemBackground);
setAttribute(Qt::WA_TransparentForMouseEvents);
newParent();
}
protected:
//! Catches resize and child events from the parent widget
bool eventFilter(QObject *obj, QEvent *ev) override {
if (obj == parent()) {
if (ev->type() == QEvent::Resize)
resize(static_cast<QResizeEvent*>(ev)->size());
else if (ev->type() == QEvent::ChildAdded)
raise();
}
return QWidget::eventFilter(obj, ev);
}
//! Tracks parent widget changes
bool event(QEvent *ev) override {
if (ev->type() == QEvent::ParentAboutToChange) {
if (parent()) parent()->removeEventFilter(this);
}
else if (ev->type() == QEvent::ParentChange)
newParent();
return QWidget::event(ev);
}
};
class ContainerWidget : public QWidget
{
public:
explicit ContainerWidget(QWidget *parent = {}) : QWidget(parent) {}
void setSize(QObject *obj) {
if (obj->isWidgetType()) static_cast<QWidget*>(obj)->setGeometry(rect());
}
protected:
//! Resizes children to fill the extent of this widget
bool event(QEvent *ev) override {
if (ev->type() == QEvent::ChildAdded) {
setSize(static_cast<QChildEvent*>(ev)->child());
}
return QWidget::event(ev);
}
//! Keeps the children appropriately sized
void resizeEvent(QResizeEvent *) override {
for(auto obj : children()) setSize(obj);
}
};
class LoadingOverlay : public OverlayWidget
{
public:
LoadingOverlay(QWidget *parent = {}) : OverlayWidget{parent} {
setAttribute(Qt::WA_TranslucentBackground);
}
protected:
void paintEvent(QPaintEvent *) override {
QPainter p{this};
p.fillRect(rect(), {100, 100, 100, 128});
p.setPen({200, 200, 255});
p.setFont({"arial,helvetica", 48});
p.drawText(rect(), "Loading...", Qt::AlignHCenter | Qt::AlignTop);
}
};
namespace compat {
#if QT_VERSION >= QT_VERSION_CHECK(5,4,0)
using QT_PREPEND_NAMESPACE(QTimer);
#else
using Q_QTimer = QT_PREPEND_NAMESPACE(QTimer);
class QTimer : public Q_QTimer {
public:
QTimer(QTimer *parent = nullptr) : Q_QTimer(parent) {}
template <typename F> static void singleShot(int period, F &&fun) {
struct Helper : public QObject {
F fun;
QBasicTimer timer;
void timerEvent(QTimerEvent *event) override {
if (event->timerId() != timer.timerId()) return;
fun();
deleteLater();
}
Helper(int period, F &&fun) : fun(std::forward<F>(fun)) {
timer.start(period, this);
}
};
new Helper(period, std::forward<F>(fun));
}
};
#endif
}
int main(int argc, char *argv[])
{
QApplication a{argc, argv};
ContainerWidget base;
QLabel label("Dewey, Cheatem and Howe, LLC.", &base);
label.setFont({"times,times new roman", 32});
label.setAlignment(Qt::AlignCenter);
label.setGraphicsEffect(new QGraphicsBlurEffect);
LoadingOverlay overlay(&base);
base.show();
compat::QTimer::singleShot(2000, [&]{
overlay.hide();
label.setGraphicsEffect({});
});
return a.exec();
}