1

EDIT 3: When I first asked this question I thought it was because the QTimer was only firing once; however it turned out to be because I was observing it based on its remainingTime() member. It turns out the real problem is that remainingTime() counts down to 0 only once (and the timeout() signal is indeed firing multiple times).


I have a single-threaded Qt application, which has a timer that needs to be repeatedly called. I also have a progress bar to display how much time is left in the main timer, and the progress bar is updated every 40ms by another timer.

Currently, the main timer is set to 15 seconds (15 x 1000 = 15000 ms), but the timer only fires its QTimer::timeout() event once. After that, remainingTime() always returns 0, even though isActive() returns true. (isSingleShot() returns false, as well.)


Below is the relevant code:

MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::MainWindow),
    interval_capture(15.00),
    timer_capture(new QTimer(this)),
    timer_updateProgress(new QTimer(this)),
    isRecording(false)
{
    ui->setupUi(this);

    QObject::connect(timer_updateProgress,  &QTimer::timeout,
                     this,                  &MainWindow::update_progress);

    // I had to add the following line to force the timer to be called repeatedly
    //QObject::connect(timer_capture,           &QTimer::timeout,
    //               timer_capture,         static_cast<void (QTimer::*)()>(&QTimer::start));

    timer_capture->setInterval(static_cast<int>(round(interval_capture * 1000)));
    timer_capture->setSingleShot(false);
    timer_updateProgress->setInterval(40);
    timer_updateProgress->setSingleShot(false);
}

// -snip-

void MainWindow::update_progress()
{
    // The math does work
    double time_passed =
            static_cast<double>(interval_capture) -
            static_cast<double>(timer_capture->remainingTime())/1000.0;
    double fraction_passed =
            time_passed / static_cast<double>(interval_capture);
    int percentage = static_cast<int>(round(100 * fraction_passed));
    ui->progressBar_timer->setValue(percentage);

    // I only get an output of "tick: 0" after the first timeout
    if (timer_capture->isSingleShot() || !timer_capture->isActive()) {
        ui->progressBar_timer->setFormat("it ded");
    } else {
        ui->progressBar_timer->setFormat("tick: " + QString::number(timer_capture->remainingTime()));
    }
}

// -snip-

void MainWindow::on_button_start_clicked()
{
    isRecording = !isRecording;
    switch (isRecording) {
        case true :
            ui->button_start->setIcon(QIcon(":/icons/stop.png"));
            timer_capture->start();
            timer_updateProgress->start();
            break;
        case false :
            ui->button_start->setIcon(QIcon(":/icons/record.png"));
            timer_capture->stop();
            timer_updateProgress->stop();
            break;
    }
}

The weird thing is, I know that timer_updateProgress does work (because I can see the progress bar updating), and it's initialized in basically the same way...


EDIT: To clarify, I am sure that all my other logic is functioning correctly, because I can see in the debugger that:

  • timer_capture.isSingleShot() is false
  • timer_capture.remainingTime() is 0
  • timer_capture.isActive() is true
  • all results of calculations are correct (e.g. time_passed)

And I can also see that the countdown works once and then stops.

EDIT 2: I added the following code to the end of update_progress() to further illustrate what is happening:

    qDebug() << "Active: " << timer_capture->isActive();
    qDebug() << "Single shot: " << timer_capture->isSingleShot();
    qDebug() << "Remaining ticks:" << timer_capture->remainingTime() <<
                " / " << timer_capture->interval() << "\n";

The output I'm getting is:

Active:  true
Single shot:  false
Remaining ticks: 1496  /  15000 

Active:  true
Single shot:  false
Remaining ticks: 996  /  15000 

Active:  true
Single shot:  false
Remaining ticks: 494  /  15000 

Active:  true
Single shot:  false
Remaining ticks: 3  /  15000 

Active:  true
Single shot:  false
Remaining ticks: 0  /  15000 

Active:  true
Single shot:  false
Remaining ticks: 0  /  15000 

(ad infinitum)

6
  • You have commented out the slot for timer_capture->timeout(); maybe QTimer doesn't actually do anything if there is no slot connected to it? If it starts working when you do connect a slot, it looks like that might be what is happening. Commented Aug 24, 2015 at 2:32
  • It doesn't work when the slot is simply connected to &MainWindow::update_progress() (uncommented) but it does work when it's connected to &QTimer::start() (as expected). I tried connecting it to update_progress() because that would make it basically identical to timer_updateProgress.
    – Ernest3.14
    Commented Aug 24, 2015 at 2:35
  • I see. Could you simplify by having just one timer, timer_updateProgress? Just have it increment the progress bar one tick every timeout. Are you running in a debugger or just observing the UI changing? Running in a debugger would help a lot, so you could see the results of your calculations. Commented Aug 24, 2015 at 2:56
  • I've done both, since timers don't seem to pause on breakpoints in the debugger :P The reason I have two timers is because the length of the main timer can change at any time, so I thought it would make more sense to have it separate (there's other logic handling all that). I'll edit my question to clarify a bit.
    – Ernest3.14
    Commented Aug 24, 2015 at 3:01
  • 1
    The title of the question is misleading: the timer doesn't fire once, it fires as it should. The remainingTime is broken, but that doesn't affect how the timer fires. Funny thing is that I've always used a separate QElapsedTimer to see how much time has really elapsed since the timer was started - my code had long life in pre-Qt5-times, so I've never run into that. But I thank you for having me notice the remainingTime() member, it'll be useful once it works :) Commented Aug 24, 2015 at 16:46

1 Answer 1

2

After some more digging around, I think I've found the answer. The problem is that even if a QTimer is not set up to be singleShot, remainingTime acts like it does. The timeout() signal is still emitted on schedule, but after the first signal, remainingTime stays at 0 while everything else happens.

I was able to test this by QObject::connecting the timeout() signal to a new slot, which pinted to the debug stream when called. This showed that timeout() was being called repeatedly while the remainingTime stayed 0.

Since this behavior doesn't seem intentional, I'm going to try filing a bug report with Qt. (For the record, I'm using Qt 5.5.0.)


EDIT: It appears that this is a known bug (46940) and will be fixed in Qt 5.5.1.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.