'Qt: Animating QPixmap

The answer code is located here (https://stackoverflow.com/a/50550471/4962676):
https://github.com/eyllanesc/stackoverflow/tree/master/50550089

The answer was more simple than the code below - the code above uses QPropertyAnimation rather than using for loops with QThread like below - which saves tons of space in the code and is more efficient.

Original question below:

I am writing an application in Qt and I am having an issue with closing the application and the threads.

Basically, the application window closes, however the process remains in the background and never closes.

my main header (just included the class since there are a lot of includes):

class ChatUI : public QWidget
{
    Q_OBJECT

    public:
        explicit ChatUI(QWidget *parent = 0);
        ~ChatUI();

    private:
        // The UI itself
        Ui::ChatUI * ui;

        // Current appliation startup directory
        QString applicationStartupDir = QDir::currentPath() + "/";

        // Typing indicator stuff
        QFrame * typingIndicator = nullptr;
        QImage circleImage;
        ThreadController * typingIndicatorThread = new ThreadController(false);
        bool currentlyFadingTypingIndicator = false;

        // The calm before the storm
        bool closing = false;

        void showTypingIndicator();
        void hideTypingIndicator();
    signals:
        void WidgetClosed();

    protected:
        void closeEvent(QCloseEvent*);
};

my controller (header):

#ifndef THREADCONTROLLER_H
#define THREADCONTROLLER_H

#include <QObject>
#include <QThread>
#include <QImage>
#include <QFrame>

#include "typingindicatorthread.h"

class ThreadController : public QObject
{
    Q_OBJECT
    QThread * workerThread = nullptr;
    TypingIndicatorThread * worker = nullptr;
signals:
    void startWork(QFrame*, QImage);
    //void killThread();
public slots:
    void killThread();
public:
    ThreadController(bool);
};

#endif // THREADCONTROLLER_H

my controller (source):

#include "threadcontroller.h"
#include <QDebug>

ThreadController::ThreadController(bool asd)
{
    if (asd == true){
        workerThread = new QThread();

        worker = new TypingIndicatorThread;
        worker->moveToThread(workerThread);
        workerThread->start();

        connect(this, &ThreadController::startWork, worker, &TypingIndicatorThread::startWorker);
    } else {
        workerThread = new QThread();
        workerThread->quit();
        workerThread->wait();

        delete workerThread;
    }
}

void ThreadController::killThread() {
    emit worker->killSignal();

    workerThread->quit();
    workerThread->wait();
}

My thread (header):

#ifndef TYPINGINDICATORTHREAD_H
#define TYPINGINDICATORTHREAD_H

#include <QObject>
#include <QLabel>
#include <QPixmap>
#include <QImage>
#include <QEventLoop>
#include <QTimer>
#include <QMetaObject>
#include <QPropertyAnimation>

class TypingIndicatorThread : public QObject
{
    Q_OBJECT
public:
    ~TypingIndicatorThread();
private:
    bool kill = false;
public slots:
    void startWorker(QFrame*, QImage);
    void killSignal();
};

#endif // TYPINGINDICATORTHREAD_H

my thread (source):

#include "typingindicatorthread.h"
#include <QDebug>

inline void delay(int millisecondsWait)
{
    QEventLoop loop;
    QTimer t;
    t.connect(&t, &QTimer::timeout, &loop, &QEventLoop::quit);
    t.start(millisecondsWait);
    loop.exec();
}

void TypingIndicatorThread::startWorker(QFrame * typingIndicator, QImage circleImage) {
    int max = 30;
    int min = 5;
    int waitTime = 5;

    QMetaObject::invokeMethod(typingIndicator, [=]() {
        QLabel * circle1 = new QLabel(typingIndicator);
        circle1->setGeometry(0,0, 50, 50);
        circle1->setAlignment(Qt::AlignCenter);
        circle1->show();

        QLabel * circle2 = new QLabel(typingIndicator);
        circle2->setGeometry(40,0, 50, 50);
        circle2->setAlignment(Qt::AlignCenter);
        circle2->show();

        QLabel * circle3 = new QLabel(typingIndicator);
        circle3->setGeometry(80,0, 50, 50);
        circle3->setAlignment(Qt::AlignCenter);
        circle3->show();

        forever {
            if (kill) {
                qDebug() << "Killing thread";

                return;
            }

            // Circle1
            for (int i=min; i < max; i++) {
                delay(waitTime);

                QPixmap circlePixmap = QPixmap::fromImage(circleImage).scaled(QSize(i, i), Qt::KeepAspectRatio, Qt::SmoothTransformation);

                circle1->setPixmap(circlePixmap);
            }

            for (int i=max; i > min; i--) {
                delay(waitTime);

                QPixmap circlePixmap = QPixmap::fromImage(circleImage).scaled(QSize(i, i), Qt::KeepAspectRatio, Qt::SmoothTransformation);

                circle1->setPixmap(circlePixmap);
            }

            // Circle2
            for (int i=min; i < max; i++) {
                delay(waitTime);

                QPixmap circlePixmap = QPixmap::fromImage(circleImage).scaled(QSize(i, i), Qt::KeepAspectRatio, Qt::SmoothTransformation);

                circle2->setPixmap(circlePixmap);
            }

            for (int i=max; i > min; i--) {
                delay(waitTime);

                QPixmap circlePixmap = QPixmap::fromImage(circleImage).scaled(QSize(i, i), Qt::KeepAspectRatio, Qt::SmoothTransformation);

                circle2->setPixmap(circlePixmap);
            }

            // Circle3
            for (int i=min; i < max; i++) {
                delay(waitTime);

                QPixmap circlePixmap = QPixmap::fromImage(circleImage).scaled(QSize(i, i), Qt::KeepAspectRatio, Qt::SmoothTransformation);

                circle3->setPixmap(circlePixmap);
            }

            for (int i=max; i > min; i--) {
                delay(waitTime);

                QPixmap circlePixmap = QPixmap::fromImage(circleImage).scaled(QSize(i, i), Qt::KeepAspectRatio, Qt::SmoothTransformation);

                circle3->setPixmap(circlePixmap);
            }
        }
    });
}

void TypingIndicatorThread::killSignal() {
    qDebug() << "oh no we are going to the shadow realm";

    kill = true;
}

TypingIndicatorThread::~TypingIndicatorThread() {
    emit killSignal();
}

The only reason why I am using messy for-loops for my animation is because I researched as much as possible into animating images, however there is nothing to make an animation outside of QML and the application is in c++.

If possible it would be great to use QPropertyAnimation as an alternative, but I can't seem to animate the size of the bubbles (circles) while at the same time updating the size it displays.

I apologize if this is a lot of code, I just wanted to give as much context as possible (and that is relevant) to help solve this issue.



Solution 1:[1]

There is no need to use QThread to make an animation, for this Qt offers the class QPropertyAnimation, and if you want the animation to be sequential you must use QSequentialAnimationGroup

In the case of QLabel you must set the scaledContents to true so that the QPixmap is the same size as the QLabel.

#include <QApplication>
#include <QFrame>
#include <QLabel>
#include <QSequentialAnimationGroup>
#include <QPropertyAnimation>

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    QFrame frame;
    frame.resize(320, 240);
    QSequentialAnimationGroup group;
    group.setLoopCount(-1);
    int minSize = 5;
    int maxSize = 30;
    int labelSize = 50;;
    for(const QPoint & pos: {QPoint(0, 0), QPoint(0, 40), QPoint(0, 80)}){
        QRect startVal = QRect(pos + (QPoint(labelSize, labelSize) + QPoint(-minSize, -minSize))/2 , QSize(minSize, minSize));
        QRect endVal = QRect(pos + (QPoint(labelSize, labelSize) + QPoint(-maxSize, -maxSize))/2 , QSize(maxSize, maxSize));
        QLabel *label = new QLabel(&frame);
        label->setGeometry(startVal);
        label->setPixmap(QPixmap(":/circle.png"));
        label->setScaledContents(true);
        label->setAlignment(Qt::AlignCenter);
        QPropertyAnimation *animation = new QPropertyAnimation(label, "geometry");
        animation->setStartValue(startVal);
        animation->setKeyValueAt(.5, endVal);
        animation->setEndValue(startVal);
        animation->setDuration(1000);
        group.addAnimation(animation);
    }
    group.start();
    frame.show();
    return a.exec();
}

You can find the complete code in the following link

enter image description here

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1