【QT学习】定时器(timerEvent与QTimer)

一、timerEvent

timerEvent 处理多个定时任务的核心逻辑是:通过「定时器 ID」区分不同任务,每个定时器通过 startTimer(interval) 启动后,会返回一个唯一的「定时器 ID」;timerEvent 触发时,通过 QTimerEvent::timerId() 获取当前触发的 ID,分支判断后执行对应任务。

#ifndef WIDGET_H
#define WIDGET_H

#include <QWidget>

QT_BEGIN_NAMESPACE
namespace Ui {
class Widget;
}
QT_END_NAMESPACE

class Widget : public QWidget
{
    Q_OBJECT

public:
    Widget(QWidget *parent = nullptr);
    ~Widget();
    void timerEvent(QTimerEvent *e);//重写定时器事件
private:
    Ui::Widget *ui;
};
#endif // WIDGET_H

#include "widget.h"
#include "ui_widget.h"
#include <QDebug>
#include <QTimer>

Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);
    /*定时器方式一*/
    //启动定时器
     startTimer(1000);
    //定时器的返回值timerId是int类型,可以根据timerId来区分timerEvent汇总执行哪些代码
	//killTimer(timerId);//传入定时器id,清除对应定时器 

}

/*定时器方式一*/
void Widget::timerEvent(QTimerEvent *e)
{
	//int timerId = e.timerId();
	//if(timerId == ){}; //根据timerId判断是哪个定时器,进行对应操作
	static int num  = 1;
	ui->label->setText(QString::number(num++));
}
	
Widget::~Widget()
{
    delete ui;
}


动态管理定时器

class DynamicTimerWidget : public QWidget {
    Q_OBJECT
public:
    DynamicTimerWidget(QWidget *parent = nullptr) : QWidget(parent) {
        // 初始化时添加 2 个基础定时器
        addTimer(1000, "界面刷新");  // 1秒
        addTimer(2000, "数据上传");  // 2秒
    }

private slots:
    // 动态添加定时器(如按钮点击触发)
    void on_addBtn_clicked() {
        int interval = ui->intervalSpin->value();  // 从SpinBox获取用户输入的周期(毫秒)
        QString taskName = ui->taskLineEdit->text();  // 任务名称
        addTimer(interval, taskName);
        qDebug() << "添加定时器:" << taskName << ",周期:" << interval << "ms";
    }

    // 动态移除指定定时器(如按钮点击触发)
    void on_removeBtn_clicked() {
        QString taskName = ui->taskLineEdit->text();
        removeTimer(taskName);
    }

protected:
    void timerEvent(QTimerEvent *e) override {
        int currentTimerId = e->timerId();
        // 通过定时器 ID 查找对应的任务名称
        if (timerMap.contains(currentTimerId)) {
            QString taskName = timerMap[currentTimerId];
            // 执行对应任务
            executeTask(taskName);
        }
    }

private:
    QMap<int, QString> timerMap;  // 关键:定时器 ID → 任务名称

    // 封装:添加定时器
    void addTimer(int interval, const QString &taskName) {
        int timerId = startTimer(interval);
        timerMap.insert(timerId, taskName);  // 保存 ID 和任务名称的映射
    }

    // 封装:移除定时器
    void removeTimer(const QString &taskName) {
        // 遍历找到任务对应的定时器 ID
        for (auto it = timerMap.begin(); it != timerMap.end(); ++it) {
            if (it.value() == taskName) {
                killTimer(it.key());  // 停止定时器
                timerMap.erase(it);   // 从映射中删除
                qDebug() << "移除定时器:" << taskName;
                return;
            }
        }
        qDebug() << "未找到定时器:" << taskName;
    }

    // 执行任务逻辑
    void executeTask(const QString &taskName) {
        if (taskName == "界面刷新") {
            ui->timeLabel->setText(QDateTime::currentDateTime().toString("hh:mm:ss.zzz"));
        } else if (taskName == "数据上传") {
            qDebug() << "任务:数据上传,时间:" << QDateTime::currentDateTime().toString();
        } else {
            // 自定义任务(用户动态添加的任务)
            qDebug() << "任务:" << taskName << ",时间:" << QDateTime::currentDateTime().toString();
        }
    }
};

注意事项

  1. 定时器 ID 的生命周期
    startTimer() 返回的 ID 是唯一的,直到调用 killTimer(id) 或对象销毁,ID 才会失效;
    若重复调用 startTimer(),会生成新的 ID,原定时器仍在运行(需手动停止原 ID 对应的定时器,避免重复触发)。
  2. 避免阻塞定时器
    timerEvent 运行在对象所属的线程(默认主线程),若任务逻辑耗时过长(如超过定时器周期),会导致定时器触发不及时;
    解决:耗时任务(如文件读写、网络请求)放在子线程执行,定时器仅负责触发任务,不直接执行耗时操作。
  3. 线程安全问题
    定时器的 timerEvent 始终在「启动定时器的线程」中执行,若线程是子线程,禁止直接操作 UI 控件(Qt UI 操作需在主线程);
    解决:子线程定时器触发后,通过 signal-slot(跨线程连接)通知主线程更新 UI。
  4. 停止定时器的正确方式
    必须传入正确的定时器 ID 调用 killTimer(id),否则可能停止错误的定时器;
    对象销毁前,建议手动停止所有定时器(避免野指针或无效触发)
  5. 定时器精度
    Qt 的 startTimer() 是基于事件循环的定时器,精度受事件循环繁忙程度影响(毫秒级精度,适合常规场景);
    若需高精度定时器(如微秒级),建议使用 QTimer 配合 Qt::PreciseTimer 类型,或直接使用系统级定时器 API。

二、QTimer

QTimer 是 Qt 提供的高层定时类,封装了底层定时器逻辑,通过 timeout() 信号触发定时任务,无需重写事件处理器(如 timerEvent);

核心优势:支持单次触发 / 重复触发、动态修改周期、暂停 / 重启、高精度模式,且线程安全(需配合信号槽跨线程连接);

依赖 Qt 事件循环:QTimer 的触发依赖 QApplication::exec() 启动的事件循环,若事件循环被阻塞(如耗时操作),定时器会延迟触发。

#include "widget.h"
#include "ui_widget.h"
#include <QDebug>
#include <QTimer>

Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);

    /*定时器方式二*/
    static int num = 1;
    //创建 QTimer 对象(父对象设为 this,自动管理生命周期,避免内存泄漏)
    QTimer * timer = new QTimer(this);
    //timer->setInterval(1000); // 周期:1000ms = 1秒
    //timer->setSingleShot(false); // 重复触发(默认 false,设为 true 则单次触发)
     connect(timer,&QTimer::timeout,this,[=](){
        ui->label->setText(QString::number(num++));
    });
    //启动定时器
    timer->start(20);
}

Widget::~Widget()
{
    delete ui;
}

关键API速查表

函数/信号功能描述
setInterval(int ms)设置定时周期(单位:毫秒)
setSingleShot(bool)设置是否单次触发(true:触发一次后自动停止)
start()启动定时器(若已启动,会重启)
stop()停止定时器(可通过 start() 重启)
isActive()判断定时器是否正在运行(返回 bool)
timeout()信号 定时周期到触发的信号(核心信号)
setTimerType(Qt::TimerType)设置定时器精度(如 Qt::PreciseTimer 高精度)

常见问题

  1. 定时器不触发?
    原因 1:未启动事件循环(如 main 函数中未调用 QApplication::exec());
    原因 2:定时器未调用 start(),或被 stop() 停止后未重启;
    原因 3:QTimer 对象被提前销毁(如未设置父对象,或手动 delete 了);
    原因 4:事件循环被阻塞(如主线程执行耗时操作,未让出 CPU 时间);
    解决:确保事件循环启动、定时器已 start()、对象生命周期正确、耗时操作放在子线程。

  2. 定时器触发延迟?
    原因 1:事件循环繁忙(主线程有大量 UI 操作或耗时任务);
    原因 2:定时器周期过短(如 1ms),超出系统处理能力;
    原因 3:使用了普通精度定时器(Qt::CoarseTimer);
    解决:优化主线程逻辑、延长周期、改用高精度定时器,或用子线程定时器。

  3. 线程安全问题(子线程操作 UI)
    错误示例:子线程中 QTimer 的槽函数直接修改 ui->label->setText();
    后果:程序崩溃或界面错乱(Qt UI 操作必须在主线程);
    解决:通过信号槽跨线程连接(默认 Qt::QueuedConnection):

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值