Pause and Resume a QTimer
-
I wrote a Timer class that adds pause and resume functionality to QTimer. I've found this useful for making games that employ a pause button.
Pausing saves the remaining time and stops the timer. Resuming runs the timer once with the remaining time as its interval. Upon timeout, the old interval and singleShot properties are restored, and the timer starts if singleShot was false.
Usage:
... Timer *generateEnemyTimer = new Timer(this, 200); ... ... generateEnemyTimer->start(); ... void Game::togglePause() { gamePaused = !gamePaused; if (gamePaused) { generateEnemyTimer->pause(); } else { generateEnemyTimer->resume(); } }
timer.h
#ifndef TIMER_H #define TIMER_H #include <QTimer> class Timer : public QTimer { public: Timer(QObject *parent = nullptr, int interval = 0, bool singleShot = false); void pause(); void resume(); void changeInterval(int newInterval); void changeSingleShot(bool newSingleShot); private: int remaining; int oldInterval; bool singleShot; void reset(); }; #endif // TIMER_H
timer.cpp
#include "timer.h" #include "QtCore/qdebug.h" Timer::Timer(QObject *parent, int interval, bool singleShot) : QTimer(parent), oldInterval(interval), singleShot(singleShot) { setInterval(interval); setSingleShot(singleShot); } // use instead of setInterval() void Timer::changeInterval(int newInterval) { oldInterval = newInterval; setInterval(newInterval); } // use instead of setSingleShot() void Timer::changeSingleShot(bool newSingleShot) { singleShot = newSingleShot; setSingleShot(newSingleShot); } // save remaining time void Timer::pause() { // qDebug() << "pause"; remaining = remainingTime(); stop(); } // run for remaining time void Timer::resume() { if (remaining < 0) return; // qDebug() << "resume: " << remaining << " ms remaining"; setInterval(remaining); setSingleShot(true); QObject::connect(this, &QTimer::timeout, this, &Timer::reset); start(); } // return timer to state before pause void Timer::reset() { // qDebug() << "resetting to " << oldInterval << " ms"; setInterval(oldInterval); setSingleShot(singleShot); QObject::disconnect(this, &QTimer::timeout, this, &Timer::reset); if (!singleShot) { start(); } }
-
Hi, welcome to the forum.
Thanks for sharing, but your implementation has pretty serious problem. You're shadowing non-virtual methods
setInterval
andsetSingleShot
. That's not gonna work correctly. Consider this:QTimer* t = new Timer(); t->setInteval(200); // not a virtual call, so QTimer::setInterval is executed, not Timer::setInterval
You don't need to override those methods, store these values or modify timer's settings at all. You can simply call this in
resume()
QTimer::singleShot(remaining, this, &QTimer::start);
and it will resume the timer after the remaining time.
Btw. If you want a single shot connection you can pass Qt::SingleShotConnection to
QObject::connect
, so that you don't have to disconnect it manually in the slot. -
@Chris-Kawa
Qt::SingleShotConnection is a good one. But, This flag was introduced in Qt 6.0. -
@Chris-Kawa Thanks for your response. I modified my implementation based on your recommendation.
The reason I had done it the way I did was because I needed the single shot timer to emit timeout() before starting, but I see now that this can be achieved with timerEvent().
-
@bstone100 said:
I needed the single shot timer to emit timeout() before starting, but I see now that this can be achieved with timerEvent()
You don't even need the event. You can just emit it directly, e.g.
QTimer::singleShot(remaining, this, [this](){ emit timeout(); if (!isSingleShot()) start(); });
Btw. you added an empty constructor to mimic the API of QTimer. Instead of this boilerplate you can just do this:
class Timer : public QTimer { public: using QTimer::QTimer; //This will "import" all the constructors the base class has ...
-
@Chris-Kawa
What's curious is that I can't doemit timeout();
because "timer.cpp:26:21: Too few arguments to function call, expected 1, have 0
qtimer.h:181:10: 'timeout' declared here"Q_SIGNALS: void timeout(QPrivateSignal);
but I can do
emit timeout({});
although I don't think you're supposed to be able to.
-
@bstone100 said:
What's curious is that I can't do emit timeout();
Ah, yes, sorry, timeout is a private signal. Bummer. Private signals are public methods so that you can connect to them, but have that "hidden" parameter, so only the class instance can call it.
but I can do emit timeout({});
I guess you can do that, but it's a bit of a dirty cheat, because you're using a quirk of the language to instantiate an object of a private struct
QTimer::QPrivateSignal
via implicit conversion from an initializer list.
Well, the next best thing would be an event, although calling the handler directly is not a nice thing to do. You can rather sendEvent() to itself, so that users can install a filter if they want. -
@Chris-Kawa You know what I just realized... the QTimer::singleShot method doesn't even work because you won't know the remaining time if you pause again during the single shot.