【Qt 应用开发 】QT 三种定时器的介绍 以及 QTimer startTimer/Timerevent QBasicTimer 之间的区别

简介: 【Qt 应用开发 】QT 三种定时器的介绍 以及 QTimer startTimer/Timerevent QBasicTimer 之间的区别



概述

Qt一共提供了三种计时器实现方式.


简单介绍

  • QBasicTimer:
    QBasicTimer类为对象提供定时器事件。
    QBasicTimer特点快速、轻量级和低级类。
    对于需要降低使用多个定时器开销的应用程序,QBasicTimer可能是一个不错的选择。
    如果是一般使用情况建议使用更高级别的QTimer类而不是此类。
  • QObject内部定时器:
    使用startTimer开启定时器,使用killTimer(int id)接口来关闭指定的定时器。
    启动定时器后会在对应间隔时间触发timerEvent事件。
  • QTimer:
    QTimer类提供重复和单次定时器。
    QTimer类为定时器提供高级编程接口。创建一个QTimer实例,将其timeout()信号连接到对应的槽中,然后调用start()开启定时器,每隔一段时间会发出timeout()信号。

QTimer 定时器(QTimer Class)


概述

QTimer 类为定时器提供了一个高级编程接口。要使用它,请创建一个 QTimer,将其 timeout() 信号连接到适当的插槽,然后调用 start()。从那时起,它将以恒定的时间间隔发出 timeout() 信号。
在多线程应用程序中,可以在任何具有事件循环的线程中使用 QTimer。要从非 GUI 线程启动事件循环,请使用 QThread::exec()。
Qt 使用计时器的线程亲和性来确定哪个线程将发出 timeout() 信号。因此,您必须在其线程中启动和停止计时器;无法从另一个线程启动计时器。
作为一种特殊情况,超时为 0 的 QTimer 会尽快超时,尽管未指定零定时器和其他事件源之间的顺序。零定时器可以用来做一些工作,同时仍然提供一个活泼的用户界面.
计时器的准确性取决于底层操作系统和硬件。大多数平台支持 1 毫秒的分辨率,尽管在许多实际情况下计时器的精度不会等于该分辨率。
精度还取决于定时器类型。对于 Qt::PreciseTimer,QTimer 将尝试将精度保持在 1 毫秒。精确的计时器也永远不会比预期的更早超时。
对于 Qt::CoarseTimer 和 Qt::VeryCoarseTimer 类型,QTimer 可能比预期更早唤醒,在这些类型的余量内:Qt::CoarseTimer 间隔的 5% 和 Qt::VeryCoarseTimer 间隔的 500 毫秒。
如果系统繁忙或无法提供所请求的精度,所有计时器类型都可能比预期的时间晚。在这种超时溢出的情况下,Qt 将只发出一次 timeout(),即使多个超时已过期,然后将恢复原始间隔。

权威资料

QTimer详细细节建议看一遍官方文档https://doc.qt.io/qt-6/qtimer.html
本文主要讲述如何快速使用.

定时器精度

//获取:
Qt::TimerType timerType() const
//设置:
void setTimerType(Qt::TimerType atype)

定时器的精度取决于底层操作系统和硬件。绝大多数平台支持精度为1毫秒,尽管定时器的准确性在许多现实世界的情况下和这不相符。同时使用的精度越高(即时间分辨率越高)所消耗的资源也就越大。
QTimer 的精度最大可以达到毫秒级(Qt::PreciseTimer),也就是说启动这个定时器后,两次计时可以相差1毫秒,非常精确了,1秒钟等于1000毫秒。Qt 的策略是在 Unix(包括 Linux、macOS)采用该精度;
但我们常用的 QTimer 默认是 Qt::CoarseTimer的。它不会那么精确,这样可以减少 CPU 唤醒从而保证系统资源不是很紧张,和1毫秒的误差也就在5%的范围内。除非场景要求很精确,一般我们就用这个默认值即可;还有一个粗略的精度是 Qt::VeryCoarseTimer,这个计时器基本上精度在1秒左右,对精度要求不高的地方用这个,同样是为了节省 CPU 资源。

时间间隔

//获取:
int interval() const
//设置:
void setInterval(int msec)

如果不设置的话默认是0,时间隔为 0 的 QTimer 将在处理完窗口系统事件队列中的所有事件后立即超时。

单次触发功能

//判断::
bool isSingleShot() const
//设置:
void setSingleShot(bool singleShot)
//static 静态函数:
void singleShot(int msec, ...)
//例如
//QTimer::singleShot(200, this, SLOT(updateCaption()));

QTimer 提供的 static 静态函数用,可以不用创建一个 QTimer 对象直接使用

开启(重启)/停止

//Public 函数
    void start(std::chrono::milliseconds msec)
//槽函数
    void start(int msec)
    void start()
    void stop()

注意第一个 start(std::chrono::milliseconds msec) 函数的意思是 msec 毫秒后才被触发;而槽函数的 msec 表示触发后时间间隔是多少,稍微不一样。
start表示启动或重新启动计时器,超时时间为 msec 毫秒。如果计时器已经在运行,它将停止并重新启动。

运行状态

//是否运行:
bool isActive() 
//const ID:
int timerId() const
//剩余时间:
 intremainingTime() const 
//发射信号:
 void timeout()

槽函数的绑定方式

//1.最常见的方式绑定
connect(m_pTimer, SIGNAL(timeout()), this, SLOT(handleTimeout()));  
//2.Lambda表达式绑定
connect(m_pTimer,&QTimer::timeout,this,[=]()
{
       handleTimeoutprocess(i);
});

示例

周期性定时器:

// 创建定时器对象
QTimer* timer = new QTimer(this);
// 修改定时器对象的精度
timer->setTimerType(Qt::PreciseTimer);
// 按钮 loopBtn 的点击事件
// 点击按钮启动或者关闭定时器, 定时器启动, 周期性得到当前时间
connect(ui->loopBtn, &QPushButton::clicked, this, [=]()
{
    // 启动定时器
    if(timer->isActive())
    {
        timer->stop();  // 关闭定时器
        ui->loopBtn->setText("开始");
    }
    else
    {
        ui->loopBtn->setText("关闭");
        timer->start(1000); // 1000ms == 1s
    }
});
connect(timer, &QTimer::timeout, this, [=]()
{
    QTime tm = QTime::currentTime();
    // 格式化当前得到的系统时间
    QString tmstr = tm.toString("hh:mm:ss.zzz");
    // 设置要显示的时间
    ui->curTime->setText(tmstr);
});

一次性定时器:

// 点击按钮 onceBtn 只发射一次信号
// 点击按钮一次, 发射一个信号, 得到某一个时间点的时间
connect(ui->onceBtn, &QPushButton::clicked, this, [=]()
{
     // 获取2s以后的系统时间, 不创建定时器对象, 直接使用类的静态方法
    QTimer::singleShot(2000, this, [=](){
        QTime tm = QTime::currentTime();
        // 格式化当前得到的系统时间
        QString tmstr = tm.toString("hh:mm:ss.zzz");
        // 设置要显示的时间
        ui->onceTime->setText(tmstr);
    });
});

QT定时器事件(Timerevent)


概述

该函数是QObject类的函数。启动一个定时器并返回该定时器基于整形的定时器id。其有如下两个重载函数.
当调用startTimer后,每隔interval毫秒(第1个函数)或time(第2个函数)就会发送一个定时器事件,直到 killTimer函数被调用才停止发送。
如果interval或time为0,则每次当系统中再也没有其它事件即当事件队列中的事件全部处理完时才会发送一次定时器事件。
当定时器事件发生时,QOjbect或其子类可以在虚函数timerEvent中捕捉定时器事件,timerEvent函数的参数就是QTimerEvent对象即定期是事件对象。
如果有多个定时器正在运行,可以用 QTimerEvent::timerId()找出哪个是正在激活使用的定时器。

开启定时器(startTimer)

int QObject::startTimer(int interval, Qt::TimerType timerType = Qt::CoarseTimer);
//A timer event will occur every interval milliseconds until killTimer() is called. 
//If interval is 0, then the timer event occurs once every time there are no more window system events to process.
int QObject::startTimer(std::chrono::milliseconds time, Qt::TimerType timerType = Qt::CoarseTimer);
//This is an overloaded function.
//A timer event will occur every time interval until killTimer() is called. 
//If time is equal to std::chrono::duration::zero(), then the timer event occurs once every time there are no more window system events to process.

Description:


Starts a timer and returns a timer identifier, or returns zero if it could not start a timer.
The virtual timerEvent() function is called with the QTimerEvent event parameter class when a timer event occurs. Reimplement this function to get timer events.
If multiple timers are running, the QTimerEvent::timerId() can be used to find out which timer was activated.
启动计时器并返回计时器标识符,如果无法启动计时器,则返回零。
发生定时器事件时,使用 QTimerEvent 事件参数类调用虚函数 timerEvent() 。 重新实现此函数以获取计时器事件。
如果多个定时器正在运行,QTimerEvent::timerId() 可以用来找出哪个定时器被激活。

Example:

class MyObject : public QObject {
    Q_OBJECT
public:
    MyObject(QObject *parent = nullptr);
protected:
    void timerEvent(QTimerEvent *event) override; };
MyObject::MyObject(QObject *parent)
    : QObject(parent) {
    startTimer(50);     // 50-millisecond timer
    startTimer(1000);   // 1-second timer
    startTimer(60000);  // 1-minute timer
    using namespace std::chrono;
    startTimer(milliseconds(50));
    startTimer(seconds(1));
    startTimer(minutes(1));
    // since C++14 we can use std::chrono::duration literals, e.g.:
    startTimer(100ms);
    startTimer(5s);
    startTimer(2min);
    startTimer(1h); }
void MyObject::timerEvent(QTimerEvent *event) {
    qDebug() << "Timer ID:" << event->timerId(); }

关闭定时器(killTimer)

void QObject::killTimer(int id)                                                 
// Kills the timer with timer identifier, id.
// The timer identifier is returned by startTimer() when a timer event is started.

定时器事件(timerEvent)

void QObject::timerEvent(QTimerEvent *event)                                //[virtual protected]
//This event handler can be reimplemented in a subclass to receive timer events for the object.
//QTimer provides a higher-level interface to the timer functionality, and also more general information about timers. 
//The timer event is passed in the event parameter.

关于timerId

  • startTime成功后将返回一个大于0的int标识.如果没成功则返回0.
  • 由于没有像QTimer有isActive()函数,判断定时器是否活跃可以通过标识值.
  • killTimer关闭定时器后,标识值不会改变,如果需要根据这个值判断的话,需要手动更改.

QBasicTimer定时器


概述

  • QBasicTimers 是一个很快的、轻量级的定时器类,它主要被Qt内部使用。
    所以,我们一般不建议在上层应用程序中直接使用这个类去做定时器工作。
    在开发应用程序时,我们一般推荐使用QTimer类和QObject的成员函数startTimer来启动定时器。
    要使用这个类,创建一个 QBasicTimer,并使用一个超时间隔和一个指向 QObject 子类的指针调用它的 start() 函数。
    当定时器超时时,它会向 QObject 子类发送一个定时器事件。
    可以使用 stop() 随时停止计时器。 isActive() 为正在运行的计时器返回 true;即已启动,未到超时时间,未停止。
    可以使用 timerId() 检索计时器的 ID。
    此类的对象无法复制,但可以移动,因此您可以通过将基本计时器保存在支持仅移动类型的容器中来维护基本计时器列表.

Public Functions

void QBasicTimer(QBasicTimer &&other)
void QBasicTimer()
~QBasicTimer()
QBasicTimer & operator=(QBasicTimer &&other)
bool isActive() const
void start(int msec, QObject *object)
void start(int msec, Qt::TimerType timerType, QObject *obj)
void stop()
void swap(QBasicTimer &other)
int timerId() const

示例

QBasicTimer timer; timer.start(60, this);   void
Widget::timerEvent(QTimerEvent *event) 
{
    if (event->timerId() == timer.timerId()) 
    {
        //处理定时操作
    } 
    else
    {
        QWidget::timerEvent(event);
    } 
    }

QTimer Timerevent QBasicTimer的区别和选择


  • QTimer需要自己编写方法,通过timeout信号连接槽(自己编写的方法),达到定时作用.
    而QT定时器事件(Timerevent)和QBasicTimer定时器都需要重写timerEvent()函数.
  • QTimertimerEvent的功能基本一致,QTimer更为清晰和灵活,而timerEvent更为简约和高效(缺点是 timerEvent() 不支持单次定时器或信号等高级功能。同时QBasicTimer比直接使用 QObject::startTimer() 更简单。

在 Qt 编程框架中,QTimerTimerevent(通常指的是重写的timerEvent()函数)和 QBasicTimer 是用于处理定时器相关任务的三种不同方法。它们各有特点和适用场景。

  1. QTimer:
  • 功能: QTimer是一个高级定时器,提供了比较丰富的接口,比如信号和槽机制。
  • 使用场景: 当你需要在定时器触发时执行复杂的操作,或者需要和其他的Qt对象交互时,QTimer是一个不错的选择。
  • 优点: 易于使用,可以很方便地集成到Qt的事件循环中。支持单次触发和周期触发。
  • 缺点: 相对于其他两种方法,QTimer可能会更占用资源。
  1. Timerevent (timerEvent()):
  • 功能: timerEvent()是一个在Qt对象中重写的事件处理函数,用于处理所有定时器事件。
  • 使用场景: 当你需要在一个对象中处理多个定时器,或者需要在定时器触发时进行较为底层的处理时,重写timerEvent()是一个好选择。
  • 优点: 提供了更多的控制权,可以处理多个定时器。
  • 缺点: 实现起来比QTimer更复杂,需要手动管理定时器ID。
  1. QBasicTimer:
  • 功能: QBasicTimer是一个更轻量级的定时器,提供基本的定时器功能。
  • 使用场景: 当你需要一个简单的、不占用多余资源的定时器,或者是在嵌入式系统中使用定时器时,QBasicTimer是一个好选择。
  • 优点: 占用资源少,适合于性能敏感的应用。
  • 缺点: 功能相对简单,不支持信号和槽机制,需要手动管理启动和停止。

总结来说,选择哪种定时器取决于你的具体需求:如果需要丰富的功能和易用性,选择QTimer;如果需要更精细的控制或处理多个定时器,可以考虑重写timerEvent();而在资源有限或性能要求较高的情况下,QBasicTimer可能是更好的选择。

特性 / 定时器类型 QTimer timerEvent() QBasicTimer
功能 高级定时器,提供丰富接口 事件处理函数,手动管理定时器 轻量级定时器,基本功能
使用场景 复杂操作,Qt对象交互 处理多个定时器,底层处理 简单用途,性能敏感应用
优点 易于使用,集成事件循环 更多控制权,多定时器处理 资源占用少,性能高效
缺点 资源占用相对较高 实现复杂,手动管理定时器 功能简单,无信号槽机制
支持的触发方式 单次和周期触发 需要手动实现 需要手动实现
适用于 桌面应用 复杂的Qt应用 嵌入式系统、性能敏感应用

QTimer和 Timerevent QBasicTimer的本质区别

  1. 信号和槽(元对象系统)
  • 信号和槽是 Qt 的一个核心特性,是其元对象系统(Meta-Object System)的一部分。这个系统提供了对象间通信的机制。
  • QTimer 使用这个机制来通知应用程序定时器已经超时。它发出一个 timeout 信号,这个信号可以连接到任何兼容的槽函数上。
  • 这个机制是独立于 Qt 的主事件循环的,但它依赖于事件循环来分发信号。
  1. Qt 事件系统
  • Qt 的事件系统是处理各种事件(如鼠标点击、按键、定时器事件等)的基础设施。
  • timerEvent 是一个事件处理函数,属于 Qt 事件系统的一部分。当使用 startTimer 创建定时器时,Qt 会在定时器到期时发送一个定时器事件(QTimerEvent)到对象的事件处理函数。
  • timerEvent 方法必须在对象类中被重写以响应定时器事件。它直接处理事件,不通过信号和槽机制。

因此,您可以这样理解:

  • 信号和槽:提供了一种灵活的方式来处理对象间的通信,它们是元对象系统的一部分,依赖于 Qt 的事件循环来分发信号。
  • Qt 事件系统:是处理和响应各种事件的基础,timerEvent 直接属于这个系统,用于响应定时器事件,与信号和槽机制相独立。

简而言之,QTimer 和信号槽机制提供了一种高级、灵活的方式来处理定时器超时,而 timerEvent 提供了一种更直接、低级的方式来处理定时器事件。二者都在 Qt 的主事件循环中起作用,但它们属于不同的系统并服务于不同的目的。

结语

在我们的编程学习之旅中,理解是我们迈向更高层次的重要一步。然而,掌握新技能、新理念,始终需要时间和坚持。从心理学的角度看,学习往往伴随着不断的试错和调整,这就像是我们的大脑在逐渐优化其解决问题的“算法”。

这就是为什么当我们遇到错误,我们应该将其视为学习和进步的机会,而不仅仅是困扰。通过理解和解决这些问题,我们不仅可以修复当前的代码,更可以提升我们的编程能力,防止在未来的项目中犯相同的错误。

我鼓励大家积极参与进来,不断提升自己的编程技术。无论你是初学者还是有经验的开发者,我希望我的博客能对你的学习之路有所帮助。如果你觉得这篇文章有用,不妨点击收藏,或者留下你的评论分享你的见解和经验,也欢迎你对我博客的内容提出建议和问题。每一次的点赞、评论、分享和关注都是对我的最大支持,也是对我持续分享和创作的动力。

目录
相关文章
|
7月前
【qt】日历和定时器
【qt】日历和定时器
88 0
|
3月前
|
设计模式 前端开发 安全
Qt注册类对象单例与单类型区别
在进行开发时,应当根据具体的应用场景和需求来选择使用单例模式或是单类型。如果是全局服务或状态管理,可能需要单例模式;如果是为了使QML环境下的不同组件能够访问到同一个后端服务对象,则可能需要使用单类型。
51 2
|
6月前
Qt定时器
Qt定时器
|
6月前
|
IDE Linux 开发工具
在Qt开发环境中qmake和cmake的区别优势
选择qmake还是CMake,主要取决于项目的需求和开发者的熟悉程度。如果你正在开发一个纯Qt项目,或者是一个不需要复杂构建脚本的小型项目,qmake可能是一个更好的选择。反之,如果你的项目需要处理复杂的依赖关系,或者你想要一个在多种编程环境中都能工作的构建系统,那么CMake可能是更好的选择。
961 2
|
6月前
|
API
【Qt】Qt定时器类QTimer
【Qt】Qt定时器类QTimer
|
7月前
|
Linux 图形学
深入理解Qt定时器:QTimer的魅力与挑战(一)
深入理解Qt定时器:QTimer的魅力与挑战
4475 0
|
7月前
|
C++
QT定时器的使用timer示例
QT定时器的使用timer示例
|
7月前
|
安全 API 开发者
深入理解Qt定时器:QTimer的魅力与挑战(二)
深入理解Qt定时器:QTimer的魅力与挑战
496 0
|
7月前
|
安全
深入理解Qt多线程编程:QThread、QTimer与QAudioOutput的内在联系__Qt 事件循环(三)
深入理解Qt多线程编程:QThread、QTimer与QAudioOutput的内在联系__Qt 事件循环
215 0
|
7月前
|
存储 安全 程序员
深入理解Qt多线程编程:QThread、QTimer与QAudioOutput的内在联系__Qt 事件循环(二)
深入理解Qt多线程编程:QThread、QTimer与QAudioOutput的内在联系__Qt 事件循环
878 0