Мне нужно реализовать окно "Загрузка..." в моем приложении, но я предпочитаю покрывать весь QMainWindow темным прозрачным слоем с текстом выше. Кто-нибудь знает, как это сделать? Я не уверен, как перекрывать виджеты/макеты в Qt. Любая помощь будет оценена.
Темный прозрачный слой над QMainWindow в Qt
Ответ 1
Этот ответ находится в серии моих ответов, связанных с оверлеем: первый, второй, третий.
Самое тривиальное решение - просто добавить дочерний прозрачный виджет к QMainWindow
. Этот виджет должен просто отслеживать размер его родительского окна. Важно правильно обрабатывать изменения родительского виджета и z-порядок с братьями и сестрами. Ниже приведен правильный пример того, как это сделать.
Если вы хотите складывать наложения, последующие оверлеи должны быть дочерними элементами OverlayWidget
, в z-порядке. Если они должны быть братьями и сестрами OverlayWidget
, их порядок укладки undefined.
Это решение имеет преимущество обеспечения минимальной связи с другим кодом. Это не требует каких-либо знаний от виджета, к которому вы применяете наложение. Вы можете применить наложение к QMainWindow
или любому другому виджету, виджет также может быть в макете.
Повторное выполнение события QMainWindow
paint не будет считаться лучшим дизайном. Это делает его привязанным к определенному классу. Если вы действительно думаете, что экземпляр QWidget
слишком много накладных расходов, вам лучше иметь измерения, чтобы показать, что это так.
Можно, конечно, сделать наложение просто a QObject
и поместить код рисования в фильтр событий. Это было бы альтернативным решением. Это сложнее сделать, так как вам также необходимо правильно обработать атрибут родительского виджета Qt::WA_StaticContents
и с помощью виджета, потенциально вызывающего его метод scroll()
. Работа с отдельным виджетами является самой простой.
// 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();
}
Ответ 2
Я бы предложил выполнить модальный, бескаркасный диалог сверху и добавить графический эффект на фоновый виджет. Это IMHO очень гибкое и короткое решение, не касаясь непосредственно системы событий.
Производительность может быть плохой - можно улучшить это, вызвав drawSource(), но у меня пока нет надежного решения.
class DarkenEffect : public QGraphicsEffect
{
public:
void draw( QPainter* painter ) override
{
QPixmap pixmap;
QPoint offset;
if( sourceIsPixmap() ) // No point in drawing in device coordinates (pixmap will be scaled anyways)
pixmap = sourcePixmap( Qt::LogicalCoordinates, &offset );
else // Draw pixmap in device coordinates to avoid pixmap scaling;
{
pixmap = sourcePixmap( Qt::DeviceCoordinates, &offset );
painter->setWorldTransform( QTransform() );
}
painter->setBrush( QColor( 0, 0, 0, 255 ) ); // black bg
painter->drawRect( pixmap.rect() );
painter->setOpacity( 0.5 );
painter->drawPixmap( offset, pixmap );
}
};
// prepare overlay widget
overlayWidget->setWindowFlags( Qt::FramelessWindowHint | Qt::Dialog | Qt::WindowStaysOnTopHint );
// usage
parentWidget->setGraphicsEffect( new DarkenEffect );
overlayWidget->exec();
parentWidget->setGraphicsEffect( nullptr );
Ответ 3
Если вы говорите об использовании отдельного макета/виджета над своим "Главным окном", вы можете просто сделать окно "Загрузка..." модальным либо через редактор пользовательского интерфейса, либо в конструктор для вашего пользовательского интерфейса.