Mike Shaw
Mike Shaw

Reputation: 393

Qt QWidget hide animation

I have a sub-class of QWidget that is a popup widget. I would like to add some animation when it shows and disappears. So I re-implemented showEvent(QShowEvent * event) and hideEvent and added some QPropertyAnimation in the functions. The showEvent works just fine for me but the hideEvent doesn't. Because

Hide events are sent to widgets immediately after they have been hidden.

Any idea about how to do it?

Update:

I don't think it's the right reason. When I use Nejat's solution. The show part works. But when I click outside the widget. It disappears immediately.

Upvotes: 3

Views: 3019

Answers (2)

IMAN4K
IMAN4K

Reputation: 1345

You should override QWidget::closeEvent() so when trying to close immediatly it will be ignored AND we start our animation and after finishing (QPropertyAnimation::finished()) we close the widget as normal.

Here is a demo to demonstrate:

class AnimatedWidget : public QWidget {
    Q_OBJECT
        Q_PROPERTY(qreal alpha READ alpha WRITE setAlpha)

public:
    AnimatedWidget(QWidget* parent = nullptr) :QWidget{ parent }, opacityAnimation{ new QPropertyAnimation{this, "alpha",this} } {
        setWindowFlags(windowFlags() | Qt::WindowFlags(Qt::FramelessWindowHint) | Qt::Tool);
        auto pal = palette();
        pal.setColor(QPalette::Background, Qt::cyan);
        setAutoFillBackground(true);
        setPalette(pal);
        setFixedSize(200, 200);
    }

    qreal alpha() const {
        return windowOpacity();
    }

    void setAlpha(qreal level) {
        setWindowOpacity(level);
        update();
    }

protected:
    void closeEvent(QCloseEvent* e) override {
        if (opacityAnimation->currentValue().toReal() == 1.0) { // Ignore event + start animation
            e->ignore();
            startHide();
            QObject::connect(opacityAnimation, SIGNAL(finished()), this, SLOT(onAnimationCallBack()), Qt::UniqueConnection);
        } else {
            e->accept();
            if (!isHidden())
                hide();
            QWidget::close(); // necessary actions
        }
    }

    public Q_SLOTS:
    void show() {
        startShow();
        QWidget::show(); // necessary actions
    }

    private Q_SLOTS:
    void onAnimationCallBack() {
        if (opacityAnimation->currentValue().toReal() == 0.0) { // we're finished so let's really close the widget
            QCloseEvent ev;
            QApplication::sendEvent(this, &ev);
            qApp->sendEvent(this, &ev);
        }
    }

    void startHide() {
        opacityAnimation->setStartValue(1.0);
        opacityAnimation->setEndValue(0.0);
        opacityAnimation->setDuration(1500);
        opacityAnimation->start();
    }

    void startShow() {
        opacityAnimation->setStartValue(0.0);
        opacityAnimation->setEndValue(1.0);
        opacityAnimation->setDuration(1500);
        opacityAnimation->start();
    }

private:
    QPropertyAnimation* opacityAnimation = nullptr;
};

class Base : public QWidget {
public:
    Base(QWidget* parent = nullptr) :QWidget{ parent }, widget{ new AnimatedWidget{} } {

    }

private:
    AnimatedWidget* widget;

protected:
    void mouseReleaseEvent(QMouseEvent* e) override {
        if (widget->isHidden())
            widget->show();
        else
            widget->close();
        QWidget::mouseReleaseEvent(e);
    }
};

int main(int argc, char *argv[]) {
    QApplication app(argc, argv);
    Base base;
    base.show();
    return app.exec();
}

enter image description here

Upvotes: 2

Nejat
Nejat

Reputation: 32685

You can override the eventFilter in your widget and check for QEvent::Show and QEvent::Close events.

bool MyWidget::eventFilter(QObject * obj, QEvent * event)
{
    if(obj == this && event->type() == QEvent::Show)
    {
        //about to show
    }
    else if(obj == this && event->type() == QEvent::Close)
    {
        //about to close
    }


    return false;
}

You should also install the event filter in the constructor by:

this->installEventFilter(this);

Upvotes: 1

Related Questions