我试图理解QTimer的操作。 我触发了超时((信号,但是如果我提前停止计时器,我无法在文档中找到是否发出超时((信号。
基本上,如何在计时器完成计数之前强制超时((? 只是通过以最小毫秒增量重新启动计时器来破解?
myTimer->start(1);
http://qt-project.org/doc/qt-5/qtimer.html#timeout
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
的类中是个好主意 - 它真的不是!
与
QTimer
一样,超时事件总是从事件循环中发出。要让它立即从stop()
发出,请设置immediateStopTimeout
。停止时
isActive
行为有两种可能的概括(与QTimer
相比(:stop
返回后立即变为 false,即使稍后会发出最终timeout
,或者- 指示事件循环是否可以发出超时事件,如果最终
timeout
信号延迟,则在stop()
后将保持true
。
我选择第一个行为作为默认行为。设置
activeUntilLastTimeout
以选择第二个行为。
对于停止时
timerId
和remainingTime
的行为(相对于QTimer
的行为(,有三种可能的概括:- 当
isActive()
为 false 时返回-1
,否则返回有效的标识符/时间(即遵循选定的isActive()
行为(, - 在
stop
返回后立即变得-1
,即使最终的timeout
稍后会发出, - 每当事件循环仍可能发出超时事件时,返回有效的 ID/时间。
我为
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; }
};
如果你停止一个QTimer,它会发出超时((信号吗?
不。
基本上,如何在计时器完成之前强制超时(( 计数?只需以最小的毫秒重新启动计时器即可破解 增加?
在计时器上调用 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();
}