简体   繁体   English

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

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

I'm trying to understand the operation of a QTimer. 我正在努力了解QTimer的操作。 I have things triggered off timeout() signals, but I am unable to find in the documentation if a timeout() signal is emitted if I stop the timer early. 我有事情触发了timeout()信号,但我无法在文档中找到如果我提前停止计时器就会发出timeout()信号。

Basically, how can I force a timeout() before the timer finishes counting? 基本上,如何在计时器完成计数之前强制执行timeout()? Just hack by restarting the timer with the minimum ms increment? 只需通过以最小ms增量重新启动计时器来破解?

myTimer->start(1);

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

In neither Qt 4 nor Qt 5 can you directly emit QTimer::timeout from outside of the class. 在Qt 4和Qt 5中都不能直接从类外部发出QTimer::timeout It's a private signal: in Qt 4, it's declared as private , in Qt 5, it's declared with an argument of a private type QObjectPrivate . 这是一个私人信号:在Qt 4中,它被声明为private ,在Qt 5中,它被声明为私有类型QObjectPrivate的参数。

You can invoke it, though: 但是你可以调用它:

// 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");

In Qt 5, the moc-generated QTimer::qt_static_metacall constructs the private argument for us: 在Qt 5中,moc生成的QTimer::qt_static_metacall为我们构造私有参数:

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

You can also make the timer act as if it had timed out by sending it a timer event: 您还可以通过向计时器事件发送计时器来使计时器像超时一样:

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

Both methods work on both Qt 4 and Qt 5. 这两种方法都适用于Qt 4和Qt 5。

Since you're looking to emit a timeout on stopping an active timer, the solutions would be, respectively: 由于您希望在停止活动计时器时发出超时,因此解决方案将分别为:

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

or 要么

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

The signals will be emitted immediately, and not by Qt code from the event loop. 信号将立即发出,而不是来自事件循环的Qt代码。 This shouldn't be a problem, since emitTimeoutAndStop will be invoked with an event loop on the stack. 这应该不是问题,因为将通过堆栈上的事件循环调用emitTimeoutAndStop We assert that fact. 我们断言这个事实。 If you wish to support invoking emitTimeoutAndStop from code tied to the same timer's timeout signal without reentering said code, then you have to use the ChattyTimer below, or the solution from another answer . 如果你希望支持从绑定到相同计时器的timeout信号的代码调用emitTimeoutAndStop而不重新输入所述代码,那么你必须使用下面的ChattyTimer ,或者来自另一个答案的解决方案

If all you need isn't quite a timer, but just an immediate, single-emission signal, QObject::destroyed is useful for that purpose. 如果您需要的只是一个计时器,而只是一个即时的单发射信号, QObject::destroyed对此非常有用。 It will be emitted at the end of the block, where source goes out of scope and is destructed. 它将在块的末尾发出,其中source超出范围并被破坏。

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

Alternatively, you can have a small helper class: 或者,您可以拥有一个小助手类:

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

Since you can connect multiple signals to a single receiver, perhaps it would make things clearer to connect both a timer and such a signal source to the receiver, instead of trying to hack around timer's behavior. 由于您可以将多个信号连接到单个接收器,因此可能会更清楚地将定时器和此类信号源连接到接收器,而不是试图破解计时器的行为。

On the other hand, if such "signaling upon stopping" timer is something you find useful in several places, it'd be better to actually implement it as a dedicated class - it's not all that hard. 另一方面,如果这种“停止时发出信号”计时器是你在几个地方发现有用的东西,那么实际上把它作为一个专用的类实现它会更好 - 它并不是那么难。 Re-using the QTimer class is not possible, since the stop() slot is not virtual, and thus ChattyTimer is not Liskov-substitutable for a QTimer . 不能重新使用QTimer类,因为stop()槽不是虚拟的,因此ChattyTimer不是Liskov可替代的QTimer This would be a bug waiting to happen - in the class of bugs that are hard to find. 这将是一个等待发生的错误 - 在很难找到的错误类中。

There are several behavioral details that demand attention. 有几个需要注意的行为细节。 This perhaps indicates that changing the behavior of something as fundamental as a timer is tricky - you never know what code might make assumptions that are obviously correct in QTimer , but not so when stop() might emit a timeout. 这或许表明将某些事物的行为改变为计时器的基本操作是棘手的 - 您永远不知道哪些代码可能会使QTimer假设明显正确,但是当stop()可能发出超时时则不然。 It is a good idea to have all this in a class that is not-a QTimer - it really is not! 把这一切都放在一个不是QTimer的课堂上是个好主意 - 实际上并非如此!

  1. As in QTimer , the timeout event is always emitted from the event loop. 与在QTimer ,超时事件始终从事件循环中发出。 To have it emitted immediately from stop() , set immediateStopTimeout . 要从stop()立即发出它,请设置immediateStopTimeout

  2. There are two generalizations possible of isActive behavior upon stop (vs. that of QTimer ): 停止时有两种可能的isActive行为(与QTimer ):

    • becomes false immediately after stop returns, even if the final timeout will be emitted later, or stop返回后立即变为假,即使最后的timeout将在稍后发出,或者
    • indicates whether timeout events may be emitted by the event loop, and will remain true after a stop() if the final timeout signal is deferred. 指示事件循环是否可以发出超时事件,并且如果延迟最终timeout信号,则在stop()之后将保持为true

    I chose the first behavior to be the default. 我选择了第一个行为作为默认行为。 Set activeUntilLastTimeout to select the second behavior. 设置activeUntilLastTimeout以选择第二个行为。

  3. There are three generalizations possible of the behavior of each of timerId and remainingTime upon stop (vs. that of QTimer ): 在停止时(与QTimer ),每个timerIdremainingTime的行为有三种可能的概括:

    • returns -1 when isActive() is false, or a valid identifier/time otherwise (ie follows chosen isActive() behavior), isActive()为false时返回-1 ,否则返回有效的标识符/时间(即跟随选择的isActive()行为),
    • becomes -1 immediately after stop returns, even if the final timeout will be emitted later, stop返回后立即变为-1 ,即使最后timeout将在稍后发出,
    • returns a valid id/time whenever timeout events may still be emitted by the event loop. 只要事件循环仍然发出超时事件,就会返回有效的id / time。

    I chose the first behavior for both timerId and remainingTime , and it is not otherwise configurable. 我选择了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; }
};

If you stop a QTimer, will it emit a timeout() signal? 如果你停止QTimer,它会发出timeout()信号吗?

No. 没有。

Basically, how can I force a timeout() before the timer finishes counting? 基本上,如何在计时器完成计数之前强制执行timeout()? Just hack by restarting the timer with the minimum ms increment? 只需通过以最小ms增量重新启动计时器来破解?

Call stop() on the timer, and then cause the signal to be emitted yourself. 在定时器上调用stop(),然后自己发出信号。 You could do this by subclassing QTimer and calling a method in your QTimer subclass that emits the signal: 您可以通过继承QTimer并在QTimer子类中调用发出信号的方法来完成此操作:

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

… but if you don't want to go to the bother of making a subclass, an easier approach would be to add a signal to your own class and connect that signal to the QTimer object's timeout() signal (do this only once of course): ...但是如果你不想去制作一个子类,那么更简单的方法就是向你自己的类添加一个信号并将该信号连接到QTimer对象的timeout()信号(当然只做一次) ):

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

… and then your stop-and-fire method would could be done like this: ...然后你的停火方法就可以这样做:

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

It's actually quite easy to do this, at least in 4.8 and later (not sure about earlier versions): simply setInterval(0) (much like you suggest in your question, although there's no need to stop the timer and restart it). 实际上很容易做到这一点,至少在4.8及更高版本中(不确定早期版本):只需setInterval(0) (就像你在问题中建议的那样,尽管没有必要停止计时器并重新启动它)。

This app will immediately print "Timer expired" and exit: 此应用程序将立即打印“计时器已过期”并退出:

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