有什么方法可以真正确保 QSplashScreen 已在屏幕上重新绘制?

Is there any way to make really sure QSplashScreen has been repainted on the screen?

我有一个问题,在 Linux 上使用 Xorg (Ubuntu 14.04) 和 Qt 5.5.1 QSplashScreen 在我进入事件循环之前不会绘制。即使我多次调用 QApplication::processEvents(),它仍然没有被绘制,即使在 1000 次调用之后,尽管 window 已经在屏幕上,保留了应用程序启动之前的原始像素,因此有效隐形*。从 this answer 我想到了使用 timed 循环调用 QApplication::processEvents(),就像这里:

#include <QThread>
#include <QApplication>
#include <QSplashScreen>

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

    QSplashScreen splash;
    splash.show();
    splash.showMessage("Loading...");
    // The hack to try to ensure that splash screen is repainted
    for(int i=0;i<30;++i)
    {
        QThread::usleep(1e3);
        a.processEvents();
    }

    QThread::usleep(5e6); // simulate slow loading process
    splash.showMessage("Finished");

    return a.exec();
}

以上代码主动休眠 30 毫秒以尝试 QSplashScreen 重绘。这对我有用,但我不确定它是否总是有效,例如在 busy/slow CPU 或任何其他条件下(根据经验发现 30 次迭代的神奇值)。

另一种通常非常具有侵入性的方法是在另一个线程中执行所有必要的加载,只是为了确保主线程中的 QSplashScreen 确实有一个活动的消息队列。由于需要大量重做主程序,这看起来不是一个很好的解决方案。

那么,有什么方法可以确保QSplashScreen已经重绘,使其window不包含垃圾,然后才继续进行长时间的阻塞加载过程?


* 当我把一个 window 移到闪屏后面时我发现了这个

避免不可知的先验魔术超时的一种方法是等待确切的事件:绘画事件。在 X11 上它似乎有延迟。要进行此等待,我们必须继承 QSplashScreen 并覆盖 QSplashScreen::paintEvent(),如下所示:

#include <QThread>
#include <QApplication>
#include <QSplashScreen>

class MySplashScreen : public QSplashScreen
{
    bool painted=false;

    void paintEvent(QPaintEvent* e) override
    {
        QSplashScreen::paintEvent(e);
        painted=true;
    }
public:
    void ensureFirstPaint() const
    {
        while(!painted)
        {
            QThread::usleep(1e3);
            qApp->processEvents();
        }
    }
};

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

    MySplashScreen splash;
    splash.show();
    splash.showMessage("Loading...");
    splash.ensureFirstPaint();

    QThread::usleep(5e6); // simulate slow loading process
    splash.showMessage("Finished");

    return a.exec();
}

解决方案相当简单:保持事件循环 运行 直到 window 被重新绘制。这应该在没有任何旋转的情况下完成,即你不应该使用任何明确的超时。

#include <QtWidgets>

class EventSignaler : public QObject {
  Q_OBJECT
  QEvent::Type m_type;
protected:
  bool eventFilter(QObject *src, QEvent *ev) override {
    if (ev->type() == m_type)
      emit hasEvent(src);
    return false;
  }
public:
  EventSignaler(QEvent::Type type, QObject *object) :
    QObject(object), m_type(type) {
    object->installEventFilter(this);
  }
  Q_SIGNAL void hasEvent(QObject *);
};

int execUntilPainted(QWidget *widget) {
  EventSignaler painted{QEvent::paint, widget};
  QObject::connect(&painted, &EventSignaler::hasEvent, qApp, &QCoreApplication::quit);
  return qApp->exec();
}

int main(int argc, char **argv) {
  QApplication app{argc, argv};

  MySplashScreen splash;
  EventSignaler painted{QEvent::Paint, &splash};
  splash.show();
  splash.showMessage("Loading...");
  execUntilPainted(&splash);

  QThread::sleep(5); // simulate slow loading process
  splash.showMessage("Finished");

  return app.exec();
}

#include "main.moc"