0

I have a main window where I am collecting information form the use and then passing that to a function. This function does a bunch of interaction with an API and returns a document. My issue is that the user doesn't get any indication that the program is doing anything.

The API function does not return any percentage complete. I have tried creating a QProgressBar and just giving it fake percentages with limited success. What I think I need is some sort of dialog box that can pop up before calling the big function, and then hide it when complete.

I have created a dialog box that basically says "Please wait...working". Unfortunately the program halts while the dialog box is open and will not continue until the dialog box is closed.

I have a class to open the "Wait Window":

class WaitDialog(QtWidgets.QDialog, UiWaitWindow):
    def __init__(self, parent=None):
        QtWidgets.QDialog.__init__(self, parent)
        self.setup_ui(self)

I open the box right before executing the function by:

dlg = WaitWindow()
dlg.exec_()

What is the best way to let the user know that the program is working and not hung?

  • If all you need is a basic busy indicator, the simplest solution would be to [set an override cursor](https://stackoverflow.com/q/8218900/984421), which only requires a few lines of code. You could also use e.g. the status-bar to tell the user what is actually happening. A dialog-box isn't particularly useful in your scenario, since you have no way to calculate the real progress. – ekhumoro Aug 04 '23 at 11:15
  • Yes - I missed the fact that the API doesn't return any percentage complete - this makes my answer part 2 useless. I was imagining the OP had some ability to identify _something_ that could indicate progress (i.e. was looping through a set number of API calls). – tdpu Aug 04 '23 at 16:08
  • I like the solution put forward by @ekhumoro. It is quick and simple. I changed the status bar message and set the cursor to wait. – chad harvey Aug 04 '23 at 21:24

1 Answers1

0

I think there are two issues:

  1. Why is the pop up blocking?

I believe the fact that the pop-up dialog is blocking is due to the use of:

dlg.exec_()

You generally don't want to call this more than once in a PyQT application as you'll spawn another event loop. This event loop blocks until it's exit() method is called (which happens when you close the dialog).

Something is blocking events in the MainWindow while this dialog is active - it's not totally clear from your question what the root cause is, but see the comments below from @musicamante for a better explanation of what might be happening.

You may need to spawn the large computation in another process (i.e. using multiprocessing) if you want to have a responsive UI while the task is running - this will make part (2) more complicated as you'll need to share the progress of the task across processes (but it can be done).

  1. How do I monitor progress of a long running task?

If you want to display the percent done, you can reference a progress variable that you would have to attach to the MainWindow. Then, the pop-up dialog can reference this as self.parent.progress.

The progress variable will have to be updated as you execute your Big Function (i.e. update number of iterations complete in a loop). You can also use that progress variable to change the QProgressBar.value and display the progress accordingly.

If your long running process is able to periodically relinquish control of the GIL (as outlined in the comments) - then you may be able to do this without the use of multiprocessing.

tdpu
  • 400
  • 3
  • 12
  • 1
    "You don't want to call this from within another PyQT application as you'll get another main thread running." -> with all due respect, that sentence is just wrong. 1. there should always be just *one* Qt Application instance and only that one is ever running its event loop, and within the main thread (or, to be precise, the thread in which it was created); 2. calling `QDialog.exec()` (or any of its overrides belonging to classes inherited from it) will *never* create any thread, they will just spawn a further *event loop* (which is a very different thing). – musicamante Aug 03 '23 at 22:47
  • Yes, sloppy choice of words on my part - would the effect of calling `QDialog.exec()` be to block the original event loop until the `QDialog` is destroyed? I was trying to explain why the OP might have experienced the program "apparently halting". – tdpu Aug 03 '23 at 23:05
  • 1
    Qt event loops don't "block" each other. They are just "nested": they will always process the same event queue that belongs to their thread, the nesting level only tells which loop will eventually handle the events; note that this is one of the reasons for which nested loops should theoretically be avoided whenever possible, even if it's normally "safe" to use them. The OP didn't clarify what happens in the meantime, but we can only assume that they are just trying to do some heavy work processing which *is* blocking. While this may not be the case for pure C++ programs, the GIL prevents -> – musicamante Aug 04 '23 at 01:28
  • 1
    -> real concurrency for CPU-bound computations within the same thread, and, unless the thread periodically releases control (ie. via `time.sleep()` or `QThread.sleep()`), that will result in a frozen ("blocked") UI until the thread has completed its task. So, no, `QDialog.exec()` doesn't block event processing, it only prevents basic UI interaction (mouse/keyboard events) for the other widgets since those events will be "eaten" by the top level loop. For instance, if you have a timer in the parent window (like an animation), it will still work even when a modal dialog is run via `exec()`. -> – musicamante Aug 04 '23 at 01:37
  • 1
    -> So, for general usage, `QDialog.exec()` (and related functions, including static functions of QDialog-based classes such as those of QMessageBox) is normally fine and safe. But it should be used with care and awareness, as some events may still "crawl up" to the nested loop structure and be handled even where they are not supposed to. Proper and safe usage of QDialogs should (theoretically) always rely upon `open()`, even if that may result in more complex code; the main advantage of `exec()` -especially in Python- is that it allows easier implementation and simpler garbage collection -> – musicamante Aug 04 '23 at 01:44
  • 1
    -> of the dialog: you can implement a basic dialog within a method and use local functions or statements to process its behavior, then just destroy it with `deleteLater()` if it was created with a parent, or let the GC do that otherwise, and that's because `exec()` is blocking in the current code flow. The crude reality is that this is unsafe, but we normally do it for laziness (me included!). In any case, the point remains: the dialog's event loop will never block the UI (*any* UI, including unrelated windows); if that happens, it's because something else is blocking its processing. – musicamante Aug 04 '23 at 01:51