【Qt开发流程】之定时器事件与随机数示例
  yQAl4kecrO8W 2023年12月22日 68 0


描述

QObject是所有Qt对象的基类,提供了Qt中基础的定时器支持。通过QObject::startTimer()函数,可以使用毫秒为单位的时间间隔来启动一个定时器。该函数返回一个唯一的整数定时器ID。该计时器现在将以规律的间隔触发,直到显式调用QObject::killTimer()函数并传入计时器ID为止。
为了使此机制工作,应用程序必须在事件循环中运行。可以使用QApplication::exec()函数启动事件循环。当计时器触发时,应用程序会发送一个QTimerEvent,并且控制流会离开事件循环,直到处理计时器事件。这意味着在应用程序忙于做其他事情时,计时器不能触发。换句话说:计时器的准确性取决于应用程序的粒度。
在多线程的应用程序中,可以在具有事件循环的任何线程中使用计时器机制。要在非GUI线程中启动事件循环,请使用QThread::exec()。Qt使用对象的线程亲和性来确定哪个线程将传递QTimerEvent。因此,必须在对象的线程中启动和停止所有计时器;无法为另一个线程中的对象启动计时器。
时间间隔值的上限由可以在有符号整数中指定的毫秒数确定(实际上,这是略微超过24天的时间段)。准确性取决于底层操作系统。Windows 2000具有15毫秒的准确性;有的其他系统可以处理1毫秒的间隔。
定时器功能的主要API是QTimer。该类提供定期定时器,当定时器触发时会发出信号,并继承QObject,因此适合大多数GUI程序的所有权结构。通常的使用方式如下所示:

QTimer *timer = new QTimer(this);
      connect(timer, SIGNAL(timeout()), this, SLOT(updateCaption()));
      timer->start(1000);

QTimer对象被设置为该窗口小部件的子项,因此当该窗口小部件被删除时,计时器也将被删除。接下来,将其timeout()信号连接到要进行的工作的槽,并启动它。它使用1000毫秒的值启动,表示它将每秒触发一次超时事件。
QTimer还提供了一个用于短期定时器的静态函数。例如:

QTimer::singleShot(200, this, SLOT(updateCaption()));

在执行此代码行后200毫秒(0.2秒),将调用updateCaption()槽。
为了使QTimer工作,必须在应用程序中有一个事件循环;也就是说,必须在某个地方调用QCoreApplication::exec()。只有在事件循环运行时,计时器事件才会被传递。
在多线程的应用程序中,可以在具有事件循环的任何线程中使用QTimer。要在非GUI线程中启动事件循环,请使用QThread::exec()。由于Qt使用定时器的线程关联性来确定哪个线程将发出timeout()信号,因此必须在其线程中启动和停止计时器;无法从另一个线程启动计时器。
如果已经有了一个QObject子类并且想要进行简单的优化,可以使用QBasicTimer而不是QTimer。使用QBasicTimer,必须在QObject的子类中重新实现timerEvent()函数,并在那里处理超时。

QTimer类

QTimer类提供了重复的单次计时器。
QTimer类为计时器提供了一个高级编程接口。要使用它,需要创建一个QTimer,将其timeout()信号连接到适当的插槽,然后调用start()。从那时起,它将以恒定的间隔发出timeout()信号。
1秒(1000毫秒)定时器的示例(来自模拟时钟示例):

QTimer *timer = new QTimer(this);
      connect(timer, SIGNAL(timeout()), this, SLOT(update()));
      timer->start(1000);

从那时起,每秒钟调用一次update()槽。
你可以通过调用setSingleShot(true)将计时器设置为只超时一次。你也可以使用静态的QTimer::singleShot()函数在指定的时间间隔后调用一个槽:

QTimer::singleShot(200, this, SLOT(updateCaption()));

在多线程应用程序中,您可以在任何具有事件循环的线程中使用QTimer。要从非gui线程启动事件循环,请使用QThread::exec()。Qt使用定时器的线程关联来确定哪个线程将发出timeout()信号。因此,必须在其线程中启动和停止计时器;不可能从另一个线程启动计时器。
作为一种特殊情况,超时为0的QTimer将在处理完窗口系统事件队列中的所有事件后立即超时。这可以用来做繁重的工作,同时提供一个时髦的用户界面:

QTimer *timer = new QTimer(this);
connect(timer, SIGNAL(timeout()), this, SLOT(processOneThing()));
timer- > start ();

从那时起,processOneThing()将被反复调用。它应该以这样一种方式编写,即它总是快速返回(通常在处理一个数据项之后),以便Qt可以将事件传递给用户界面并在完成所有工作后立即停止计时器。这是在GUI应用程序中实现繁重工作的传统方式,但是随着多线程在越来越多的平台上变得可用,我们期望零毫秒的QTimer对象将逐渐被qthread所取代。

精度和计时器分辨率

计时器的准确性取决于底层操作系统和硬件。大多数平台支持1毫秒的分辨率,尽管在许多实际情况下计时器的精度不等于这个分辨率。
准确度还取决于计时器的类型。对于Qt:: precistimer, QTimer将尝试将精度保持在1毫秒。精确的计时器也不会比预期更早超时。
对于Qt::CoarseTimer和Qt::VeryCoarseTimer类型,QTimer可能比预期更早唤醒,在这些类型的间隔范围内:Qt::CoarseTimer的间隔为5%,Qt::VeryCoarseTimer的间隔为500毫秒。
如果系统繁忙或无法提供所要求的准确性,所有计时器类型都可能超时,超时时间晚于预期。在这种超时超时的情况下,即使多个超时已经过期,Qt也只会发出一次activated(),然后恢复原来的时间间隔。

QTimer的替代品

使用QTimer的另一种方法是为对象调用QObject::startTimer(),并在类中重新实现QObject::timerEvent()事件处理程序(必须继承QObject)。缺点是timerEvent()不支持单次计时器或信号等高级特性。
另一个选择是QBasicTimer。它通常比直接使用QObject::startTimer()要简单。
一些操作系统限制可以使用的计时器的数量;Qt试图绕过这些限制。

QTimer常用方法
  1. std::chrono::milliseconds QTimer::intervalAsDuration() const 以std::chrono::milliseconds对象的形式返回计时器的间隔。
  2. bool QTimer::isActive() const 如果计时器正在运行(等待触发)则返回true,否则返回false。
    注意:active属性的getter函数。
  3. std::chrono::milliseconds QTimer::remainingTimeAsDuration() const 以std::chrono::milliseconds对象的形式返回计时器对象的剩余时间。如果计时器已到期或已超时,则返回std::chrono::milliseconds::zero()。如果找不到剩余时间或计时器未处于活动状态,则该函数返回负持续时间。
  4. [static] void QTimer::singleShot(int msec, const QObject *receiver, const char *member) 此静态函数在给定的时间间隔后调用槽。
    使用此函数非常方便,因为不需要使用timerEvent或创建本地QTimer对象。
    示例:
#include <QApplication>
#include <QTimer>

int main(int argc, char *argv[])
{
  QApplication app(argc, argv);
  QTimer::singleShot(600000, &app, SLOT(quit()));
  ...
  return app.exec();
}

该示例程序在10分钟后自动终止(600,000毫秒)。
接收者是接收对象,成员是槽。时间间隔为msec毫秒。

  1. [static] void QTimer::singleShot(int msec, Qt::TimerType timerType, const QObject *receiver, const char *member) 此函数是重载函数。
    此静态函数在给定的时间间隔后调用槽。
    使用此函数非常方便,因为您不需要使用timerEvent或创建本地QTimer对象。
    接收者是接收对象,成员是槽。时间间隔为msec毫秒。timerType影响计时器的准确性。
  2. [static] void QTimer::singleShot(int msec, Qt::TimerType timerType, const QObject *receiver, PointerToMemberFunction method) 此函数是重载函数。
    此静态函数在给定的时间间隔后调用QObject的成员函数。
    使用此函数非常方便,因为您不需要使用timerEvent或创建本地QTimer对象。
    接收者是接收对象,method是成员函数。时间间隔为msec毫秒。timerType影响计时器的准确性。
    如果在时间间隔到达之前接收者被销毁,则不会调用该方法。该函数将在接收对象的线程中运行。接收者的线程必须具有运行的Qt事件循环。
  3. [static] void QTimer::singleShot(int msec, Functor functor) 此函数是重载函数。
    在给定的时间间隔之后,该静态函数将调用functor。
    使用此函数非常方便,因为您不需要使用timerEvent或创建本地QTimer对象。
    时间间隔为msec毫秒。
  4. [static] void QTimer::singleShot(int msec, Qt::TimerType timerType, const QObject *context, Functor functor) 此函数是重载函数。
    在给定的时间间隔之后,该静态函数将调用functor。
    使用此函数非常方便,因为您不需要使用timerEvent或创建本地QTimer对象。
    时间间隔为msec毫秒。timerType影响计时器的准确性。
    如果在时间间隔到达之前上下文被销毁,则不会调用该函数。该函数将在上下文的线程中运行。上下文的线程必须具有运行的Qt事件循环。
  5. [static] void QTimer::singleShot(std::chrono::milliseconds msec, const QObject *receiver, const char *member) 此函数是重载函数。
    在给定的时间间隔之后,该静态函数将调用槽。
    使用此函数非常方便,因为您不需要使用timerEvent或创建本地QTimer对象。
    接收者是接收对象,member是槽。时间间隔以duration对象msec的形式给出。
  6. [slot] void QTimer::start(int msec) 使用msec毫秒的超时间隔启动或重新启动计时器。
    如果计时器已经在运行,则停止并重新启动。
    如果singleShot为true,则计时器仅会被激活一次。
  7. [slot] void QTimer::start() 该函数重载start()。
    使用interval指定的超时间隔启动或重新启动计时器。
    如果计时器已经在运行,则停止并重新启动。
    如果singleShot为true,则计时器仅会被激活一次。
  8. void QTimer::start(std::chrono::milliseconds msec) 这是一种重载函数。
    使用持续时间msec毫秒的超时启动或重新启动计时器。
    如果计时器已经在运行,则停止并重新启动。
    如果singleShot为true,则计时器仅会被激活一次。
  9. [slot] void QTimer::stop() 停止计时器。
  10. [signal] void QTimer::timeout() 当计时器超时时发出此信号。
    注意:这是一个私有信号。它可以用于信号连接,但用户不能发出它。
  11. [virtual protected] void QTimer::timerEvent(QTimerEvent *e) 重新实现自QObject::timerEvent()。
  12. int QTimer::timerId() const 如果计时器正在运行,则返回计时器的ID;否则返回-1。

QTimerEvent类

QTimerEvent类包含描述计时器事件的参数。
计时器事件定期发送给启动了一个或多个计时器的对象。每个计时器都有一个唯一的标识符。计时器通过QObject::startTimer()启动。
QTimer类提供了一个使用信号而不是事件的高级编程接口。它还提供单次计时器。
事件处理程序QObject::timerEvent()接收定时器事件。

class MyObject : public QObject
  {
      Q_OBJECT

  public:
      MyObject(QObject *parent = 0);

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

  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();
  }

定时器示例

下面是一个使用定时器在界面显示当前时间的示例。
.h

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>
#include <QTimer>
#include <QTimerEvent>

namespace Ui {
class MainWindow;
}

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    explicit MainWindow(QWidget *parent = nullptr);
    ~MainWindow();

protected:
    void timerEvent(QTimerEvent *event);

private:
    Ui::MainWindow *ui;
    int mTimerId;
};

#endif // MAINWINDOW_H

.cpp

#include "mainwindow.h"
#include "ui_mainwindow.h"

#include <QTime>

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

    mTimerId = startTimer(1000);
}

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

void MainWindow::timerEvent(QTimerEvent *event)
{
    if(event->timerId() == mTimerId)
    {
        ui->label->setText(QTime::currentTime().toString("hh:mm:ss"));
    }
    QMainWindow::timerEvent(event);
}

【Qt开发流程】之定时器事件与随机数示例_事件循环

随机数

void qsrand(uint seed) 标准c++ srand()函数的线程安全版本。
设置参数seed,用于生成由qrand()返回的伪随机整数组成的新随机数序列。
每个线程生成的随机数序列是确定的。例如,如果两个线程调用qsrand(1)并随后调用qrand(),则线程将获得相同的随机数序列。
使用时,需要在构造中,添加以下代码,不然每次生成的随机数都是一样的,称为"伪随机"。

qsrand(QTime(0, 0, 0).msecsTo(QTime::currentTime()));
int nRand = qrand()%100+1;
qDebug() << "nRand : " << nRand;

结论

生活不止眼前的苟且,还有未来的苟且


【版权声明】本文内容来自摩杜云社区用户原创、第三方投稿、转载,内容版权归原作者所有。本网站的目的在于传递更多信息,不拥有版权,亦不承担相应法律责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@moduyun.com

  1. 分享:
最后一次编辑于 2023年12月22日 0

暂无评论

推荐阅读
yQAl4kecrO8W