3

MY APOLOGIES:

THIS APPEARS TO BE A DUPLICATE OF: PyQt - running a loop inside GUI

WHICH HAS A GOOD SOLUTION AND TUTORIAL LINK.

My Setup:

OS: Windows 10 ver1903

Python: 3.7.4

PyQt5: 5.13.0

My Problem:

PyQt5 is not updating the statusBar consistently. I'm seeing this problem in a larger application. I wrote this debug app to try to identify the problem more clearly, and it has reproduced:

import sys, time
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *

class statusdemo(QMainWindow):
   def __init__(self, parent = None):
      super(statusdemo, self).__init__(parent)

      qpb = QPushButton("Debug")
      qpb.clicked.connect(self.debug)
      self.setCentralWidget(qpb)

      self.statusBar = QStatusBar()
      self.setWindowTitle("QStatusBar Debug")
      self.setStatusBar(self.statusBar)

   def wait(self, duration=2.0):
      print(f"waiting for {duration}")
      tstart = time.time()
      while(True):
         if duration < (time.time() - tstart):
            break

   def debug(self):
      # self.statusBar.showMessage("Checkpoint 001", 2000)
      self.statusBar.showMessage("Checkpoint 001")
      # time.sleep(2)
      self.wait()
      # self.statusBar.showMessage("Checkpoint 002", 2000)
      self.statusBar.showMessage("Checkpoint 002")
      # time.sleep(2)
      self.wait()
      # self.statusBar.showMessage("Checkpoint 003", 2000)
      self.statusBar.showMessage("Checkpoint 003")
      # time.sleep(2)
      self.wait()
      # self.statusBar.showMessage("Completed debug()", 2000)
      self.statusBar.showMessage("Completed debug()")


def main():
   app = QApplication(sys.argv)
   ex = statusdemo()
   ex.show()
   sys.exit(app.exec_())


if __name__ == '__main__':
   main()

EXPECTED: Click "Debug" button and see "Checkpoint ###" printed in the status bar for 2 seconds periodically, ending with an indefinite status bar display of "Completed debug()".

ACTUAL: Click the "Debug" button, see the print statements from wait() in cmd, but I see none of the "Checkpoint ###" updates until "Completed debug()".

  • I have tried using the build-in duration argument statusBar.showMessage() has naitively.
  • I have tried using time.sleep(2).
  • I have tried creating my own wait method that should not suspend the process like sleep does (in case that was getting in the way).

I'm at the point where it seems the next step is to try to leverage the "statusBar.messageChanged" signal, but that feels like too much for something that is supposed to be built in. I figure I'm missing something obvious but can't see it.

LastTigerEyes
  • 647
  • 1
  • 9
  • 21
  • Thanks @ekhumoro , I believe you are 100% correct. I realized this after getting some sleep and talking it over with a friend, too. Thanks everyone for their helpful responses! If you list it as an answer, I can mark it as answering my question. – LastTigerEyes Aug 14 '19 at 18:13

2 Answers2

3

Your example doesn't work because the while-loop in the wait function will block the gui and prevent the status-bar updating. There are several different ways to deal with this problem. If the status-bar updates happen at fixed intervals, you could use a timer and connect a slot to its timeout signal. However, if the slot does some heavy calculations, this could still block the gui - in which case, you should move the calculations into a worker thread, and send the updates back to the main thread via signal (see here for a simple example). Then again, if you only need a quick and dirty method for debugging, you could temporarily force gui updates using process-events. For example, the wait function in your example could be made to work like this:

   def wait(self, duration=2.0):
      qApp.processEvents() # clear current event queue
      time.sleep(duration) # this will block gui updates
ekhumoro
  • 115,249
  • 20
  • 229
  • 336
  • GUI doesn't update until you return from clicked function. statusBar updates don't happen until you return from the ```debug()``` method. Then it processes all the ```statusBar.showMessage()``` components at once, each overwriting the remaining time on the last, so it only shows the last message. I'm working on incorporating the multiprocessing module into a better solution. :) – LastTigerEyes Aug 14 '19 at 19:06
  • Hey, sorry about that. I got mixed up as to your answer and another's. In addition, it looks like I went about asking my question poorly without realizing it. I was using wait/sleep functions as a stand-in for functions with longer run times. If I want my program to not halt, it seems I need to leverage a separate process, best case leveraging PyQt's Signals & Slots. I was ultimately trying to update the StatusBar to let the user know things were happening not just freezing the GUI while it happened. I re-marked it as a solution, and thank you again for your responses! – LastTigerEyes Aug 23 '19 at 16:55
  • 2
    Would like to mention that you actually can update `statusBar` several times inside any function that is invoked after button click with `self.statusBar.repaint()` after `self.statusBar.showMessage()` – v.grabovets Oct 17 '19 at 15:14
0

Try it:

import sys, time
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *

class statusdemo(QMainWindow):
    def __init__(self, parent = None):
        super(statusdemo, self).__init__(parent)

        self.msgs = ["Checkpoint 001", "Checkpoint 002", "Checkpoint 003", "Completed debug()"] # +
        self.n = len(self.msgs)                                                                 # +
        self.i = 0                                                                              # +

        qpb = QPushButton("Debug")
        qpb.clicked.connect(self.debug)
        self.setCentralWidget(qpb)

        self.statusBar = QStatusBar()
        self.setWindowTitle("QStatusBar Debug")
        self.setStatusBar(self.statusBar)

        self.timer = QTimer(self)                                                               # +
        self.timer.setInterval(2000)                             
        self.timer.timeout.connect(self.show_message)

    def show_message(self):
        self.statusBar.showMessage(self.msgs[self.i])
        self.i += 1
        if self.i == self.n: 
            self.timer.stop()
            self.i = 0

    def debug(self):
        self.timer.start()


def main():
    app = QApplication(sys.argv)
    ex = statusdemo()
    ex.show()
    sys.exit(app.exec_())


if __name__ == '__main__':
    main()

enter image description here

S. Nick
  • 12,879
  • 8
  • 25
  • 33