I'm building a simple game that shows a countdown to the user, starting at 1:00 and counting down to zero. As 0:00 is reached, I want to display a message like "time's up!".
I currently have a QTimer and a QTime object (QTime starting at 00:01:00)
QTimer *timer=new QTimer();
QTime time{0,1,0};
In the constructor I'm setting the timer to timeout every 1 second, and it's connected to an event that updates the countdown on screen, which is initially displaying the timer at 1:00:
connect(timer, SIGNAL(timeout()), this, SLOT(updateCountDown()));
timer->start(1000);
ui->countdown->setText(time.toString("m:ss"));
This is the slot being called every 1 second:
void MainWindow::updateCountDown(){
time=time.addSecs(-1);
ui->countDown->setText(time.toString("m:ss"));
}
Now I need to be able to call a new method whenever the QTime reaches 0:00. I'm not very keen on adding an if on the updateCountdown method to check if the QTime is at 0:00 every second. I also thought maybe I could add a second QTimer that times out at 1 minute, but I'm not sure if both QTimer objects will start at the exact same time so the 1 minute timeout will happen exactly when the QTime object is at 0:00.
So is there a way to add a second timeout to the same QTimer object (to timeout once every second to update the countdown on screen and then a second timeout after 1 minute to end the game? I suspect the answer will be "no", but in that case, what would be the best approach? (if none of my options are valid, is there a better way to do it?).
The answer to your first question is no -- QTimer supports a single timeout (or a single specified time-interval, if it's not running in single-shot mode).
The answer to your second question is -- the best approach is the simplest one that can possibly work. Use a single QTimer, and in your updateCountdown() method, include an if statement to do something different when the countdown finally reaches zero. (Btw you don't really need to use a QTime object to represent the countdown; you could just as easily use an int that starts at 60 and is decremented until it reaches 0)
Why is this better/simpler? You could use two QTimer objects instead, but then you have to worry about keeping them in sync -- perhaps that's not a big deal for now, but what happens when you want to add a "Pause" button to your game, or when you want to add a time-bonus that gives the player 10 extra seconds of play time? All of a sudden, your 60-second timer will no longer be doing the right thing, and it will be a pain to stop and restart it correctly in all cases.
If-statements are cheap, and easy to understand/control/debug; so you might as well use one.
Related
I wanted to program an interval-timer which you can use for training. So there have to be a stack widget were the user can input the times for the training- and the rest-rounds and the repetitions followed by a press on a start button which changes the page and starts the first round-countdown shown in a display.
So if the user enter 20 seconds for training, 10 seconds for rest and 3 repetitions the numbers
20 to 0, 10 to 0, 20 to 0, 10 to 0 and 20 to 0
should be displayed one after another.
The problem I ran in:
I tried QTimer and a QThread with a 1 sec-sleep-function and a signal-slot to the gui, but in both options the gui froze.
The use of a QTimer will not block the main window. This is the purpose of timers.
Moreover, you don't need to use threads at all, you only have to start a timer with the desired interval (for example, a tick every 10ms) and connect the timeout() signal to a slot that will process your application behaviour.
In this slot, you just have to handle the countdown and the state change (working time to break time if the number of repetitions is not reached, break time to working time and the finished state).
I have created such an application and it worked well. Maybe I will later make it available on github. If I do it, I would made an edit to my answer to provide the link.
I hope it helps.
I think you have designed the solution in a very complicated way. With no code it's impossible to tell you what went wrong.
If I had to develop a solution for this, it'd be in the form of interconnecting blocks, which can be delay blocks or flow control blocks (child classes of the block parent).
Each block has a next one and a trigger function. A delay block also has a time. A flow control block may have different functionalities, like pointing to a previous block only for x repetitions. You can use a global QTimer when a new delay block is triggered to trigger the next block (connect the timeout signal of the timer to the trigger function of the next block, then start the timer with the current block's time).
For instance, if you wanted to do 3 times 30s exercise, 10s rest, you'd connect two delay blocks with a repeat block.
I have a QTimer triggering periodical events.
I don't want to stop the timer directly because I want to sync some special signals to the last event.
Can I achieve this by setting setSingleShot(true) on this QTimer so that its next shot will be a single shot, and the timer stops after the next shot?
Edit:
It seems to work. but setSingleShot(true) seems not atomic: I sometimes observe 2 shots before the timmer stops.
Yes. Yes you can. In fact, there is no difference between "regular" and single-shot timers - other than this flag. Each time the timer times out, it check the single-shot flag. If it is set, the timer unregisters itself and thus doesn't fire again until you restart it.
I have loop inside my main window code, that is simply changing the colour of some text-boxes on screen.
It is simply for(int i=0; i<200; i++), but I'd like to make every colour change visible to the user, so inside the loop I've tried to add sth like a 10ms pause, so every execution is visible on screen.
I used this:
QTimer t;
t.start(10);
QEventLoop loop;
connect(&t, SIGNAL(timeout()), &loop, SLOT(quit()));
loop.exec();
The problem is, that I'd like to have this 10ms pace constantly, so the whole operation will take about ~2 seconds. Unfortunately, it slows down gradually, so hard, that the last ~20 executions takes even about 1 second each
It looks rather decently when i<20~50, adding more makes it significantly slowing...
I thought about my not-really-brand-new PC, but it is really simple operation to be done, so I don't really think it is because of my slow pc.
I'm assume my approach is wrong
PS. During the execution, ram usage for my app is about ~21MB, and cpu about 20-30%
It is not good way to achieve something. QTimer is enough to this task. For example:
QTimer *t = new QTimer;//without loops and sleeping
connect(t, SIGNAL(timeout()), this, SLOT(someSlot()));
t->start(10);
Create someSlot and in this slot change color and do other tasks. To stop timer after 2 seconds, you can use counter instead of using system time.
void MainWindow::someSlot()
{
//do something
}
Also consider that 10 ms is very very fast, human eyes not able to catch so fast changing. Try to use longer value.
Here is how I use QTimer:
QTimer *timer = new QTimer(this);
connect(timer, SIGNAL(timeout()), this, SLOT(update()));
timer->setInterval(1000);
timer->start();
Program monitors update() function and prints the current time in it. Normally it works as expected, it prints time at every second, but when program starts to process other jobs, there would be some breaks like 5 to 8 secs.
Qt Documentation mentions about accuracy issues like 1 ms, obviously I have another problem. Any ideas ?
QTimer (and all event-base message deliveries) is not interrupt driven. That means you are not guaranteed you will receive the event right when it's sent. The accuracy describes how the event is triggered, not how it's delivered.
If you are not doing threaded process on long job, call QCoreApplication::processEvents() periodically during the long process to ensure your slot gets called.
Your other jobs run for several seconds, and there's no event processing during these. You'd need to thread the jobs in order to get the responsiveness you want.
I try to start timer at specific time like 02:30. Every day it starts at 02.30.
Is it possible? Do you have any idea?
Thank a lot.
QTimer doesn't handle specific times of day natively, but you could use it in conjunction with QDateTime to get what you want. That is, use QDateTime objects to figure out how many seconds are between (right now) and 2:30 (QDateTime::msecsTo() looks particularly appropriate here), then set your QTimer to go off after that many seconds. Repeat as necessary.
Depending on the required resolution, you could use an ordinary QTimer that fires let's say every minute.
In the timerEvent, you could check if you are on the right time (using QDateTime), and trigger the necessary event.
The solution of Jeremy is indeed elegant, but it doesn't take into account the daylight savings time.
To guard against that, you should fire a timer event every hour and check the wall clock.
Calculate the delta to the target, like Jeremy proposes, and if it falls within the coming hour, set a timer to fire, and disable the hourly timer.
If not, just wait for the hourly timer to fire again.
Pseudo code:
Get wall clock time
Calculate difference between target time and wall clock
If difference < 1 hour:
Set timer to fire after difference secs
If this is a repeating event, restart the hourly timer
Else:
Start watch timer to do this calculation again after one hour