QT 中使用定时器,有两种方式:

  • 定时器类:QTimer
  • 定时器事件:QEvent::Timer,对应的子类是QTimerEvent

本节通过一个案例,同时讲解这两种方式

  • 当点击 “开始” 按钮,两个标签同时向右移动,下边标签的定时间隔是上边标签的2倍,因此移动慢。
  • 当两个标签移动到最右侧时,回到最右端循环移动

整体效果,如下:
122.gif

1. 定时器事件 QTimerEvent

1.1 界面布局

把两个标签以及 “启动”、“停止”、“复位” 三个按钮布局在界面上。

首先,来到timer_widget.h,声明相关的变量和函数,如下:

#include <QLabel>

class TimerWidget : public QWidget {

private slots:
    void onBtnStartClicked();
    void onBtnStopClicked();
    void onBtnResetClicked();

private:
    QLabel* lbl1;
    QLabel* lbl2;

    // 定时器id
    int id1;
    int id2;
};

然后,来到timer_widget.cpp中完成布局,如下:

#include <QLabel>
#include <QPushButton>
#include <QVBoxLayout>

TimerWidget::TimerWidget(QWidget* parent) : QWidget{parent}
{
    QVBoxLayout* verticalLayout = new QVBoxLayout(this);
    verticalLayout->setSpacing(0);
    verticalLayout->setContentsMargins(0, 0, 0, 0);

    // 1. 添加第一个QLabel
    lbl1 = new QLabel();
    lbl1->setFrameShape(QFrame::Box);
    lbl1->setFixedSize(60, 60);
    lbl1->setStyleSheet("background-color: red;");
    verticalLayout->addWidget(lbl1);

    // 2. 添加第二个QLabel
    lbl2 = new QLabel();
    lbl2->setFrameShape(QFrame::Box);
    lbl2->setFixedSize(60, 60);
    lbl2->setStyleSheet("background-color: blue;");
    verticalLayout->addWidget(lbl2);

    // 3. 添加按钮的水平布局
    QHBoxLayout* horizontalLayout = new QHBoxLayout();
    horizontalLayout->setSpacing(0);
    horizontalLayout->setContentsMargins(0, 0, 0, 0);

    QPushButton* btnStart = new QPushButton(this);
    btnStart->setText("开始");
    horizontalLayout->addWidget(btnStart);

    QPushButton* btnStop = new QPushButton(this);
    btnStop->setText("停止");
    horizontalLayout->addWidget(btnStop);

    QPushButton* btnReset = new QPushButton(this);
    btnReset->setText("复位");
    horizontalLayout->addWidget(btnReset);
    verticalLayout->addLayout(horizontalLayout);

    this->setStyleSheet("QPushButton { font-size: 20px; }");
    
    // 关联信号槽
    connect(btnStart, &QPushButton::clicked, this, &TimerWidget::onBtnStartClicked);
    connect(btnStop, &QPushButton::clicked, this, &TimerWidget::onBtnStopClicked);
    connect(btnReset, &QPushButton::clicked, this, &TimerWidget::onBtnResetClicked);
}

此时,运行程序:
image-20260820103852401

1.2 启动定时器

首先,来到timer_widget.cpp中,实现3个按钮的槽函数,如下:

void TimerWidget::onBtnStartClicked()
{
    // 返回一个非零的正整数作为定时器 ID
    id1 = startTimer(10);
    id2 = startTimer(20);
}

void TimerWidget::onBtnStopClicked()
{
    if (id1 != 0) {
        killTimer(id1);
        id1 = 0;
    }
    if (id2 != 0) {
        killTimer(id2);
        id2 = 0;
    }
}

void TimerWidget::onBtnResetClicked()
{
    lbl1->move(0, lbl1->y());
    lbl2->move(0, lbl2->y());
}

然后,声明并实现timerEvent()函数,当定时时间到会自动调用该函数,如下:

// timer_widget.h
class TimerWidget : public QWidget {

protected:
    void timerEvent(QTimerEvent* event);
};

// timer_widget.cpp
// 向右移动标签。当标签超出当前窗口,重新回到最左侧
// 启动的多个定时器,使用 QTimerEvent 类的 timerId() 函数可以获取哪个定时器定时时间到
void TimerWidget::timerEvent(QTimerEvent* event)
{
    // qDebug() << "timerEvent: " << event->timerId();
    if (event->timerId() == id1) {
        lbl1->move(lbl1->x() + 5, lbl1->y());
        if (lbl1->x() >= this->width()) {
            lbl1->move(0, lbl1->y());
        }
    } else if (event->timerId() == id2) {
        lbl2->move(lbl2->x() + 5, lbl2->y());
        if (lbl2->x() >= this->width()) {
            lbl2->move(0, lbl2->y());
        }
    }
}

2. 定时器类 QTimer

除了使用定时器事件QTimerEvent,还可以使用定时器类QTimer来实现以上同样的效果

首先,在timer_widget.h声明变量和函数,如下:

#include <QTimer>

class TimerWidget : public QWidget
{
private slots:
    void onTimeout1();
    void onTimeout2();

private:
    QTimer* timer1;
    QTimer* timer2;
};

然后,在timer_widget.cpp的构造中创建定时器,并关联槽函数:

TimerWidget::TimerWidget(QWidget* parent) : QWidget{parent}
{
    // ...
    
    timer1 = new QTimer(this);
    connect(timer1, &QTimer::timeout, this, &TimerWidget::onTimeout1);

    timer2 = new QTimer(this);
    connect(timer2, &QTimer::timeout, this, &TimerWidget::onTimeout2);
}

然后,在timer_widget.cpp中实现两个槽函数

这里移动标签,并在标签超出当前窗口边界时,复位到最左侧

void TimerWidget::onTimeout1()
{
    lbl1->move(lbl1->x() + 5, lbl1->y());
    if (lbl1->x() >= this->width()) {
        lbl1->move(0, lbl1->y());
    }
}

void TimerWidget::onTimeout2()
{
    lbl2->move(lbl2->x() + 5, lbl2->y());
    if (lbl2->x() >= this->width()) {
        lbl2->move(0, lbl2->y());
    }
}

最后,修改 “启动”、”停止” 按钮的槽函数

为便于切换定时器类和定时器事件这两种方式,定义了一个宏

#define USE_TIMER_EVENT

void TimerWidget::onBtnStartClicked()
{
#ifdef USE_TIMER_EVENT
    // 返回一个非零的正整数作为定时器ID
    id1 = startTimer(10);
    id2 = startTimer(20);
#else
    timer1->start(20);
    timer2->start(10);
#endif
}

void TimerWidget::onBtnStopClicked()
{
#ifdef USE_TIMER_EVENT
    if (id1 != 0) {
        killTimer(id1);
        id1 = 0;
    }
    if (id2 != 0) {
        killTimer(id2);
        id2 = 0;
    }
#else
    if (timer1->isActive()) {
        timer1->stop();
    }
    if (timer2->isActive()) {
        timer2->stop();
    }
#endif
}