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

Minimal requirement to update a QLabel's text and show it



  • I have code (Qt 5.7, all one thread for GUI + backend) which does processing and does not call QApplication::processEvents().

    Until now it just showed a QProgressBar and called setValue() as it goes along. This does get shown to the user (somewhere it must say that QProgressBar does not require an event loop to update visually).

    Now I have decided to add a QLabel to the left, to inform user what "stage" the processing is at. This is to be shown in addition to the QProgressBar.

    What is the "minimum" code I need to write to make the updated QLabel text be shown to the user? I have tried a mix of QLabel's update/hide/show() methods and none of these display the new text. The only thing I've found is QApplication::processEvents(), which does show the new text. But I really didn't want to have to use that if I don't have to.

    Is there a call to just show this QLabel's text, or just process events for that widget or its "paint" event?



  • To update, say, a QLabel's text immediately so that it's shown to the user, without waiting for the main event loop to be re-entered or writing an explicit QApplication::processEvents(), turns out all you have to do is call QWidget::repaint().



  • Hey Jon - have you considered using a QLineEdit instead of a QLabel? Set to readonly, of course.



  • @mzimmers
    I don't see why that would address the issue. This is a repaint question.



  • If I understand your objective, it's an update issue. I was thinking that a change to the progress bar would signal the line edit, which would change its text accordingly. No?


  • Lifetime Qt Champion

    Hi,

    A QProgressBar or a QProgressDialog ? Because the later uses processEvent if modal. See the value property documentation.



  • Hi, what about QProgressBar::setFormat() (http://doc.qt.io/qt-5/qprogressbar.html#format-prop) - you can use it to display text on top of the progressbar. This is not at the left of the progressbar, but it might adress your issue as well.



  • @SGaist
    I am using a QProgressBar as a widget, at the bottom of an overall page (QWidget). It is not some kind of QProgressDialog. But that should be neither here nor there.

    Forget I ever mentioned QProgressBar. This is a question about when an update to the text of a QLabel is supposed to actually be "visible" to the user, when the code does QLabel.setText()s dynamically as it goes along doing something and it does not call QApplication::processEvents() (or hit the event loop). So the widget does not get a repaint event (right?). In that situation, what do I have to do to get the widget to show its new text now to the user? I am finding I must call QApplication::processEvents() for the new text to show, do I have to call that?


  • Lifetime Qt Champion

    If you have a loop or something that does block the event loop, then yes, you have to call processEvents otherwise the paint request queued won't be handled by the event loop.



  • @SGaist
    Thanks, that is indeed how I understood it. And there isn't a way to just cause paint request(s) to be processed? To force an immediate update but not other things? I see QEventLoop::ExcludeUserInputEvents, possible, I had in mind paints and only paints? All I want to do is force this text and only this text to update right now, nothing else, please!


  • Lifetime Qt Champion

    One thing you can try is to add an event filter to your QLabel and ignore everything that's not a paint event.



  • @SGaist
    Yes, but QApplication::processEvents() sprays events all over the place, I'm more worried about what everything else might do than I am about my own widget! I just want my widget to update, or at least nothing but paint events everywhere, period! But I guess you've answered.

    Let me ask one question though: a QProgressBar manages to update its own text/visuals (when I go QProgressBar::setValue()) without doing a QApplication::processEvents(). How does it do that?


  • Lifetime Qt Champion

    If you don't want any event to be handle by the QApplication instance, then you have to do the same things a QProgressDialog: spin your own event loop.



  • @SGaist , or whoever,

    OK, there is something "magic" about QProgressBar which differs from QLabel, and I need someone to explain what it is, please. Basically, a QProgressBar updates visually without my needing to call QApplication::processEvents() but a QLabel does not.

    Consider the following sample, skeleton code:
    Main program:

    if __name__ == '__main__':
        app = QtWidgets.QApplication(sys.argv)
    
        main = Main()
    
        sys.exit(app.exec_())
    

    Main function:

    class Main(QtWidgets.QMainWindow):
        def __init__(self):
            super().__init__()
    
            self.setWindowTitle("Main")
            self.setGeometry(100, 100, 500, 500)
    
            self.centralWidget = QtWidgets.QWidget(self)
            self.setCentralWidget(self.centralWidget)
            self.centralLayout = QtWidgets.QHBoxLayout(self.centralWidget)
    
            self.lblPb = QtWidgets.QLabel()
            self.centralLayout.addWidget(self.lblPb)
            self.lblPb.setText("Initial")
    
            self.pb = QtWidgets.QProgressBar()
            self.centralLayout.addWidget(self.pb)
            self.pb.setRange(0, 10)
            self.pb.setValue(0)
    
            self.show()
    
            QtCore.QTimer.singleShot(1000, self.doProgress)
    

    doProgress() function:

        def doProgress(self):
            import time
            for i in range(10 + 1):
                # update the label text
                self.lblPb.setText(str(i))
    
                # We need *one* of the next two lines
                # to see the updated label text from above
                self.pb.setValue(i)
                # QApplication::processEvents()
    
                # Yes, the next line is *deliberately* a "sleep"
                # It *simulates* doing some processing but *not* calling `processEvents()`
                time.sleep(1)
    

    So, I'm just setting off to call doProgress() after the main window is up & shown. Its job is to update the QProgressBar and the QLabel while in a non-event-processing-loop, to see what is visible.

    Now, what is interesting is that the progressive QLabel.setText() is only visible because we are also calling QProgressBar.setValue(). The progress bar gets visually updated, and so does the label text. But if we remove the call to QProgressBar.setValue() we do not see the label updating. We then have to put in an explicit QApplication::processEvents() to get to see the label's new value.

    So: what is QProgressBar.setValue() doing exactly to cause this, and where is that documented, please, please?



  • Ah ha!!!

    Googling around, I have come across QWidget::repaint(). After calling QLabel::setText(), I can just call QLabel::repaint() and I see the text immediately updated, just as wanted, without having to call QApplication::processEvents() or QProgressBar.setValue().

    I'd still like to know what QProgressBar.setValue() does to allow the QLabel to repaint too, but I seem to have my solution.

    May I ask: I kept asking "how can I force widget repaint without processing events", and nobody drew my attention to QWidget::repaint(). Why is that, please?


  • Lifetime Qt Champion

    Nothing against you, I worked from memory and I had forgotten about the specifics of repaint well update VS repaint.

    What QProgressBar does is that it check if it needs to repaint based on internal logic and calls repaint if it turns out to be the case.

    Just in case, don't forget the warning and recommendation in the repaint documentation.

    Thanks for the reminder :)



  • @SGaist
    Thanks, no problem. I trust you to be a guru :) I just had to understand why I was hitting my head against a brick wall, I believed there just had to be some kind of "repaint now, don't wait for event loop".

    It seems to be ideal for my "force update of text while doing other stuff". Just like the docs say, and yes I am aware of recursion and no I won't be inside another paintEvent!

    For the record, QProgressBar.setValue() must do more than just "repaint itself" (e.g. by calling repaint()). My point is, when I call QProgressBar.setValue() in addition to seeing the progressbar repaint I also see the unrelated QLabel repaint. So it must actually/presumably be doing some kind of processEvents(), allowing other widgets to paint etc. too. It's easy for me to say this, but I'd really like to see the setValue() docs have something to say about this immediate repaint/process events behaviour, as it's "unique" to QProgressBar and can be very relevant... (I know, I know, I need to start getting into raising Qt docs requests!)

    Meanwhile I've got what I need, and I (more or less) understand what's going on, so thank you.



  • To update, say, a QLabel's text immediately so that it's shown to the user, without waiting for the main event loop to be re-entered or writing an explicit QApplication::processEvents(), turns out all you have to do is call QWidget::repaint().


  • Lifetime Qt Champion

    It doesn't, widgets are only responsible for dealing with their own updates, as for what happens to their parents, it's not them to decide. You can see the setValue implementation here.

    What you can also do is submit fixes for the documentation to make it clearer :)



  • @SGaist

    It doesn't

    Now we have a problem! Because I have posted code above which demonstrates that calling QProgressBar.setValue() not only paints itself but also causes my unrelated QLabel to get repainted. If I take out the QProgressBar.setValue() then I have to call either QApplication::processEvents() or QLabel::repaint() to see the altered text in the QLabel. So how do you explain that?? :) I agree I can't see much in the QProgressBar.setValue(), but I've given the code above from a test I did, so what's causing the QLabel to repaint when all I do is call QProgressBar.setValue()?!


  • Lifetime Qt Champion

    My point was that QProgressBar doesn't do anything special with regard to other widgets surrounding it (as you saw from its implementation). It's a border effect, nice in your case, but I wouldn't rely on it.



  • @SGaist said in Minimal requirement to update a QLabel's text and show it:

    It's a border effect

    What does that mean? Are you saying something like: the QProgressBar::repaint() call (in QProgressBar.setValue()) just happens to cause the QLabel to repaint because the widgets are close to each other, there is some overlap?


  • Lifetime Qt Champion

    From a quick look, it goes down to the rendering engine, I haven't read the whole implementation to determine exactly how the repainting of one of the widget triggers an update of the whole "container" widget.


Log in to reply