1

I have developed an app (my first in PyQt) that downloads a log file from a networked printer and it is working. The only piece I am now missing is figuring out a way to let the user know the download is happening because the log files can potentially be quite large and take several minutes.

I have tried using the status bar's showMessage() method but nothing shows up until after the download has completed. I realize now this is probably because my event handler is blocking any UI updates. So my question is in two parts:

  1. Is there a way to force the UI update in the middle of an event handler?
  2. If this is not possible, is there a better way to show some sort of feedback to say "something is happening, don't click that button again"?

Thank you.

The relevant parts of my code (yes, I used qt-designer):

class Ui_MainWindow(object):
  def setupUi(self, MainWindow):
    MainWindow.setObjectName("MainWindow")
    ...
    self.pushButton = QtWidgets.QPushButton(self.layoutWidget)
    self.pushButton.setFlat(False)
    self.pushButton.setObjectName("pushButton")
    ...
    self.statusbar = QtWidgets.QStatusBar(MainWindow)
    self.statusbar.setObjectName("statusbar")

def do_dl():
  session = login(device, id, pw)
  if session:
    ui.statusbar.showMessage("Beginning download ...")
    download(session)
  ...

if __name__ == "__main__":
  import sys
  app = QtWidgets.QApplication(sys.argv)
  MainWindow = QtWidgets.QMainWindow()
  ui = Ui_MainWindow()
  ui.setupUi(MainWindow)
  ui.pushButton.clicked.connect(do_dl)

  MainWindow.show()
  sys.exit(app.exec_())
Colin Wu
  • 611
  • 7
  • 25
  • 1
    Have you considered making it multithreaded? See [Qt4 example](https://nikolak.com/pyqt-threading-tutorial/) and [Qt5 example](https://www.zeolearn.com/magazine/getting-started-guis-with-python-pyqt-qthread-class) – Alan Apr 21 '20 at 20:28
  • I did think of that but wasn't sure I was prepared to go there yet. When I did some similar thing in Tk/Tcl it was possible to call an "update" function to refresh the GUI, so I was hoping it would be that simple. :) I will take a look at your links, though. Thanks. – Colin Wu Apr 21 '20 at 20:33
  • I was writing a nice answer and this got closed... -_- – Guimoute Apr 21 '20 at 20:34
  • Sorry, what got closed? The question? It wasn't by me... – Colin Wu Apr 21 '20 at 20:35
  • @ColinWu I guess I can share it that way: https://ctxt.io/2/AABAe60PFA Basically you have two functions, one that does the length operation in a thread, and the main one which prepares the terrain, starts the thread, updates the GUI while the thread is working, then processes the work. – Guimoute Apr 21 '20 at 20:45
  • @Guimoute Use processEvents is a "nice answer"? Well it is not because it will cause silent bugs that are the worst because they are difficult to track. – eyllanesc Apr 21 '20 at 20:47
  • @ColinWu Each technology establishes its own rules. Tk/Tcl is not Qt so don't pretend that the functionalities exist in both technologies. In the case of Qt this recommends executing the time consuming tasks in another thread and sending the information through signals. – eyllanesc Apr 21 '20 at 20:50
  • @eyllanesc Why silent bugs? I never had an issue with this. – Guimoute Apr 21 '20 at 20:50
  • @Guimoute In your trivial example you are assuming that the time consuming task can be divided into n tasks each consuming 0.1 second, and what if the task cannot be divided? Or does the best division make each task consume at least 10 seconds? In those cases even with processEvents the GUI will not be updated. – eyllanesc Apr 21 '20 at 20:54
  • @Guimoute Also see https://stackoverflow.com/questions/2150966/should-i-use-qcoreapplicationprocessevents-or-qapplicationprocessevents – eyllanesc Apr 21 '20 at 20:56
  • @eyllanesc Sorry but I do not understand the issue. Say we replace the whole time consuming method with simply `time.sleep(60)`, the main thread will still print "tick" just fine and all widgets will still be able to work in the meantime. We cannot extract meaningful info to send for example progression signals if the task cannot be divided, but that sounds like a distinct problem. – Guimoute Apr 21 '20 at 21:01
  • @eyllanesc I made no assertion that the same (update) functionality should exist in Qt as well. I only *hoped* it would be. – Colin Wu Apr 21 '20 at 21:08
  • Thanks for all the feedback, guys. While the two questions that @eyllanesc cited as being related, and the reason for his closing this question so quickly, were not related to my original question (but are related to the solution), they, along with the discussion here, have given me a direction to investigate and learn. I think QThread is what I need... :) – Colin Wu Apr 22 '20 at 02:29
  • 1
    Just for completeness, I found this [tutorial](https://www.learnpyqt.com/courses/concurrent-execution/multithreading-pyqt-applications-qthreadpool/) that is immensely helpful. @Guimoute, it also explains why processEvents() is not a good thing to use. – Colin Wu Apr 22 '20 at 17:17
  • @ColinWu Thank you for the link, it's interesting. I am still hoping for an answer from eyllanesc because this article only explains how using `QApplication.processEvents()` is bad **when used next to the lengthy work**. The solution I wrote has a worker thread which never calls that... – Guimoute Apr 22 '20 at 17:33
  • @Guimoute, Yes, your solution would actually work for my case because I'm only interested in showing a status message. My understanding, from the tutorial, is that the danger comes, not so much from a lengthy "worker" process, but from other events that happened to come along just as the "worker" has been suspended and the main thread had to handle those too. Then the effect on your suspended "worker" becomes unpredictable. I guess having a lengthy worker results in more chances of this happening. Does that make sense? – Colin Wu Apr 22 '20 at 21:24
  • @ColinWu Yep it does make sense. If the worker thread is working on something quite specific and has been fed the right inputs at the beginning, I think we're safe. If it's working on something dynamic and that something can be changed by signals, that would be where the unpredictability happens. § To give you some context, the way I use the example I wrote for you is a work thread that performs measurements and a main thread that draws a graph and processes events to see if I clicked a "stop" button basically. So your case with a status message is quite close I think. – Guimoute Apr 22 '20 at 22:23
  • I'm glad I learned about threading in Qt, but sure seems like an awful lot of work just to show a simple status message! – Colin Wu Apr 23 '20 at 16:20

0 Answers0