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? 
    

    }@


  • Moderators

    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);
            });
        });
    });
    

    }
    @


  • Moderators

    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


  • Moderators

    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.


  • Lifetime Qt Champion

    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)@



  • edited


  • Lifetime Qt Champion

    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();
    }
    };
    @



  • edited



  • edited



  • 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_OBJECT

    Q_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 also

    FaderLabel
    @#include <QLabel>
    #include <QTimer>
    #include <QPropertyAnimation>
    #include <QGraphicsOpacityEffect>

    class FaderLabel : public QLabel
    {
    Q_OBJECT

    public:
    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);
    

    }
    @



  • I ended up creating a "FaderWidget", "FaderFrame" and "FaderLabel"

    since QFrame and QLabel are childrens of QWidet, would it be possible to just code "FaderWidget" and make them inherit "FaderWidget"? Or do I have to edit Qt code?

    Thanks!


  • Moderators

    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);
    });
    });
    });
    }
    };
    @



  • Hey Chris thanks for your message!

    I know the oriented style design is not the best in this case, but I need to have control over the fade effect (stop effect, resume,.. ) because it is tied with user interactions. Also with my lack of C++ programming experience, I'm no good to adapt your solution and it''s more "crash-prone" in my case..

    Thanks for your help!
    Max



  • Lets discuss prof1ts! Are you afraid of big numbers? I protest, you don’t know what that is. You are no different than N4ZIs book burners if you DEF4CE this, you deserve to have it splattered around.

    annualreport2013.digia .com/ financial_statement/consolidated_income_statement

    @
    Digia
    Total assets in 2013 83,277,864.58

    Operating prof1t

    2006 2007 2008 2009 2010 2011 2012 2013 2014 Total
    8,3 11 13,4 -7,8 17,1 -22,1 6,9 -2,8 XXX 24,0
    @

    You might have a hard time to find info older then 2008, but don’t take my word on it, even if you discount years before 2008 you are still on 4,7 Million green mark TOTAL.
    Now listen, do you see that forbidden triple X? You are there now, that’s you critical branch-point. I could call you a bunch of cry babies, but I won’t. Here’s my message to you.

    MILLIONS IN PROF1T! Most people yearly “salaries” are not even close to “1%” of that smaller million of that smaller mostly unknown company. All while paying many millions on an undervalued Qt from a fading company, with salesman altering/faking peoples CVs just to sell/dump people to customers signing whatever prof1table contracts and ultimately firing LOTS of people with fake excuses.
    There’s a very pertinent question that demands an answer.
    Even though, many world economies, Nokia, Digia and all other companies are still under the wise guidance of the best supreme leaders available, why does at least the first three are still in crisis or under a steep decline in operating prof1ts, among other things.
    This question is not, I think, irreverent, although it may trouble whatever supreme leaders may be.

    “Those worlds in space are as countless as all the grains of sand on all the beaches of the Earth. Each of those worlds is as real as ours. In every one of them, there's a succession of incidents, events, occurrences which influence its future. Countless worlds, numberless moments, an immensity of space and time. And our small planet, at this moment, here we face a critical branch-point in the history. What we do with our world, right now, will propagate down through the centuries and powerfully affect the destiny of our descendants. It is well within our power to destroy our civilization, and perhaps our species as well. If we capitulate to superstition, or greed, or stupidity we can plunge our world into a darkness deeper than time between the collapse of classical civilization and the Italian Renaissance. But, we are also capable of using our compassion and our intelligence, our technology and our wealth, to make an abundant and meaningful life for every inhabitant of this planet. To enhance enormously our understanding of the Universe, and to carry us to the stars.”
    https://www.youtube dot com/watch?v=hLkC7ralR30

    The Earth is the only world known, so far, to harbor life. There is nowhere else, at least in the near future, to which our species could migrate. Visit, yes. Settle, not yet. Like it or not, for the moment, the Earth is where we make our stand. It has been said that astronomy is a humbling and character-building experience. There is perhaps no better demonstration of the folly of human conceits than this distant image of our tiny world. To me, it underscores our responsibility to deal more kindly with one another and to preserve and cherish the pale blue dot, the only home we've ever known.”””

    !https://upload.wikimedia.org/wikipedia/commons/7/73/Pale_Blue_Dot.png (https://upload.wikimedia.org/wikipedia/commons/7/73/Pale_Blue_Dot.png)!
    123213213


Log in to reply
 

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