2014-09-05 228 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

相关问题