簡體   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