28

I am trying to fade in and fade out a QLabel or for that matter any QWidget subclass. I have tried with QGraphicsEffect, but unfortunately it works well only on Windows and not on Mac.

The only other solution which can work on both Mac & Windows seems to be having my own custom paintEvent where I set the opacity of QPainter and also define a Q_PROPERTY for "opacity" in my derived QLabel and change the opacity through QPropertyAnimation.

I am pasting below the relevant code snippet for your reference. I still see an issue here - reusing the QLabel::paintEvent doesn't seem to be working, it works only if I do a complete custom painting using the QPainter, but that doesn't seem to be an easy way and if I need to do that for every QWidget subclass I want to fade out, that's a nightmare. Please clarify if I am doing any obvious mistakes here.

Q_PROPERTY(qreal opacity READ opacity WRITE setOpacity)

void MyLabel::setOpacity(qreal value) {
    m_Opacity = value;
    repaint();
}

void MyLabel::paintEvent((QPaintEvent *pe) {
    QPainter p;
    p.begin(this);
    p.setOpacity();
    QLabel::paintEvent(pe);
    p.end();
}

void MyLabel::startFadeOutAnimation() {
    QPropertyAnimation *anim = new QPropertyAnimation(this, "opacity");
    anim->setDuration(800);
    anim->setStartValue(1.0);
    anim->setEndValue(0.0);
    anim->setEasingCurve(QEasingCurve::OutQuad);
    anim->start(QAbstractAnimation::DeleteWhenStopped);
}
Angie Quijano
  • 4,167
  • 3
  • 25
  • 30
Soumya Das
  • 1,635
  • 2
  • 19
  • 28
  • The Qt API is telling you you're using it in a way it was not meant to be used. Widgets are not really great performers when you want to animate their contents like that. It may work for a proof-of-concept, but in the long run you should be using Qt Quick. Whether you use Qt Quick 1 or Qt Quick 2 is up to you, even Qt Quick 1 (the declarative module) would be an improvement over what you're doing. – Kuba hasn't forgotten Monica Sep 30 '13 at 13:50

4 Answers4

48

There's actually a super easy way to do this without messy QPaintEvent intercepts and without the tough requirements of QGraphicsProxyWidget, which doesn't work on promoted widget children. The technique below will work even with promoted widgets and their children widgets.

Fade In Your Widget

// w is your widget
QGraphicsOpacityEffect *eff = new QGraphicsOpacityEffect(this);
w->setGraphicsEffect(eff);
QPropertyAnimation *a = new QPropertyAnimation(eff,"opacity");
a->setDuration(350);
a->setStartValue(0);
a->setEndValue(1);
a->setEasingCurve(QEasingCurve::InBack);
a->start(QPropertyAnimation::DeleteWhenStopped);

Fade Out Your Widget

// w is your widget
QGraphicsOpacityEffect *eff = new QGraphicsOpacityEffect(this);
w->setGraphicsEffect(eff);
QPropertyAnimation *a = new QPropertyAnimation(eff,"opacity");
a->setDuration(350);
a->setStartValue(1);
a->setEndValue(0);
a->setEasingCurve(QEasingCurve::OutBack);
a->start(QPropertyAnimation::DeleteWhenStopped);
connect(a,SIGNAL(finished()),this,SLOT(hideThisWidget()));
// now implement a slot called hideThisWidget() to do
// things like hide any background dimmer, etc.
Volomike
  • 23,743
  • 21
  • 113
  • 209
  • I really like this answer! Although, is there a simple method in fading multiple widgets in/out? – philm May 21 '18 at 18:17
  • 5
    **important** If running this code in python, you need to save the `a` and `eff` variables (`self.a=`) so they don't get garbage collected – Jonathan Aug 19 '18 at 06:33
2

Try expose some part of palette as property of label then animate it:

Q_PROPERTY(QColor color READ color WRITE setColor)

void MyLabel::setColor(const QColor &value) {
    QPalette palette;
    palette.setBrush(QPalette::WindowText, value);
    setPalette(palette);
}

QColor MyLabel::color() {
    return palette(QPalette::Normal, QPalette::Window).
}

void MyLabel::startFadeOutAnimation() {
    QPropertyAnimation *animation = new QPropertyAnimation(label, "color", this);
    QColor c = label->color();
    animation->setKeyValueAt(0, c);
    c.setAlpha(0);
    animation->setKeyValueAt(1, c);
    animation->setEasingCurve(QEasingCurve::OutQuad);
    animation->setDuration(1000);
    animation->start(QAbstractAnimation::DeleteWhenStopped);
}

You can try avoid subclassing by defining and registering new interpolator which will handle QPalette qRegisterAnimationInterpolator, but this is a bit complicated.

Marek R
  • 32,568
  • 6
  • 55
  • 140
1

You can put your widgets into a QGraphicsScene. It supports opacity changing and animation.

See QGraphicsProxyWidget documentation for an example.

Pavel Strakhov
  • 39,123
  • 5
  • 88
  • 127
1

I'm sorry i can't write it in C++, but the implementation in python3 and in C++ is the same:

You can create a QVariantAnimation animating a float value representing the opacity, connecting the valueChanged signal to a custom function:

anim = QVariantAnimation()
anim.setStartValue(.3)
anim.setEndValue(.6)
anim.setDuration(900)
anim.setEasingCurve(QEasingCurve.InOutQuad) # Not strictly required, just for the aesthetics
anim.valueChanged.connect(lambda opacity: mySpecialFun(opacity))
anim.start()

Then, define the custom function which changes the opacity (or the property you want to animate):

def mySpecialFun(opacity: float) -> None:
    mywidget.setStyleSheet(f"background-color: rgba(0, 0, 0, {color});")

The reason for this to work is that we are not animating a property. Instead, we are animating a float value, but the key is that every time the float value changes we get its value and we pass it to a custom function that does our custom job (in our case setting the opacity though the stylesheet)

Martí Climent
  • 407
  • 4
  • 9