我想了解QTimer的操作。我有事情觸發了timeout()信號,但是如果我提前停止計時器,我無法在文檔中找到是否發出了timeout()信號。如果停止QTimer,它會發出一個timeout()信號嗎?
基本上,我怎麼能強制超時()之前,定時器完成計數?只需通過以最小ms增量重新啓動定時器來破解?
myTimer->start(1);
http://qt-project.org/doc/qt-5/qtimer.html#timeout
我想了解QTimer的操作。我有事情觸發了timeout()信號,但是如果我提前停止計時器,我無法在文檔中找到是否發出了timeout()信號。如果停止QTimer,它會發出一個timeout()信號嗎?
基本上,我怎麼能強制超時()之前,定時器完成計數?只需通過以最小ms增量重新啓動定時器來破解?
myTimer->start(1);
http://qt-project.org/doc/qt-5/qtimer.html#timeout
如果停止QTimer,它會發出超時()信號?
號
基本上,如何可以強制超時()定時器結束計數 前?只需通過以最小ms 增量重新啓動計時器來破解?
在定時器上調用stop(),然後使信號自行發射。你可以通過繼承QTimer和調用你的QTimer子類中的方法發出信號做到這一點:
void MyQTimer :: EmitTimeoutSignal() {emit timeout();}
...但如果你不想去打擾做一個子類的,更簡單的方法是一個信號添加到自己的類和信號連接到QTimer對象的超時()信號(這樣做只是一次課程):
connect(this, SIGNAL(MyTimeoutSignal()), myTimer, SIGNAL(timeout()));
...然後你停止和火法就可以做像這樣:
myTimer->stop();
emit MyTimeoutSignal();
注意:這個答案適用於Qt 4,但* not *對於Qt 5 - 以上都不會像Qt 5中寫的那樣工作。Qt 5通過需要一個你不能構造的參數來間接私有很多信號... – 2018-02-08 02:35:40
我錯了:第二種方法 - 連接到'QTimer :: timeout ' - 應該使用Qt 5中的Qt 4連接語法,因爲moc隱藏了簽名中的'QPrivateSignal',並且不需要它在信號時隙連接中傳遞。該值在'qt_static_metacall'中生成,並非來自調用信號。不過,它不適用於Qt 5連接語法。 – 2018-02-08 17:01:21
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
而不重新輸入上述代碼,則必須使用下面的ChattyTimer
或the 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()
可能會發出超時。將所有這些放在一個不是-的課堂上 - 這真的不是一個好主意!
與QTimer
一樣,超時事件總是從事件循環中發出。要立即從stop()
發出,請設置immediateStopTimeout
。
停止時有兩種可能的一般化的isActive
行爲(即對比的QTimer
):
stop
回報爲假,即使最終timeout
將在稍後發出,或timeout
信號被延遲,則在stop()
之後將保持true
。我選擇了第一個行爲作爲默認行爲。設置activeUntilLastTimeout
以選擇第二個行爲。
有三種可能的一般化每個timerId
和remainingTime
的停止時的行爲(與那個的QTimer
)的:
-1
當isActive()
是假的,或者有效的標識符/時間否則(即如下選擇isActive()
行爲),-1
後立即stop
回報,即使最終timeout
將在稍後發出,我選擇了timerId
和remainingTime
的第一個行爲,並且它是不可配置的。
// 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; }
};
這其實很容易做到這一點,至少在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();
}
當然'app.exit()'隱藏了停止定時器的需要。在大多數應用程序中,計時器不會用於退出應用程序,並且必須明確停止。在任何情況下,事件循環都會提供計時器事件,因此不需要使用'singleShot'間接啓動計時器。來自第二個lambda的代碼可以直接寫在'main()'的主體中。在糾正這兩個缺陷之後,這個答案會更好。 – 2018-02-08 02:39:42
@KubaOber我不明白你關於「需要停止計時器」的觀點。這是一個單發計時器,所以它不會再次啓動。在任何情況下,這與問題沒有任何關係,即在最初指定的時間間隔到期之前如何觸發回調。 – 2018-02-08 05:53:51
這是一個單發計時器,因爲你是這麼做的。這個問題沒有具體說明。另外,我注意到另一個問題:'timer.start()'後面跟'timer.setInterval'是浪費的:它會啓動計時器,然後停下來,然後再次啓動它。你的答案可以概括爲:''timer.setInterval(0); timer.setSingleShot(true);'「將 - 如果定時器是活動的 - 會導致從事件循環立即發出超時事件,並停止定時器然後。也許還應該保留原定時器的屬性,並在以這種方式「停止併發信號通知」定時器之後恢復它們。 – 2018-02-08 16:48:10
我很困惑,可能失去了一些東西。如果您手動停止計時器,則還可以手動調用其超時調用的任何函數。我想有些複雜因素,我忽略了什麼是要求? – Erik 2014-09-05 23:54:47
「如果我提早停止計時器,如果發出超時()信號,我無法在文檔中找到。」這就是爲什麼你有Qt Creator。創建一個新項目並輸入測試所需的代碼應該會花費不到60秒的時間:)這就是Qt的力量:) – 2014-09-06 00:47:23
真實的,真@ kubaOber – tarabyte 2014-09-06 03:56:12