QGraphicsOpacityEffect on a QLabel
-
Hi,
I use a lot of QGraphicsOpacityEffect to show and hide label (using opacity property)
There is a lot of code repeat used to do that, and i'm wondering if there isn't a better way to achieve the same result.
Basically what I need : show the QLabel gradually, and after some time, remove it from screen gradually.
I would also like to .setVisible(false) on my QLabel after the animation is done (fadout), is this possible?
I could use another QTimer, but then I would need 2 timer and 2 slots for a simple animation effect, seems overkill, any advice appreciated!
Thanks!Code :
@void WorkoutDialog::batteryStatusReceived(QString sensorType, int level) {
labelBatteryStatus->setText(tr("Battery warning! Sensor:") + sensorType + tr(" is ") + level); //show gradually QGraphicsOpacityEffect *effect = new QGraphicsOpacityEffect(labelBatteryStatus); labelBatteryStatus->setGraphicsEffect(effect); QPropertyAnimation *anim = new QPropertyAnimation(labelBatteryStatus); anim->setPropertyName("opacity"); anim->setTargetObject(effect); anim->setDuration(2000); anim->setStartValue(0.0); anim->setEndValue(1.0); anim->setEasingCurve(QEasingCurve::OutQuad); anim->start(QAbstractAnimation::DeleteWhenStopped); labelBatteryStatus->setVisible(true); if (timerRemoveBatteryStatus->isActive()) { timerRemoveBatteryStatus->stop(); timerRemoveBatteryStatus->start(10000); } else { timerRemoveBatteryStatus->start(10000); }
}
//-------------------------------------------------------
void WorkoutDialog::fadeOutBatteryStatus() {timerRemoveBatteryStatus->stop(); //remove gradually QGraphicsOpacityEffect *effect = new QGraphicsOpacityEffect(labelBatteryStatus); labelBatteryStatus->setGraphicsEffect(effect); QPropertyAnimation *anim = new QPropertyAnimation(labelBatteryStatus); anim->setPropertyName("opacity"); anim->setTargetObject(effect); anim->setDuration(2000); anim->setStartValue(1.0); anim->setEndValue(0.0); anim->setEasingCurve(QEasingCurve::OutQuad); anim->start(QAbstractAnimation::DeleteWhenStopped); // Would also like to .setVisible(false) on my QLabel after the animation is done, is this possible?
}@
-
What if you need to fade something else? You're gonna copy/paste all of that? You should check out the "DRY rule":http://en.wikipedia.org/wiki/Don't_repeat_yourself
Here's a potential implementation of this:
EDIT: formatted to fit better
@
class Delay: public QObject {
public:
Delay(int duration) {
QTimer::singleShot(duration, this, SLOT(deleteLater()));
}
};class Fader : public QObject {
public:
Fader(QWidget* target, double start, double end, int fade) {
auto effect = new QGraphicsOpacityEffect(this);
target->setGraphicsEffect(effect);auto anim = new QPropertyAnimation(effect, "opacity", this); connect(anim, &QPropertyAnimation::finished, this, &Fader::deleteLater); anim->setDuration(fade); anim->setStartValue(start); anim->setEndValue(end); anim->setEasingCurve(QEasingCurve::OutQuad); anim->start();
}
static void fadeInOut(QWidget* target, int fade, int pause) {
connect(new Fader(target, 0.0, 1.0, fade), &Fader::destroyed, ={
connect(new Delay(pause), &Delay::destroyed, ={
connect(new Fader(target, 1.0, 0.0, fade), &Fader::destroyed, ={
target->setVisible(false);
});
});
});
}
};
@
You can of course extend that to have fadeIn, fadeOut and whatever else animation you might need.Now whenever you need to fade something you just do this:
@
Fader::fadeInOut(someWidget, 2000, 1000);
@ -
Wow this is exactly what I needed! your C++ skills are way better than mine. Never used the "auto keyword":http://en.cppreference.com/w/cpp/language/auto, will have to learn how to use it, better than a raw pointer right? Really clever use of signal and slot for auto memory management, thanks so much!
@//----------------------------------------------------------------------------
Fader::Fader(QWidget* target, double start, double end, int fadeTime) {auto effect = new QGraphicsOpacityEffect(this); target->setGraphicsEffect(effect); auto anim = new QPropertyAnimation(effect, "opacity", this); connect(anim, &QPropertyAnimation::finished, this, &Fader::deleteLater); anim->setDuration(fadeTime); anim->setStartValue(start); anim->setEndValue(end); anim->setEasingCurve(QEasingCurve::OutQuad); anim->start();
}
//----------------------------------------------------------------------------
void Fader::fadeInOut(QWidget* target, int fadeInTime, int fadeOutTime, int pause) {connect(new Fader(target, 0.0, 1.0, fadeInTime), &Fader::destroyed, [=](){ target->setVisible(true); connect(new Delay(pause), &Delay::destroyed, [=](){ connect(new Fader(target, 1.0, 0.0, fadeOutTime), &Fader::destroyed, [=](){ target->setVisible(false); }); }); });
}
@ -
It's still a raw pointer. It just fits into a line on this forum :P
auto is not a type or a smart pointer or anything like that. It's just a way to tell the compiler to figure out the type of expression. It's very useful for things like this:
@std::vector<std::pair<std::vector<int>, std::vector<int>>>::const_iterator it1 = ...auto it2 = ...
@
it2 is still the same ugly long type. You just don't have to type it ;) -
Oh I get it,
I think there is a small problem with a part of the code, if the *target pointer is deleted (user closed QDialog before animation was over) I will get a crash, still trying to figure a good solution to check if the pointer is still good or not,
thanks@//----------------------------------------------------------------------------
void Fader::fadeInAndOut(QWidget* target, int fadeInTime, int fadeOutTime, int pause) {qDebug() << "fadeInAndOut"; target->setVisible(true); connect(new Fader(target, 0.0, 1.0, fadeInTime), &Fader::destroyed, [=](){ connect(new Delay(pause), &Delay::destroyed, [=](){ connect(new Fader(target, 1.0, 0.0, fadeOutTime), &Fader::destroyed, [=](){ qDebug() << "fadeInAndOut2"; if( target != NULL){ target->setVisible(false); } }); }); });
}@
Edit: Problem is with the Fader constructor, it doesnt' check if *target is still valid before creating.. still investigating
-
If you need to keep an eye on the lifetime of a QObject use "QPointer":http://qt-project.org/doc/qt-5/qpointer.html. It automatically becomes null when the object is destroyed and you can check with isNull() before using it.
-
Hi,
Are you thinking about something like QPointer ?
[edit: looks like I have some lag around hereā¦]
-
Thanks for the tip guys.
I will check to use QPointer, first time using it so hopefully I get it right.
For example, I could use a QPointer in the constructor instead of a QWidget*? Then check in the constructor if the QPointer is null before creating the animation.
Will try and post if it's working good!@Fader::Fader(QPointer ptrWidget, double start, double end, int fadeTime)@
-
Rather than calling setVisible, why not connect the animation e.g. finished signal to the show/hide slot of your widget ?
-
[quote author="SGaist" date="1416875137"]Rather than calling setVisible, why not connect the animation e.g. finished signal to the show/hide slot of your widget ?[/quote]
I could do that, but the same problem can happen--, the slot Show() or Hide() from the QWidget could crash the program if the QWidget is no longer existing.
I find it hard to do a elegant solution, my original solution was okay and not crash prone even though it had a lot of copy and pasted code...@class FadeObj: public QObject {
public:
FadeObj(QPointer<QWidget> target, double start, double end, int fadeTime) {qDebug() << "NEW FADER!"; if (!target.isNull()) { qDebug() << "widget still exist"; } else { qDebug() << "widget is now null! dont do it!"; } qDebug() << "BUG HERE"; auto effect = new QGraphicsOpacityEffect(target.data()); target.data()->setGraphicsEffect(effect); auto anim = new QPropertyAnimation(effect, "opacity", this); connect(anim, SIGNAL(finished()), target.data(), SLOT(show()));
/// Could crash the program..
connect(anim, &QPropertyAnimation::finished, this, &FadeObj::deleteLater);
anim->setDuration(fadeTime);
anim->setStartValue(start);
anim->setEndValue(end);
anim->setEasingCurve(QEasingCurve::OutQuad);
anim->start();
}
};
@ -
I think I forgot about QObject parent property, since Fader is a subclass of QObject, I will pass a reference to the parent and the pointer should be taken care when the parentWidget is destroyed? will try.
@Fader::Fader(QWidget* parentAndTarget) : QObject( parentAndTarget ) {
//give target and parent to have auto memory management this->widgetPtr = parentAndTarget;
}@
-
Okay Version 3 is better:
Fader.h
@class Fader : public QObject
{
Q_OBJECT
public:
Fader(QWidget* parentTarget);
~Fader();void fadeIn(int fadeInTime); void fadeOut(int fadeInTime); void fadeOutAfter(int fadeOutTime, int fadeAfter); void fadeInAndOut(int fadeInTime, int fadeOutTime, int pause);
private slots:
void animateFadeIn();
void animateFadeOut();private :
QWidget *widget;QTimer *timerSetVisible; QTimer *timerFadeOut; int fadeOutTime; int fadeInTime;
};@
Fader.cpp
@Fader::Fader(QWidget* parentAndTarget) : QObject( parentAndTarget ) {widget = parentAndTarget; widget->setVisible(true);
}
///////////////////////////////////////////////////////////////////////////////
void Fader::fadeIn(int fadeInTime) {
this->fadeInTime = fadeInTime;
animateFadeIn();
}
void Fader::fadeOut(int fadeOutTime) {
this->fadeOutTime = fadeOutTime;
animateFadeOut();
}//----------------------------------------------------------------------------
void Fader::fadeOutAfter(int fadeOutTime, int fadeAfter) {timerFadeOut = new QTimer(this); timerSetVisible = new QTimer(this); timerFadeOut->setSingleShot(true); timerSetVisible->setSingleShot(true); this->fadeOutTime = fadeOutTime; connect(timerFadeOut, SIGNAL(timeout()), this, SLOT(animateFadeOut()) ); connect(timerSetVisible, SIGNAL(timeout()), widget, SLOT(hide()) ); timerFadeOut->start(fadeAfter); timerSetVisible->start(fadeAfter + fadeOutTime);
}
//----------------------------------------------------------------------------
void Fader::fadeInAndOut(int fadeInTime, int fadeOutTime, int pause) {this->fadeInTime = fadeInTime; animateFadeIn(); timerFadeOut = new QTimer(this); timerSetVisible = new QTimer(this); timerFadeOut->setSingleShot(true); timerSetVisible->setSingleShot(true); this->fadeOutTime = fadeOutTime; connect(timerFadeOut, SIGNAL(timeout()), this, SLOT(animateFadeOut()) ); connect(timerSetVisible, SIGNAL(timeout()), widget, SLOT(hide()) ); timerFadeOut->start(fadeInTime + pause); timerSetVisible->start(fadeInTime + fadeOutTime + pause);
}
//---------------------------------------------------------------------------------
void Fader::animateFadeIn() {qDebug() << "animateFadeIn"; QGraphicsOpacityEffect *effect = new QGraphicsOpacityEffect(widget); QPropertyAnimation *anim = new QPropertyAnimation(widget); widget->setGraphicsEffect(effect); anim->setPropertyName("opacity"); anim->setTargetObject(effect); anim->setDuration(fadeInTime); anim->setStartValue(0.0); anim->setEndValue(1.0); anim->setEasingCurve(QEasingCurve::OutQuad); anim->start(QAbstractAnimation::DeleteWhenStopped);
}
//---------------------------------------------------------------------------------
void Fader::animateFadeOut() {qDebug() << "animateFadeOut"; QGraphicsOpacityEffect *effect = new QGraphicsOpacityEffect(widget); QPropertyAnimation *anim = new QPropertyAnimation(widget); widget->setGraphicsEffect(effect); anim->setPropertyName("opacity"); anim->setTargetObject(effect); anim->setDuration(fadeOutTime); anim->setStartValue(1.0); anim->setEndValue(0.0); anim->setEasingCurve(QEasingCurve::OutQuad); anim->start(QAbstractAnimation::DeleteWhenStopped);
}@
-
Just want to close the thread with what I chose.
After a lot of testing using the Fader class, it was working but not 100% like I wanted. I will go back to the old approach with many slots and QTimer inside the QDialog
Pro to old method:
- I can see if the widget is already being animated before setting a new animation on it (The Fader just blindly set a new animation, if there is still an active animation on the Widget it can cause weird animations and bad timing)
- Easier memory management
Cons:
- Lot of copied code, will try to put the common animation in a function to reuse code.
Thanks for your help
-
Solution found inside FancyTab! (credit to QtCreator source code)
Keep a reference to QPropertyAnimation, stop the current animation every time a new one is received before activating new one, brillant!
@#ifndef FANCYTAB_H
#define FANCYTAB_H#include <QIcon>
#include <QWidget>#include <QPropertyAnimation>
class FancyTab : public QObject
{
Q_OBJECTQ_PROPERTY(float fader READ fader WRITE setFader)
public:
FancyTab(QWidget *tabbar) : enabled(false), mTabBar(tabbar), mFader(0)
{
mAnimator.setPropertyName("fader");
mAnimator.setTargetObject(this);
}float fader() { return mFader; } void setFader(float value); void fadeIn(); void fadeOut(); QIcon icon; QString text; QString toolTip; bool enabled;
private:
QPropertyAnimation mAnimator;
QWidget *mTabBar;
float mFader;
};#endif // FANCYTAB_H@
-
Okay here is a complete working solution (add it to Qt? :P)
Only wish I subclassed QWidget instead of QLabel, as I need this for other QWidget alsoFaderLabel
@#include <QLabel>
#include <QTimer>
#include <QPropertyAnimation>
#include <QGraphicsOpacityEffect>class FaderLabel : public QLabel
{
Q_OBJECTpublic:
explicit FaderLabel(QWidget *parent = 0);void fadeIn(int durationMs); void fadeOut(int durationMs); void fadeOutAfterPause(int fadeDuration, int pause); void fadeInAndFadeOutAfterPause(int fadeInDuration, int fadeOutDuration, int pause);
private slots: //Used to connect with timer
void fadeInAfterTimer(); void fadeOutAfterTimer();
private :
QTimer *timerAnimatorFadeOut;
QTimer *timerAnimatorFadeIn;QGraphicsOpacityEffect *effect; QPropertyAnimation *anim; int tmpDurationFadeOut; int tmpDurationFadeIn;
};
#endif // FADERLABEL_H@
FaderLabel.cpp
@#include "faderlabel.h"#include <QDebug>
FaderLabel::FaderLabel(QWidget *parent) :
QLabel(parent)
{timerAnimatorFadeOut = new QTimer(this); timerAnimatorFadeOut->setSingleShot(true); timerAnimatorFadeIn = new QTimer(this); timerAnimatorFadeIn->setSingleShot(true); connect(timerAnimatorFadeOut, SIGNAL(timeout()), this, SLOT(fadeOutAfterTimer()) ); connect(timerAnimatorFadeIn, SIGNAL(timeout()), this, SLOT(fadeInAfterTimer()) ); effect = new QGraphicsOpacityEffect(this); this->setGraphicsEffect(effect); anim = new QPropertyAnimation(effect, "opacity", this); tmpDurationFadeOut = 0; tmpDurationFadeIn = 0;
}
//---------------------------------------------------
void FaderLabel::fadeInAfterTimer() {fadeIn(this->tmpDurationFadeIn);
}
void FaderLabel::fadeOutAfterTimer() {fadeOut(this->tmpDurationFadeOut);
}
//---------------------------------------------------------------------------------
void FaderLabel::fadeIn(int durationMs) {qDebug() << "animateFadeIn FaderLabel" << durationMs; anim->stop(); anim->setDuration(durationMs); anim->setStartValue(0); anim->setEndValue(1); anim->setEasingCurve(QEasingCurve::OutQuad); anim->start();
}
//---------------------------------------------------------------------------------
void FaderLabel::fadeOut(int durationMs) {qDebug() << "animateFadeout FaderLabel" << durationMs; anim->stop(); anim->setDuration(durationMs); anim->setStartValue(1); anim->setEndValue(0); anim->setEasingCurve(QEasingCurve::OutQuad); anim->start();
}
//--------------------------------------------------------------------------------
void FaderLabel::fadeOutAfterPause(int fadeDuration, int pause) {this->tmpDurationFadeOut = fadeDuration; timerAnimatorFadeOut->start(pause);
}
//-------------------------------------------------------------------------------------------------
void FaderLabel::fadeInAndFadeOutAfterPause(int fadeInDuration, int fadeOutDuration, int pause) {fadeIn(fadeInDuration); this->tmpDurationFadeOut = fadeOutDuration; timerAnimatorFadeOut->start(pause);
}
@ -
For whatever reason i stopped receiving notifications for this thread... :/
I think creating this sort of class for each possible widget is cumbersome and it's back to copy/pasting which is no good. You want something that just takes (any) widget and animates it without intruding into that class code. And you want to write it only once.
A little tweak to my original proposal using QPointer to take care of that releasing problem:
@
class Delay: public QObject {
public:
Delay(int duration) {
QTimer::singleShot(duration, this, SLOT(deleteLater()));
}
};class Fader : public QObject {
public:
Fader(QWidget* target, double start, double end, int fade) {
auto effect = new QGraphicsOpacityEffect(this);
target->setGraphicsEffect(effect);auto anim = new QPropertyAnimation(effect, "opacity", this); connect(anim, &QPropertyAnimation::finished, this, &Fader::deleteLater); anim->setDuration(fade); anim->setStartValue(start); anim->setEndValue(end); anim->setEasingCurve(QEasingCurve::OutQuad); anim->start();
}
static void fadeInOut(QWidget* target, int fade, int pause) {
QPointer<QWidget> p(target);
if(p)connect(new Fader(target, 0.0, 1.0, fade),&Fader::destroyed,={
if(p)connect(new Delay(pause), &Delay::destroyed, ={
if(p)connect(new Fader(target,1.0,0.0,fade),&Fader::destroyed,={
if(p)target->setVisible(false);
});
});
});
}
};
@