1

I have a problem that on Linux with Xorg (Ubuntu 14.04) and Qt 5.5.1 QSplashScreen isn't painted until I get to the event loop. Even if I call QApplication::processEvents() multiple times, it still isn't painted, even after 1000 calls, although the window is already on the screen, retaining the original pixels which were there before the app launched, thus being effectively invisible*. From this answer I got an idea of using a timed loop of calling QApplication::processEvents(), like here:

#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();
}

The above code actively sleeps for 30 ms in an attempt to make QSplashScreen repaint. This works for me, but I'm not sure that it'll always work e.g. on a busy/slow CPU or in whatever other conditions (the magic value of 30 iterations was found empirically).

Another, quite intrusive in general, way would be to do all the necessary loading in another thread, only to make sure that QSplashScreen in the main thread does have an active message queue. Due to the need to considerably redo the main program, this looks not too good of a solution.

So, is there any way to make sure that QSplashScreen has been repainted, so that its window doesn't contain garbage, and only then to proceed with long blocking loading process?


* I discovered this when I moved a window behind the splash screen

Ruslan
  • 18,162
  • 8
  • 67
  • 136
  • 2
    aside: "long blocking loading process?" that shouldn't be done on the UI thread. – UKMonkey Apr 27 '18 at 09:40
  • Not a solution to the actual problem, but couldn't you just use QTimer::singleshot to call an initialization function, then execute the event loop and make the main window visible at the end of the initialization? That way everything is executed in the main thread and inside the event loop. – SteakOverflow Apr 27 '18 at 10:06
  • @SteakOverflow that would also potentially require considerable redesign of the main program. E.g. what if the loading process creates some automatic variables intended to live on? In a single-shot timer handler they'd be destroyed before returning. If they are instead made static, they'll be destroyed after `QApplication`, which may also be no good. – Ruslan Apr 27 '18 at 10:15
  • Btw, `processEvents` is a static method. – Dmitry Sazonov Apr 27 '18 at 10:56

2 Answers2

0

One way to avoid the unknowable a priori magic timeout is to wait for an exact event: the paint event. On X11 it appears to come with a delay. To do this waiting we'll have to subclass QSplashScreen and override QSplashScreen::paintEvent(), like here:

#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();
}
Ruslan
  • 18,162
  • 8
  • 67
  • 136
0

The solution is rather simple: keep the event loop running until the window is repainted. This should be done without any spinning, i.e. you shouldn't be using any explicit timeouts.

#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"
Kuba hasn't forgotten Monica
  • 95,931
  • 16
  • 151
  • 313