Dark Transparent Layer Over a Qmainwindow in Qt

Dark transparent layer over a QMainWindow in Qt

This answer is in a series of my overlay-related answers: first, second, third.

The most trivial solution is to simply add a child transparent widget to QMainWindow. That widget must merely track the size of its parent window. It is important to properly handle changes of widget parentage, and the z-order with siblings. Below is a correct example of how to do it.

If you want to stack overlays, subsequent overlays should be the children of OverlayWidget, in the z-order. If they were to be siblings of the OverlayWidget, their stacking order is undefined.

This solution has the benefit of providing minimal coupling to other code. It doesn't require any knowledge from the widget you apply the overlay to. You can apply the overlay to a QMainWindow or any other widget, the widget can also be in a layout.

Reimplementing QMainWindow's paint event would not be considered the best design. It makes it tied to a particular class. If you really think that a QWidget instance is too much overhead, you better had measurements to show that being the case.

It is possible, of course, to make the overlay merely a QObject and to put the painting code into an event filter. That'd be an alternative solution. It's harder to do since you have to also properly deal with the parent widget's Qt::WA_StaticContents attribute, and with the widget potentially calling its scroll() method. Dealing with a separate widget is the simplest.

screenshot of the example

// https://github.com/KubaO/stackoverflown/tree/master/questions/overlay-widget-19362455
#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 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::AlignVCenter);
}
};

int main(int argc, char * argv[])
{
QApplication a{argc, argv};
QMainWindow window;
QLabel central{"Hello"};
central.setAlignment(Qt::AlignHCenter | Qt::AlignTop);
central.setMinimumSize(400, 300);
LoadingOverlay overlay{¢ral};
QTimer::singleShot(5000, &overlay, SLOT(hide()));
window.setCentralWidget(¢ral);
window.show();
return a.exec();
}

How to change the opacity of Qt MainWindow?

The below works for me most of times (as long as we can run in stylesheet override problem with other ways). Consider change the last component of rgba to less than 255 for making it semi-transparent.

widget->setStyleSheet("background-color: rgba(255, 255, 255, 255);");

Mind that child widgets may inherit the transparent background of parent widget by default unless you specify "background-color: rgba(255, 255, 255, 0);" for them or limit the visibility of upper stylesheet somehow e.g. "QMainWindow(background-color: rgba(255, 255, 255, 255);}".

Draw transparent image over the screen with Qt

I have tried your code and it works fine, apparently it seems to be a problem of the image, I recommend you to use some kind of external application to erase the transparent parts like this.

Here is a test that works with your code:Sample Image

If you want to do this automatically I recommend you to use opencv.

Qt mainwindow transparency background

You can do something like this:

this->setWindowFlags(Qt::FramelessWindowHint);
this->setAttribute(Qt::WA_TranslucentBackground);

Then paint the background of your window in the paint event.

Note: As pointed out by Dmitry, in Qt4 there is a bug with minimizing and restoring the window.

Another option is to use QWidget::setMask(const QBitmap & bitmap), but this could be slow if the region is complex.

How to create a transparent QWidget on top of a sibling QVideoWidget?

Demo of transparent background overlay on the video:

#include <QApplication>
#include <QGraphicsView>
#include <QGraphicsScene>
#include <QGraphicsVideoItem>
#include <QMediaPlayer>
#include <QFileDialog>
#include <QPushButton>
#include <QLabel>
#include <QGraphicsProxyWidget>

const QSizeF VideoItemSize(500, 500);

int main(int argc, char *argv[])
{
QApplication a(argc, argv);

QMediaPlayer player;
QGraphicsView v;
QGraphicsScene scene;
QGraphicsVideoItem video;
v.setScene(&scene);
video.setSize(VideoItemSize);
scene.setSceneRect(QRectF(QPointF(0, 0), VideoItemSize)); // VideoItem full fill the scene
scene.addItem(&video);
player.setVideoOutput(&video);
player.setMedia(QMediaContent(QFileDialog::getOpenFileUrl()));

// Recommend using QGraphicsItems for overlay component
QGraphicsTextItem text("Loading...",&video);
text.setPos(100, 150);

// If you need a button...
QPushButton button("ButtonTest");
QGraphicsProxyWidget* proxyButton = scene.addWidget(&button);
proxyButton->setPos(100, 200);

// Instead of QGraphicsItems, if you really need a QWidget...
QLabel label("LabelTest");
label.setAttribute(Qt::WA_TranslucentBackground); // You can delete this line to see different
QGraphicsProxyWidget* proxyLabel = scene.addWidget(&label);
proxyLabel->setPos(100, 250);

v.show();
player.play();

return a.exec();
}


Related Topics



Leave a reply



Submit