'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
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 |