Qt QPropertyAnimation+QTimer to realize self-made suspension window

Suspended window under Qt

The recent project needs a suspension window similar to 360 suspension ball. When the mouse is placed and stayed for a period of time, the suspension window will be expanded and the removed area will be automatically retracted. I looked for it on the Internet, but I didn't find it. Thinking about getting familiar with Qt and improving my programming technology, I made a wheel myself. If there is a problem, I hope you can correct it.

QPropertyAnimation

I use the animation class provided by Qt. The official document explains:

The red box above means that you can specify the start and end values of the attribute.

The method of use is as follows:

// Set the property to geometry, representing the location size
m_Animation = new QPropertyAnimation(this, "geometry");
// Set animation duration in MS
m_Animation->setDuration(600);

// Sets the end value of the animation
m_Animation->setEndValue(QRect(m_posX,m_posY,
                               this->width(), this->height()));
// Start of animation
m_Animation->setStartValue(QRect(m_posX - ui->m_menu->width(), m_posY,
                                 this->width(), this->height()));
// Animated motion tracks
m_Animation->setEasingCurve(QEasingCurve::InQuad);
m_Animation->start();

For setEasingCurve() function, which is the action track for setting animation, please refer to Qt official document:

Many animation curves are introduced here.

QTimer

Another core is the timer. The simple logic is:

  1. When the mouse moves into the title bar, the pop-up timer will be started, and the pop-up function will be executed when the time is up. When the time is not up, but the title is removed, the pop-up timer will be closed;

  2. When the mouse moves into the menu bar, it will close the stow timer. When it moves out of the menu bar, it will start the stow timer, and the stow function will be executed at the time. When it moves in before the time, it will close the stow timer;

The initialization code of the timer is as follows:

m_expandTimer = new QTimer();
m_flodTimer = new QTimer();
// Set timer time (unit: ms)
m_expandTimer->setInterval(700);
m_flodTimer->setInterval(700);

// Connecting signal and slot
connect(m_expandTimer, &QTimer::timeout, this, &Floating::expandMenu); 
connect(m_flodTimer, &QTimer::timeout, this, &Floating::flodMenu);     

When the timer expires, the processing function is as follows:

void Floating::expandMenu()
{
    if (m_Animation->state() == QPropertyAnimation::Running) {
        return;
    }

    m_isExpand = true;

    setTitleIcon();

    m_Animation->setStartValue(QRect(m_posX, m_posY,
                                     this->width(), this->height()));
    m_Animation->setEndValue(QRect(m_posX - ui->m_menu->width(), m_posY,
                                  this->width(), this->height()));
    m_Animation->start();

    m_expandTimer->stop();
}

void Floating::flodMenu()
{
    if (m_Animation->state() == QPropertyAnimation::Running) {
        return;
    }

    m_isExpand = false;

    setTitleIcon();

    m_Animation->setEndValue(QRect(m_posX,m_posY,
                                   this->width(), this->height()));
    m_Animation->setStartValue(QRect(m_posX - ui->m_menu->width(), m_posY,
                                     this->width(), this->height()));
    m_Animation->start();

    m_flodTimer->stop();
}

Event filtering

You need to design the mouse in and out event of the child control of the floating window. The code is as follows:

bool Floating::eventFilter(QObject *target, QEvent *event)
{

    //drag 
    // TODO

    if (target == ui->m_title) {
        if (event->type() == QEvent::Enter) {
            if (!m_bDragFlag && !m_isExpand) {
                m_expandTimer->start();
                return QWidget::eventFilter(target, event);
            }
        }

        if (event->type() == QEvent::Leave) {
            m_expandTimer->stop();
        }
    }

    if (target == ui->m_menu || target == this) {
        if (event->type() == QEvent::Enter) {
            m_flodTimer->stop();
            return QWidget::eventFilter(target, event);
        }

        if (event->type() == QEvent::Leave) {
            if (!m_bDragFlag && m_isExpand) {
                m_flodTimer->start();
            }
        }
    }

    return QWidget::eventFilter(target, event);
}

Icon transformation

void Floating::setTitleIcon()
{
    m_isExpand ? ui->m_title->setProperty("status", "show")
               : ui->m_title->setProperty("status", "hide");

    // After setting, be sure to polish, otherwise the style may not be displayed
    ui->m_title->style()->polish(ui->m_title);
}

Here, the corresponding styles are set according to the set dynamic attributes. For details, see This blog

Adaptive window size

Sometimes, the position of the suspended window may change again after the window size is adjusted, or after the resolution is changed. Therefore, you need to reload the resizeEvent of the window to be placed to dynamically set the size of the suspended window.

The code is as follows:

// mainwindow.h
class MainWindow
{
 // ...
protected:
    virtual void resizeEvent(QResizeEvent* event) override;
}

// mainwindow.cpp

void resizeEvent(QResizeEvent* event)
{
    m_floating->adjustParent(this.width());
}

// floating.cpp
void Floating::adjustParent(int parentWidth)
{
    // Here my posY is set to a fixed 50,
    // m_posX = width of main window - (width of title bar + gap of layout)
    int horSpacing = static_cast<QGridLayout*>(this->layout())->horizontalSpacing();
    m_posX = parentWidth - ui->m_title->width() - horSpacing;
    m_posY = 50;

    // move windows
    this->move(m_posX, m_posY);
}

usage method

  1. Copy the file to the project folder

  2. Introducing pri file into pro file

    include(floating/floating.pri)

  3. Add header file

    include "floating/floating"

  4. Get singleton object:

// .h 
Floating* m_floating;

// And you need to add this sentence to the destructor of the class you use
// Otherwise, there will be a problem of multiple releases because of this m_floating is released before the destructor
// However, the relationship tree of the child window created before is still there, and the child object will be destructed when the main window is destructed
// It will go wrong. Therefore, you need to delete this relationship in the object tree.
m_floating->setParent(nullptr);

// .cpp
m_floating = &Floating::getInstance(this);
  1. Set style sheet
/* Set the style of the stow */
Floating QWidget#m_title[status=hide]
{
/*    border-image:url(:/img/images/ZT.png);*/
background-color: rgba(12,55,214,1);
}

/* Set the style of the expansion */
Floating QWidget#m_title[status=show]
{
/*    border-image:url(:/img/images/YT.png);*/
background-color: rgba(12,55,214,1);
}

/* Style the menu bar */
Floating QWidget#m_menu
{
    border: 2px solid #969696;
    background-color: rgba(0, 25, 67, 0.9);;
    border-top-left-radius: 10px;       /* Angle radian: lower left corner*/
    border-bottom-left-radius: 10px;      /* Angle radian: lower right corner*/

}
  1. If you need to change the position of the floating window after the main window changes, please overload the resize event of the main window to manually change the size of the floating window
// mainwindow.h
class MainWindow
{
 // ...
protected:
    virtual void resizeEvent(QResizeEvent* event) override;
}

// mainwindow.cpp

void resizeEvent(QResizeEvent* event)
{
    m_floating->adjustParent(this.width());
}

Sure enough, only by writing or teaching others can I really test whether my knowledge is in place. My colleagues who are blogging have a deeper understanding of these codes and QPropertyAnimation.

Code resources, please go here download

Keywords: Qt

Added by phpcharan on Thu, 06 Jan 2022 02:34:00 +0200