2014-09-05 274 views
1

我想了解QTimer的操作。我有事情觸發了timeout()信號,但是如果我提前停止計時器,我無法在文檔中找到是否發出了timeout()信號。如果停止QTimer,它會發出一個timeout()信號嗎?

基本上,我怎麼能強制超時()之前,定時器完成計數?只需通過以最小ms增量重新啓動定時器來破解?

myTimer->start(1); 

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

+0

我很困惑,可能失去了一些東西。如果您手動停止計時器,則還可以手動調用其超時調用的任何函數。我想有些複雜因素,我忽略了什麼是要求? – Erik 2014-09-05 23:54:47

+2

「如果我提早停止計時器,如果發出超時()信號,我無法在文檔中找到。」這就是爲什麼你有Qt Creator。創建一個新項目並輸入測試所需的代碼應該會花費不到60秒的時間:)這就是Qt的力量:) – 2014-09-06 00:47:23

+0

真實的,真@ kubaOber – tarabyte 2014-09-06 03:56:12

回答

4

如果停止QTimer,它會發出超時()信號?

基本上,如何可以強制超時()定時器結束計數 前?只需通過以最小ms 增量重新啓動計時器來破解?

在定時器上調用stop(),然後使信號自行發射。你可以通過繼承QTimer和調用你的QTimer子類中的方法發出信號做到這一點:

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

...但如果你不想去打擾做一個子類的,更簡單的方法是一個信號添加到自己的類和信號連接到QTimer對象的超時()信號(這樣做只是一次課程):

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

...然後你停止和火法就可以做像這樣:

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

注意:這個答案適用於Qt 4,但* not *對於Qt 5 - 以上都不會像Qt 5中寫的那樣工作。Qt 5通過需要一個你不能構造的參數來間接私有很多信號... – 2018-02-08 02:35:40

+0

我錯了:第二種方法 - 連接到'QTimer :: timeout ' - 應該使用Qt 5中的Qt 4連接語法,因爲moc隱藏了簽名中的'QPrivateSignal',並且不需要它在信號時隙連接中傳遞。該值在'qt_static_metacall'中生成,並非來自調用信號。不過,它不適用於Qt 5連接語法。 – 2018-02-08 17:01:21

9

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,商務部生成QTimer::qt_static_metacall構建私有參數我們:

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

您也可以使計時器充當如果它通過發送計時器事件超時:

這兩種方法都適用於Qt 4和d的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而不重新輸入上述代碼,則必須使用下面的ChattyTimerthe solution from another answer。如果你所需要的不是一個計時器,而只是一個立即的單發射信號,那麼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對於QTimer不是Liskov可替換的。這將是一個等待發生的錯誤 - 在很難找到的錯誤類中。

有幾個行爲細節需要注意。這也許表明,改變某些與定時器一樣基本的行爲是非常棘手的 - 您永遠不知道QTimer中的代碼可能會產生明顯正確的假設,但stop()可能會發出超時。將所有這些放在一個不是-的課堂上 - 這真的不是一個好主意!

  1. QTimer一樣,超時事件總是從事件循環中發出。要立即從stop()發出,請設置immediateStopTimeout

  2. 停止時有兩種可能的一般化的isActive行爲(即對比的QTimer):

    • 後立即stop回報爲假,即使最終timeout將在稍後發出,或
    • 指示事件循環是否可以發出超時事件,並且如果最後的timeout信號被延遲,則在stop()之後將保持true

    我選擇了第一個行爲作爲默認行爲。設置activeUntilLastTimeout以選擇第二個行爲。

  3. 有三種可能的一般化每個timerIdremainingTime的停止時的行爲(與那個的QTimer)的:

    • 返回-1isActive()是假的,或者有效的標識符/時間否則(即如下選擇isActive()行爲),
    • 成爲-1後立即stop回報,即使最終timeout將在稍後發出,
    • 返回時超時事件仍可由事件循環發出有效的身份證件/次。

    我選擇了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; } 
}; 
-1

這其實很容易做到這一點,至少在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(); 
} 
+0

當然'app.exit()'隱藏了停止定時器的需要。在大多數應用程序中,計時器不會用於退出應用程序,並且必須明確停止。在任何情況下,事件循環都會提供計時器事件,因此不需要使用'singleShot'間接啓動計時器。來自第二個lambda的代碼可以直接寫在'main()'的主體中。在糾正這兩個缺陷之後,這個答案會更好。 – 2018-02-08 02:39:42

+0

@KubaOber我不明白你關於「需要停止計時器」的觀點。這是一個單發計時器,所以它不會再次啓動。在任何情況下,這與問題沒有任何關係,即在最初指定的時間間隔到期之前如何觸發回調。 – 2018-02-08 05:53:51

+0

這是一個單發計時器,因爲你是這麼做的。這個問題沒有具體說明。另外,我注意到另一個問題:'timer.start()'後面跟'timer.setInterval'是浪費的:它會啓動計時器,然後停下來,然後再次啓動它。你的答案可以概括爲:''timer.setInterval(0); timer.setSingleShot(true);'「將 - 如果定時器是活動的 - 會導致從事件循環立即發出超時事件,並停止定時器然後。也許還應該保留原定時器的屬性,並在以這種方式「停止併發信號通知」定時器之後恢復它們。 – 2018-02-08 16:48:10

相關問題