Qt中QMainWindow上的暗透明层

16

我需要在我的应用程序中实现一个“加载中…”窗口,但我更喜欢使用一个黑色透明层覆盖整个QMainWindow,并在上面添加一些文本。有人知道该如何实现吗?我不确定如何在Qt中重叠小部件/布局。任何帮助都将不胜感激。


只是一个想法:你尝试重写QMainWindow的绘图事件了吗? - vahancho
嗯,有趣的解决方案!所以我可以像平常一样绘制QMainWindow,然后在其上应用一个透明层,但不知道是否会起作用,我会试试看。 - Didac Perez Parera
正确的做法是使用 QMainWindow::paintEvent(event),然后使用自定义绘图器在其上绘制一些东西(半透明矩形)。 - vahancho
为什么不使用QSplashScreen? - Md. Minhazul Haque
3个回答

26

这个回答是我与覆盖相关的系列回答之一:第一个第二个第三个

最简单的解决方法是简单地将一个透明小部件添加为 QMainWindow 的子级。该小部件仅需跟踪其父窗口的大小。重要的是要正确处理小部件父级变化和兄弟姐妹的 z-order。以下是如何正确实现的示例。

如果您想叠加覆盖层,则后续的覆盖层应该是 OverlayWidget 的子元素,在 z-order 中。如果它们是 OverlayWidget 的同级,则它们的堆叠顺序是未定义的。

此解决方案的好处是对其他代码提供了最小的耦合性。它不需要从您应用覆盖层的小部件中获取任何知识。您可以将覆盖层应用于 QMainWindow 或任何其他小部件,该小部件也可以位于布局中。

重新实现 QMainWindow 的绘图事件不会被认为是最佳设计。它使其与特定类绑定。如果您真的认为 QWidget 实例开销太大,那么最好有测量结果来证明这是事实。

当然,可能的解决方案是将叠加层仅作为一个 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{&central};
   QTimer::singleShot(5000, &overlay, SLOT(hide()));
   window.setCentralWidget(&central);
   window.show();
   return a.exec();
}

8
我建议在顶部执行一个模态、无框对话框,并在背景小部件上添加图形效果。 这是我个人认为非常灵活且简短的解决方案,而不必直接触及事件系统。
性能可能会很差 - 可以通过调用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 );

1
优秀的代码。在我的桌面和Android构建中工作得很好。当有用户输入等待时,您不必关心性能,优化是过度操作。 - Nulik

0

如果您想在“主窗口”上使用单独的布局/小部件,您可以通过UI编辑器或UI构造函数将“加载中...”窗口设置为模态窗口。


不,一个单独的小部件是简单的解决方案,但我想做我所描述的。 - Didac Perez Parera

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接