QSplashScreen animation



  • Hello everyone, a simple question, I believe.

    I have Q QSplashScreen for my application, to bridge loadup and show some credits, firm logo etc.

    To give it a bit of an extra flair, I derived QSplashScreen to add an animation.

    The problem, while the app is setting itself up, the SplashScreen, visible to the animation, freezes.

    Even placed in a seperate Thread I have to call QApplication::processEvents() during the Setup to see anything of my animation.
    I was surpised it compiled at all, as there is the only one MainThread my handle the UI thing.

    The problem is that ui->setupUi(this); in my classes takes a significant amount of time that results in an obvious freeze of the QSplashScreen.

    So my question is it possible to have an animated QSplashscreen without regulary calling QApplication::processEvents(); Or do I have to make it a seperated program, start is started before my main programm to have that effect?

    Code example:

    //Main
    QElapsedTimer time; time.start();
    
        QApplication::setAttribute(Qt::AA_Use96Dpi);
        QApplication a(argc, argv);
    
        QImage pMap (":/images/Logo.png");
        QPainter painter(&pMap);
        QString credits("Credits");
        QFont f = painter.font(); f.setPixelSize(16); painter.setFont(f);
        QFontMetrics fMetric (painter.font());
        QRect rec = fMetric.boundingRect(credits);
        painter.drawText((pMap.width()-rec.width())/2,(pMap.height()-rec.height()*1),credits);
    
        jhSplashScreen *splash = new jhSplashScreen();
        splash->setImage(pMap);
    
        splash->setWindowFlags( Qt::WindowStaysOnTopHint | Qt::SplashScreen | Qt::FramelessWindowHint );
    
        splash->show();
    
        QThread *t = new QThread();
        splash->moveToThread(t);
        QObject::connect(t, &QThread::started, splash, &jhSplashScreen::init);
        QObject::connect(t, &QThread::finished, splash, &jhSplashScreen::deleteLater);
        QObject::connect(t, &QThread::finished, t,      &QThread::deleteLater);
        t->start();
    
        myApp w;
    
        if(!w.lock())
            return -42;
    
        while(time.elapsed() < 5450){
            QApplication::processEvents();
        }
    
        w.show();
        t->quit();
    
        return a.exec();
    


  • this

    while(time.elapsed() < 5450){
            QApplication::processEvents();
        }
    

    is useless.

    GUI operations are only allowed in the same thread the QApplication lives so the multi-thread is not feasible.

    I'd say the fastest way here is to move the splash to a separate binary altogether and use QProcess to control it



  • @VRonin said in QSplashScreen animation:

    this

    while(time.elapsed() < 5450){
            QApplication::processEvents();
        }
    

    is useless.

    Actually, not entirely useless, this garanties at least 5 seconds of splashscreen. And processEvents was so only way I managed to update the animation.

    GUI operations are only allowed in the same thread the QApplication lives so the multi-thread is not feasible.

    I'd say the fastest way here is to move the splash to a separate binary altogether and use QProcess to control it

    That was what I suspected. The question I have to that, is there a way to also close the seperated Process if the user terminated the app in an unusual way, e.g. forces a close via taskmanager?



  • So, I think I found a solution that works.
    I'm not sure however, if it is the most elegant solution.

    I turned the SplshScreen animation into its own program, compiled that as an executable:

    The Splash.exe and my main program have each a QSharedMemory object.
    The QSharedMemory is aparently also freed, when I kill my main program via TaskManager.

    After 5 seconds, I check from my splash app if the Object was created/is maintained from my main program. If it isn't -> cancel program else wait for cancle from main program.

    jhSplash::jhSplash(QWidget *parent) : QWidget(parent)
    {
        _MainApp = new QSharedMemory("myAppSingleInstance",this);
        this->setWindowFlags( Qt::WindowStaysOnTopHint | Qt::FramelessWindowHint | Qt::WindowTransparentForInput);
        this->setAttribute(Qt::WA_TranslucentBackground,true);
        
        m_Img = QImage(":/Logo.png");
        
        this->resize(m_Img.size());
        
        m_updateTimer = new QTimer(this);
        connect(m_updateTimer, SIGNAL(timeout()), this, SLOT(repaint()));
        m_updateTimer->start(6);
    
        this->show();
        QTimer::singleShot(5000,this,[=]{m_CheckMemory = true;});
    }
    
    jhSplash::~jhSplash()
    {
    
    }
    
    void jhSplash::paintEvent(QPaintEvent *event)
    {
        int penWidth = 6;
        QRectF oRect (0,0,this->width(), this->height());
        QPainter painter(this);
        painter.setBackgroundMode(Qt::TransparentMode);
        painter.drawImage(0,0,m_Img);
        QString credits("Credits");
        QFont f = painter.font(); f.setPixelSize(16); painter.setFont(f);
        QFontMetrics fMetric (painter.font());
        QRect rec = fMetric.boundingRect(credits);
        painter.drawText((oRect.width()-rec.width())/2,(oRect.height()-rec.height()*1),credits);
    
        QPen pen;
        pen.setWidth(penWidth);
        pen.setColor(Qt::black);
        painter.setPen(pen);
    
        QRect arcRect(penWidth/2,penWidth/2,this->width()-penWidth,this->height()-penWidth);
    
        m_drawAngle = m_drawAngle +16;
        int startAngle = 0+m_drawAngle;
        int spanAngle = (5760 -960)*(-1);
        painter.drawArc(arcRect,startAngle,spanAngle);
    
        pen.setColor(Qt::red);
        painter.setPen(pen);
        startAngle = 0 + m_drawAngle;
        spanAngle  = 960;
        painter.drawArc(arcRect,startAngle,spanAngle);
    
        if(m_CheckMemory){
            if(!_MainApp->attach(QSharedMemory::ReadOnly)){
                this->close();
            }
            _MainApp->detach();
        }
    
        QWidget::paintEvent(event);
    }
    
    //Main Application main.cpp
    
    QElapsedTimer time; time.start();
    
        QApplication::setAttribute(Qt::AA_Use96Dpi);
        QApplication a(argc, argv);
    
        QProcess splash;
        splash.start("path/to/Splash/Splash.exe");
    
        uConfig w;
    
        if(!w.lock())
            return -42;
    
        while(time.elapsed() < 5450){
        }
    
        w.show();
        splash.close();
    
        QObject::connect(&a, &QApplication::aboutToQuit, &splash, &QProcess::close);
    
        return a.exec();
    


  • While I like your solution best, below an alternative based on a heartbeat socket:

    Splash App

    splashwidget.h

    #ifndef SPLASHWIDGET_H
    #define SPLASHWIDGET_H
    
    #include <QWidget>
    #include <QTimer>
    #include <QLocalSocket>
    #include <QApplication>
    #include <QDataStream>
    class SplashWidget : public QWidget
    {
        Q_OBJECT
        Q_DISABLE_COPY(SplashWidget)
        enum {ContinueChar = 88};
    public:
        SplashWidget(const QString& serverName, QWidget* parent= Q_NULLPTR)
            :QWidget(parent)
            ,m_serverName(serverName)
        {
            init();
        }
        virtual ~SplashWidget() {}
    protected:
        void init(){
            setWindowFlags( Qt::WindowStaysOnTopHint | Qt::FramelessWindowHint | Qt::WindowTransparentForInput);
            setAttribute(Qt::WA_TranslucentBackground,true);
            m_hbTimer = new QTimer(this);
            connect(m_hbTimer,&QTimer::timeout,this,&SplashWidget::resetTimer);
            m_hbTimer->setInterval(1000); // 1 sec
            m_hbReceiver = new QLocalSocket(this);
            connect(m_hbReceiver, static_cast<void(QLocalSocket::*)(QLocalSocket::LocalSocketError)>(&QLocalSocket::error),qApp,&QApplication::quit);
            connect(m_hbReceiver, &QLocalSocket::disconnected,qApp,&QApplication::quit);
            connect(m_hbReceiver, &QLocalSocket::readyRead, this, &SplashWidget::resetTimer);
            connect(m_hbReceiver, &QLocalSocket::connected, m_hbTimer, static_cast<void(QTimer::*)()>(&QTimer::start));
            m_hbReceiver->connectToServer(m_serverName,QIODevice::ReadOnly);
        }
        Q_SLOT void resetTimer(){
            if(m_hbReceiver->bytesAvailable()>=sizeof(qint8)){
                QDataStream hbin(m_hbReceiver);
                hbin.setVersion(QDataStream::Qt_5_0);
                qint8 readHb=0;
                hbin >> readHb;
                if(readHb == static_cast<qint8>(ContinueChar))
                    return m_hbTimer->start();
                qApp->quit();
            }
        }
    private:
        const QString m_serverName;
        QTimer* m_hbTimer;
        QLocalSocket* m_hbReceiver;
    
    };
    #endif
    

    main.cpp

    #include "splashwidget.h"
    #include <QApplication>
    
    int main(int argc, char *argv[])
    {
        QApplication a(argc, argv);
        const QStringList argList = a.arguments();
        if(argList.size()<2)
            return -1;
        if(argList.at(1).isEmpty())
            return -1;
        SplashWidget w(argList.at(1));
        w.show();
    
        return a.exec();
    }
    

    Main Application

    splashcontroller.h

    #ifndef SPLASHCONTROLLER_H
    #define SPLASHCONTROLLER_H
    #include <QObject>
    #include <QUuid>
    #include <QLocalServer>
    #include <QLocalSocket>
    #include <QTimer>
    #include <QProcess>
    #include <QDataStream>
    class SplashController : public QObject
    {
        Q_OBJECT
            Q_DISABLE_COPY(SplashController)
    public:
        SplashController(QObject* parent = Q_NULLPTR)
            :QObject(parent)
            , m_localSocket(Q_NULLPTR)
            , m_localServer(Q_NULLPTR)
            , m_splashProcess(Q_NULLPTR)
            , m_splashProcessPath("SplashScreenApp")
        {
            m_hbTimer = new QTimer(this);
            m_hbTimer->setInterval(500);
        }
        QString splashProcessPath() const
        {
            return m_splashProcessPath;
        }
    
        void setSplashProcessPath(const QString &splashProcessPath)
        {
            m_splashProcessPath = splashProcessPath;
        }
        Q_SLOT void start()
        {
            if (m_localServer)
                return;
            const QString serverName = QUuid::createUuid().toString();
            m_localSocket = Q_NULLPTR;
            m_localServer = new QLocalServer(this);
            if (!m_localServer->listen(serverName)) {
                m_localServer->deleteLater();
                m_localServer = Q_NULLPTR;
                error();
            }
            QObject::connect(m_localServer, &QLocalServer::newConnection, this, &SplashController::splashConnected);
            m_splashProcess = new QProcess(this);
            QObject::connect(m_splashProcess, &QProcess::errorOccurred, this, &SplashController::stopComponents);
            QObject::connect(m_splashProcess, &QProcess::errorOccurred, this, &SplashController::error);
            QObject::connect(m_splashProcess, static_cast<void(QProcess::*)(int, QProcess::ExitStatus)>(&QProcess::finished), this, &SplashController::stopComponents);
            QObject::connect(m_splashProcess, static_cast<void(QProcess::*)(int, QProcess::ExitStatus)>(&QProcess::finished), this, &SplashController::error);
            m_splashProcess->start(m_splashProcessPath, QStringList(serverName));
    
        }
        Q_SLOT void stop()
        {
            stopComponents();
            finished();
        }
        Q_SIGNAL void error();
        Q_SIGNAL void finished();
    protected:
        Q_SLOT void stopComponents()
        {
            if (!m_localServer)
                return;
            m_hbTimer->stop();
            disconnect(m_splashProcess);
            disconnect(m_localServer);
            if (m_localSocket)
                disconnect(m_localSocket);
            m_splashProcess->deleteLater();
            m_localServer->deleteLater();
            m_splashProcess = Q_NULLPTR;
            m_localServer = Q_NULLPTR;
            m_localSocket = Q_NULLPTR;
        }
    
        Q_SLOT void splashConnected()
        {
            if (m_localSocket)
                return;
            m_localSocket = m_localServer->nextPendingConnection();
            QObject::connect(m_localSocket, &QLocalSocket::disconnected, this, &SplashController::stopComponents);
            QObject::connect(m_localSocket, &QLocalSocket::disconnected, this, &SplashController::error);
            QObject::connect(m_localSocket, static_cast<void(QLocalSocket::*)(QLocalSocket::LocalSocketError)>(&QLocalSocket::error), this, &SplashController::stopComponents);
            QObject::connect(m_localSocket, static_cast<void(QLocalSocket::*)(QLocalSocket::LocalSocketError)>(&QLocalSocket::error), this, &SplashController::error);
            QObject::connect(m_hbTimer, &QTimer::timeout, this, &SplashController::spalshHB);
            m_hbTimer->start();
        }
        Q_SLOT void spalshHB()
        {
            if (!m_localSocket)
                return;
            QDataStream hbin(m_localSocket);
            hbin.setVersion(QDataStream::Qt_5_0);
            hbin << static_cast<qint8>(88);
        }
    
    private:
        QTimer* m_hbTimer;
        QLocalServer* m_localServer;
        QLocalSocket* m_localSocket;
        QProcess* m_splashProcess;
        QString m_splashProcessPath;
    };
    
    #endif // SPLASHCONTROLLER_H
    

    an example of main.cpp

    #include <QApplication>
    #include <QThread>
    #include "splashcontroller.h"
    
    int main(int argc, char *argv[])
    {
    
        QApplication app(argc, argv);
        QThread splashThread;
        SplashController* splashScreen = new SplashController;
        splashScreen->setSplashProcessPath("C:/temp/SplashScreenApp");
        splashScreen->moveToThread(&splashThread);
        QObject::connect(splashScreen, &SplashController::finished, &splashThread, &QThread::quit);
        QObject::connect(splashScreen, &SplashController::error, &splashThread, &QThread::quit);
        QObject::connect(splashScreen, &SplashController::finished, splashScreen, &SplashController::deleteLater);
        QObject::connect(splashScreen, &SplashController::error, splashScreen, &SplashController::deleteLater);
        QObject::connect(splashScreen, &SplashController::finished, [&splashScreen]()->void {splashScreen = Q_NULLPTR; });
        QObject::connect(splashScreen, &SplashController::error, [&splashScreen]()->void {splashScreen = Q_NULLPTR; });
        QObject::connect(&splashThread, &QThread::started, splashScreen, &SplashController::start);
        splashThread.start();
        QTimer::singleShot(10000, [&splashScreen]()->void {
            QMetaObject::invokeMethod(splashScreen, "stop", Qt::QueuedConnection);
        });
        QTimer::singleShot(10100, &app, &QApplication::quit);
        return app.exec();
    }
    

    Of course you'll have to subclass SplashWidget and implement your own paint



  • @VRonin Thanks for the detailed and complex answer! I'll defenitly look into your heartbeat approach a bit more!

    I wanted to add a few lines, of code that I forgot in my post above.

    The QShared Memory part of the main-app main.cpp:

     QApplication::setAttribute(Qt::AA_Use96Dpi);
        QApplication a(argc, argv);
    //!New Part start
    QSharedMemory _singular("myAppSingleInstance");
        if(_singular.attach(QSharedMemory::ReadOnly)){
            _singular.detach();
            return -42;
        }else{
            _singular.create(1);
        }
    //!New Part End
        QProcess 
    

    And also a small addition, so that noone accidentally starts Splash.exe without a main App:

    //Main Application, main.cpp
    QProcess splash;
    QStringList s{"openByApp"};
    splash.start("path/to/Splash/Splash.exe",s);
    
    //Splash App, main.cpp
    QApplication a(argc, argv);
        if(a.arguments().last() != "openByApp"){
            return -42;
        }
    

    Also on a second thought, this would also be the perfect situtation to test Qt 5.9's brand new Technology Preview Module : Qt Remote Objects ?

    It seems to fit, according to the quick description I read.



  • @J.Hilk said in QSplashScreen animation:

    Also on a second thought, this would also be the perfect situtation to test Qt 5.9's brand new Technology Preview Module : Qt Remote Objects ?

    My solution uses the principles behind this. It uses the "local" connection and, given the simplicity of the inter-process communication in this case, You probably do not need the overhead of that module. QSharedMemory is perfect in this case even though your current code probably doesn't handle opening the main app twice well

    A side note for curious readers:
    You might ask why we are using QSharedMemory or QLocalSocket instead of using stdin/stdout to communicate between processes and the answer is that on Windows stdin is not a socket so you can't use QSocketNotifier to get a sort of readyRead() in the child process. In this specific case you probably can have a thread in the child just stuck on a std::cin call until the server responds, but I'm not going to try it



  • Not sure this will help. But I only wished to have messages animate for my splash. And this is what I do:

    In my main I define a QApplication derived class:

    MyAppObject app(argc, argv);
    

    I construct the object, I have an init method that emits progress signal messages such as:

    message="Reading configurations: " % settings.fileName();
    emit initMessage(message);
    

    Back in main, after app is declared but before app.init():

        const QImage image(":/Images/Splash.png");
        QPixmap pixmap = QPixmap::fromImage(image).scaled(QSize(300,300)
                           ,Qt::AspectRatioMode::KeepAspectRatio
                           ,Qt::SmoothTransformation);
        QSplashScreen *splash = new QSplashScreen(pixmap);
        splash->show();
    // bind app init messsage signals/updates to splash message
    QObject::connect(&app, &ClassName::initMessage,
        [=]( const QString message){
            int alignment = Qt::AlignCenter;
            const QColor &color = Qt::darkYellow;
            splash->showMessage(message, alignment, color);
            } 
    // start the fun
    app.Init();
    // everything is ready, kill off the splash
    splash->close();
    // start the QApp
    return app.exec();
    

Log in to reply
 

Looks like your connection to Qt Forum was lost, please wait while we try to reconnect.