我想了解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