Important: Please read the Qt Code of Conduct - https://forum.qt.io/topic/113070/qt-code-of-conduct

Using QTimer::singleShot correctly for updating messages on GUI



  • Hi,

    I am creating a GUI application and I do not want to use the status bar or MessageBox for printing info messages to the user. Instead, I am using a QLabel (that can be seen better in the GUI). Whenever I want to print a message for the user, I am using this function (assuiming that "ui->devices" is my QLabel which should print the message):

    void Gui::setStatus(QString statusMessage)
    {
            ui->devices->setText(statusMessage);
            ui->devices->setWordWrap(true);
            QTimer::singleShot(3000, this, [this]{ui->devices->clear();});
    }
    

    So in my case, I am scanning for Bluetooth devices and everytime a Device is found, this function is called with a message like "Found the following device: ..."

    I am using 3 seconds as the waiting time until the message dissapears, but it is clear that if several devices are found within a short time, the message for the respective device will not be displayed on the GUI for a full 3 seconds. The problem is that I expect the very last message to be displayed for exactly 3 seconds, because after that there are no more messages. So for example the device "Test" is the last device that is found, then I expect the message "Found the following device: Test" to appear on the GUI for exactly 3 seconds. Usually it doesn't and sometimes it disappears after less than 2 or 1 second. What exactly could the problem be?


  • Moderators

    What others already said in a nutshell :

    class Gui 
    {
       ...
       QTimer timer;
    };
    
    Gui::Gui(...)
    {
        ...
        timer.setSingleShot(true); //makes it trigger only once per start() call
        connect(&timer, &QTimer::timeout, this, [&](){  ui->devices->clear(); });
    }
    
    void Gui::setStatus(const QString& statusMessage)
    {
        ui->devices->setText(statusMessage);
        ui->devices->setWordWrap(true);
        timer.start(3000); //this will restart the timer if it was already started
    }
    

  • Lifetime Qt Champion

    Hi
    The issue is that you start multiple timers regardless if one is in progress already.
    You could use a QTimer instance instead of singleShot and use
    https://doc.qt.io/qt-5/qtimer.html#remainingTime-prop
    to see if a clear is in progress if it is, then restart so any text set, always get full 3 seconds.



  • @TUStudi
    You are creating multiple single-shot timers. They are distinct, not the same one. So whenever, say, the first-created one fires, it will clear whatever last message happens to be shown.

    In a word, to achieve this you must:

    • either cancel any previous, pending single-shot you created whenever you start a new one.
    • or use a repeating timer (e.g. could be every second) and do your own seconds counting, so that you only clear when 3 seconds have elapsed since the last time you showed the message.


  • @mrjj thank you. I want teh messages to de displayed in real time, so if 3 devices are discovered within 2 seconds, I ant the messages for the first and secodn devices to be overwritten so that the message for the third device is printed at the same time when this device is discovered. So with your solution I assume, every message will last 3 seconds, right? So for example the message for the 3. device is printed after 6 seconds, although it has been discovered e.g. after 2 seconds?

    @JonB : Thank you too. So cancelling the previous, pending single-shot is a good idea for me. But how can this be done, because I have no object, on which I can call stop() (because QTimer:singleShot is static I guess). So, maybe by using setSingleShot? If yes, how can this be used, because in the documentation, when I click on that function, i am navigated to a (wrong) place: setSingleShot


  • Lifetime Qt Champion

    @TUStudi said in Using QTimer::singleShot correctly for updating messages on GUI:

    because I have no object, on which I can call stop() (because QTimer:singleShot is static I guess).

    Then don't use the static method but a QTimer object.


  • Moderators

    What others already said in a nutshell :

    class Gui 
    {
       ...
       QTimer timer;
    };
    
    Gui::Gui(...)
    {
        ...
        timer.setSingleShot(true); //makes it trigger only once per start() call
        connect(&timer, &QTimer::timeout, this, [&](){  ui->devices->clear(); });
    }
    
    void Gui::setStatus(const QString& statusMessage)
    {
        ui->devices->setText(statusMessage);
        ui->devices->setWordWrap(true);
        timer.start(3000); //this will restart the timer if it was already started
    }
    


  • @Chris-Kawa Thank you very much, that worked for me (I actually have 6 QLabels, so I now have 6 timer and so on, but this wokrs).

    Just one more issue (I think, it is not worth it to create an extra thread for that).

    My QLabels have the sizePolicy horizontal: preferred and vertical: fixed and a vertical length of 20.
    When a text is displayed, the QLabel expands so that the elements above the QLabel are also moved upwards and after the text disappears, they go back to their initial place. This also happens if the QLabel has a size of 30 or 40 (which is very large), although only one line is displayed in the QLabel. Any idea why this happens? The elements above the QLabels also have the fixed vertical sizepolicy..


  • Moderators

    @TUStudi said:

    I actually have 6 QLabels, so I now have 6 timer and so on

    Derive a class from QLabel and add that functionality to it. Don't write the same code 6 times!

    My QLabels have the sizePolicy horizontal: preferred and vertical: fixed and a vertical length of 20.
    When a text is displayed, the QLabel expands so that the elements above the QLabel are also moved upwards and after the text disappears, they go back to their initial place.

    Fixed size policy means that widget uses its sizeHint() as the size. If you change the text the size hint also changes, so label grows/shrinks.