繁体   English   中英

如果你停止QTimer,它会发出timeout()信号吗?

[英]If you stop a QTimer, will it emit a timeout() signal?

我正在努力了解QTimer的操作。 我有事情触发了timeout()信号,但我无法在文档中找到如果我提前停止计时器就会发出timeout()信号。

基本上,如何在计时器完成计数之前强制执行timeout()? 只需通过以最小ms增量重新启动计时器来破解?

myTimer->start(1);

http://qt-project.org/doc/qt-5/qtimer.html#timeout

在Qt 4和Qt 5中都不能直接从类外部发出QTimer::timeout 这是一个私人信号:在Qt 4中,它被声明为private ,在Qt 5中,它被声明为私有类型QObjectPrivate的参数。

但是你可以调用它:

// fast, guaranteed to work in Qt 4 and 5
myTimer->QTimer::qt_metacall(QMetaObject::InvokeMetaMethod, 5, {});

// slower, has to look up the method by name
QMetaObject::invokeMethod(myTimer, "timeout");

在Qt 5中,moc生成的QTimer::qt_static_metacall为我们构造私有参数:

//...
case 0: _t->timeout(QPrivateSignal()); break;

您还可以通过向计时器事件发送计时器来使计时器像超时一样:

void emitTimeout(QTimer * timer) {
  Q_ASSERT(timer);
  QTimerEvent event{timer->timerId()};
  QCoreApplication::sendEvent(timer, &event);
}

这两种方法都适用于Qt 4和Qt 5。

由于您希望在停止活动计时器时发出超时,因此解决方案将分别为:

void emitTimeoutAndStop(QTimer * timer) {
  Q_ASSERT(timer);
  Q_ASSERT(QAbstractEventDispatcher::instance()); // event loop must be on the stack
  if (!timer->isActive()) return;
  timer->QTimer::qt_metacall(QMetaObject::InvokeMetaMethod, 5, {});
  timer->stop();
}

要么

void emitTimeoutAndStop(QTimer * timer) {
  Q_ASSERT(timer);
  Q_ASSERT(QAbstractEventDispatcher::instance()); // event loop must be on the stack
  if (!timer->isActive()) return;
  QTimerEvent event{timer->timerId()};
  QCoreApplication::sendEvent(timer, &event);
  timer->stop();
}

信号将立即发出,而不是来自事件循环的Qt代码。 这应该不是问题,因为将通过堆栈上的事件循环调用emitTimeoutAndStop 我们断言这个事实。 如果你希望支持从绑定到相同计时器的timeout信号的代码调用emitTimeoutAndStop而不重新输入所述代码,那么你必须使用下面的ChattyTimer ,或者来自另一个答案的解决方案

如果您需要的只是一个计时器,而只是一个即时的单发射信号, QObject::destroyed对此非常有用。 它将在块的末尾发出,其中source超出范围并被破坏。

{ QObject source;
  connect(&source, &QObject::destroyed, ...); }

或者,您可以拥有一个小助手类:

// signalsource.h
#pragma once
#include <QObject>
class SignalSource : public QObject {
  Q_OBJECT
public:
  Q_SIGNAL void signal();
  SignalSource(QObject * parent = {}) : QObject(parent) {}
};

由于您可以将多个信号连接到单个接收器,因此可能会更清楚地将定时器和此类信号源连接到接收器,而不是试图破解计时器的行为。

另一方面,如果这种“停止时发出信号”计时器是你在几个地方发现有用的东西,那么实际上把它作为一个专用的类实现它会更好 - 它并不是那么难。 不能重新使用QTimer类,因为stop()槽不是虚拟的,因此ChattyTimer不是Liskov可替代的QTimer 这将是一个等待发生的错误 - 在很难找到的错误类中。

有几个需要注意的行为细节。 这或许表明将某些事物的行为改变为计时器的基本操作是棘手的 - 您永远不知道哪些代码可能会使QTimer假设明显正确,但是当stop()可能发出超时时则不然。 把这一切都放在一个不是QTimer的课堂上是个好主意 - 实际上并非如此!

  1. 与在QTimer ,超时事件始终从事件循环中发出。 要从stop()立即发出它,请设置immediateStopTimeout

  2. 停止时有两种可能的isActive行为(与QTimer ):

    • stop返回后立即变为假,即使最后的timeout将在稍后发出,或者
    • 指示事件循环是否可以发出超时事件,并且如果延迟最终timeout信号,则在stop()之后将保持为true

    我选择了第一个行为作为默认行为。 设置activeUntilLastTimeout以选择第二个行为。

  3. 在停止时(与QTimer ),每个timerIdremainingTime的行为有三种可能的概括:

    • isActive()为false时返回-1 ,否则返回有效的标识符/时间(即跟随选择的isActive()行为),
    • stop返回后立即变为-1 ,即使最后timeout将在稍后发出,
    • 只要事件循环仍然发出超时事件,就会返回有效的id / time。

    我选择了timerIdremainingTime的第一个行为,并且它是不可配置的。

// https://github.com/KubaO/stackoverflown/tree/master/questions/chattytimer-25695203
// chattytimer.h
#pragma once
#include <QAbstractEventDispatcher>
#include <QBasicTimer>
#include <QTimerEvent>
class ChattyTimer : public QObject {
   Q_OBJECT
   Q_PROPERTY(bool active READ isActive)
   Q_PROPERTY(int remainingTime READ remainingTime)
   Q_PROPERTY(int interval READ interval WRITE setInterval)
   Q_PROPERTY(bool singleShot READ singleShot WRITE setSingleShot)
   Q_PROPERTY(Qt::TimerType timerType READ timerType WRITE setTimerType)
   Q_PROPERTY(bool immediateStopTimeout READ immediateStopTimeout WRITE setImmediateStopTimeout)
   Q_PROPERTY(bool activeUntilLastTimeout READ activeUntilLastTimeout WRITE setActiveUntilLastTimeout)
   Qt::TimerType m_type = Qt::CoarseTimer;
   bool m_singleShot = false;
   bool m_stopTimeout = false;
   bool m_immediateStopTimeout = false;
   bool m_activeUntilLastTimeout = false;
   QBasicTimer m_timer;
   int m_interval = 0;
   void timerEvent(QTimerEvent * ev) override {
      if (ev->timerId() != m_timer.timerId()) return;
      if (m_singleShot || m_stopTimeout) m_timer.stop();
      m_stopTimeout = false;
      emit timeout({});
   }
public:
   ChattyTimer(QObject * parent = {}) : QObject(parent) {}
   Q_SLOT void start(int msec) {
      m_interval = msec;
      start();
   }
   Q_SLOT void start() {
      m_stopTimeout = false;
      m_timer.stop(); // don't emit the signal here
      m_timer.start(m_interval, m_type, this);
   }
   Q_SLOT void stop() {
      if (!isActive()) return;
      m_timer.stop();
      m_stopTimeout = !m_immediateStopTimeout;
      if (m_immediateStopTimeout)
         emit timeout({});
      else // defer to the event loop
         m_timer.start(0, this);
   }
   Q_SIGNAL void timeout(QPrivateSignal);
   int timerId() const {
      return isActive() ? m_timer.timerId() : -1;
   }
   bool isActive() const {
      return m_timer.isActive() && (m_activeUntilLastTimeout || !m_stopTimeout);
   }
   int remainingTime() const {
      return
            isActive()
            ? QAbstractEventDispatcher::instance()->remainingTime(m_timer.timerId())
            : -1;
   }
   int interval() const { return m_interval; }
   void setInterval(int msec) {
      m_interval = msec;
      if (!isActive()) return;
      m_timer.stop(); // don't emit the signal here
      start();
   }
   bool singleShot() const { return m_singleShot; }
   void setSingleShot(bool s) { m_singleShot = s; }
   Qt::TimerType timerType() const { return m_type; }
   void setTimerType(Qt::TimerType t) { m_type = t; }
   bool immediateStopTimeout() const { return m_immediateStopTimeout; }
   void setImmediateStopTimeout(bool s) { m_immediateStopTimeout = s; }
   bool activeUntilLastTimeout() const { return m_activeUntilLastTimeout; }
   void setActiveUntilLastTimeout(bool s) { m_activeUntilLastTimeout = s; }
};

如果你停止QTimer,它会发出timeout()信号吗?

没有。

基本上,如何在计时器完成计数之前强制执行timeout()? 只需通过以最小ms增量重新启动计时器来破解?

在定时器上调用stop(),然后自己发出信号。 您可以通过继承QTimer并在QTimer子类中调用发出信号的方法来完成此操作:

void MyQTimer :: EmitTimeoutSignal() {emit timeout();}

...但是如果你不想去制作一个子类,那么更简单的方法就是向你自己的类添加一个信号并将该信号连接到QTimer对象的timeout()信号(当然只做一次) ):

connect(this, SIGNAL(MyTimeoutSignal()), myTimer, SIGNAL(timeout()));

...然后你的停火方法就可以这样做:

myTimer->stop();
emit MyTimeoutSignal();

实际上很容易做到这一点,至少在4.8及更高版本中(不确定早期版本):只需setInterval(0) (就像你在问题中建议的那样,尽管没有必要停止计时器并重新启动它)。

此应用程序将立即打印“计时器已过期”并退出:

int main(int argc, char* argv[])
{
  QCoreApplication app(argc, argv);

  QTimer timer;
  timer.setSingleShot(true);
  timer.setInterval(1000 * 60 * 60); // one hour

  QObject::connect(
      &timer,  &QTimer::timeout,
      [&]()
      {
        std::cout << "Timer expired" << std::endl;
        app.exit();
      });

  QTimer::singleShot(
      0, //trigger immediately once QtEventLoop is running
      [&]()
      {
        timer.start();
        timer.setInterval(0);  // Comment this out to run for an hour.
      });

  app.exec();
}

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM