Beachwalker
Beachwalker

Reputation: 7915

Showing a QMovie in QSplashScreen

I have a simple splash screen extension which should show a little animation (animated gif) while the user waits for the main window. The problem is, that only the first frame is shown instead of all:

class SplashScreen : public QSplashScreen
{
        Q_OBJECT

    public:
        explicit SplashScreen(const QPixmap& pixmap, const QString& animation, Qt::WindowFlags flags = 0);

    protected:
        void paintEvent(QPaintEvent* event);

    signals:
        void frameChanged();

    private slots:
        void convertFrameChanged(int)
        {
            repaint();
        }

    private:
        QMovie movie;

};


SplashScreen::SplashScreen(const QPixmap& pixmap, const QString& animation, Qt::WindowFlags flags)
    : QSplashScreen(pixmap, flags),
      movie(animation)
{
    movie.start();
    connect(&(movie), SIGNAL(frameChanged(int)), this, SLOT(convertFrameChanged(int)));
}

void SplashScreen::paintEvent(QPaintEvent* event)
{
    QSplashScreen::paintEvent(event);

    QPixmap frame = movie.currentPixmap();
    QRect rect = frame.rect();
    rect.moveCenter(this->rect().center());
    if (rect.intersects(event->rect()))
    {
        QPainter painter(this);
        painter.drawPixmap(rect.left(), rect.top(), frame);
    }
}

EDIT:

Tried to invoke repaint with QTimer in SplashScreen constructor:

QTimer *timer = new QTimer(this);
connect(timer, SIGNAL(timeout()), this, SLOT(doRefresh()));
timer->start(1000);

Added slot to the Splashscreen:

    void doRefresh()
    {
        repaint();
    }

But this didn't work, too. doRefresh isn't invoked. It seems the QTimer also requires an alread existing event loop.

Upvotes: 1

Views: 4325

Answers (3)

RJR Software
RJR Software

Reputation: 1

QTimer::singleShot(1500, &splash, [&splash] () {
QPixmap pxm("img1.png");
if (pxm.isNull()==false) {
    splash.setPixmap(pxm);
}});
QTimer::singleShot(3000, &splash, [&splash] () {
QPixmap pxm("img2.png");
if (pxm.isNull()==false) {
    splash.setPixmap(pxm);
}});

Upvotes: 0

Marek R
Marek R

Reputation: 37697

Problem is quite profound. To have animation working you have to allow process events in main thread. On other hand initialization process is also performed on main thread.
First approach is add those app.processEvents() as often as possible but this will limit frame rate so badly that it is useless.

To fix it properly you have answer two important question:

  • what are you doing during initialization?
  • and can this initialization be moved to separate thread?

Once I've had application where during initialization large index was build. This took about 40 seconds so quite a lot. I've moved building index to separate thread using QtConcurrent::run(this, &MyClass::MyMethodToBuildIndex) and showing some progress by emitting signals periodically from MyClass::MyMethodToBuildIndex (default automatic connection did the whole trick).
If you have similar log time initialization this solution will enable animation of your QMovie out of the box, problem will be just smart connection of signal and slots so windows will be shown and hidden with proper timings.


It is quite simple to do. Details depends how did you designed your application. I usually create separate class with business logic. If this class contains some heavy calculation done during initialization then it can be done something like:

int main(int argc, char *argv[])
{
    QApplication app(argc, argv);
    SplashScreen splash();
    splash.show();
    app.processEvents(); // need to process events manually

    QMainWindow window;
    SomeLogicClass data;
    window.setData(&data);

    connect(&data, SIGNAL(initializationProgressChanged(int)),
            &spash, SLOT(setProgress(int)));

    // show main window when initialization is finished
    connect(&data, SIGNAL(initializationFinished()),
            &window, SLOT(show())); 

    // close splash when initialization is finished
    connect(&data, SIGNAL(initializationFinished()),
            &spash, SLOT(close()));

    // this line I usually hide behind some method like: startBackgroundInitialization
    QtCuncurent::run(&data, &SomeLogicClass::heavyInitialization);

    return app.exec();
}

If you need to use QSplashScreen::finish instead close then QSignalMapper can help.
Note that last argument of connect is default value Qt::AutoConnection which will enforce slots to be run in main thread.

Upvotes: 1

TheDarkKnight
TheDarkKnight

Reputation: 27611

Assuming that the splash screen is displayed before the QApplication::exec() function is called, then it's likely that events aren't being processed, so you need to call processEvents on the QApplication object.

Note the help example: -

int main(int argc, char *argv[])
{
    QApplication app(argc, argv);
    QPixmap pixmap(":/splash.png");
    QSplashScreen splash(pixmap);
    splash.show();
    app.processEvents(); // need to process events manually
    ...
    QMainWindow window;
    window.show();
    splash.finish(&window);
    return app.exec();

}

In this situation it discusses calling raise() on the splash screen, with a QTimer to ensure it stays on top, but needs the processEvents call for the QTimer to function.

As it states in Qt help for dismissing the splash screen when the mouse button is clicked: -

Since the splash screen is typically displayed before the event loop has started running, it is necessary to periodically call QApplication::processEvents() to receive the mouse clicks.

The same will likely apply to the animated gif.

Upvotes: 1

Related Questions