我正在尝试了解QTimer的操作。我有事情触发了timeout()信号,但如果我提前停止计时器,我无法在文档中找到是否发出了timeout()信号。
基本上,如何在计时器完成计数之前强制执行timeout()?只需通过以最小ms增量重新启动计时器来进行攻击?
myTimer->start(1);
答案 0 :(得分:10)
在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
。我们断言这个事实。如果您希望支持从与同一计时器的emitTimeoutAndStop
信号绑定的代码中调用timeout
而不重新输入所述代码,则必须使用下面的ChattyTimer
或the solution from another answer。< / p>
如果您只需要一个计时器,但只是一个即时的单发射信号,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
的课堂中是个好主意 - 它确实不是!
与QTimer
一样,超时事件始终从事件循环中发出。要立即从stop()
发出,请设置immediateStopTimeout
。
停止时isActive
行为可能有两种概括(与QTimer
的行为相比):
stop
返回后立即变为假,即使最后timeout
将在稍后发出,或true
信号,则stop()
后将保持timeout
。我选择第一个行为作为默认行为。设置activeUntilLastTimeout
以选择第二种行为。
timerId
和remainingTime
在停止时的行为可能有三种概括(与QTimer
的行为相比):
-1
为false时返回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; }
};
答案 1 :(得分:4)
如果停止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();
答案 2 :(得分:-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();
}