In a PyQt GUI, I will have several workers (QObjects associated to QThread), and 1 QProgressDialog per worker. Each one may have a different lifespan than the other.
I naively made the following example, where I create all necessary QProgressDialog at GUI init time.
However, each QProgressDialog defined at init time shows up at startup, even if I explicitly set visibility to False. I made several attempts to play with QProgressDialog, and it seems that the working versions are all based on a new QProgressDialog instance being created each time we need it.
Why is the QProgressDialog showing at startup ? Is it preferrable to instantiate on-the-fly ? (I am not looking for opinions, but for formal elements coming from either Qt code or Qt doc I may have missed)
There are various questions dealing with progress dialog/progress bar management, but none of them seem to address my question. The PyQt QProgressDialog documentation has no explanation for this, neither has QDialog documentation.
MCVE:
import sys
import time
from PyQt5.QtCore import QThread, Qt, QObject, pyqtSignal
from PyQt5.QtWidgets import (QApplication, QMainWindow, QProgressDialog, QPushButton)
class WorkerA(QObject):
finished = pyqtSignal()
def do_it(self):
time.sleep(5)
self.finished.emit()
class Window(QMainWindow):
def __init__(self, parent=None):
super().__init__(parent)
self.setMinimumSize(250, 250)
self.button = QPushButton(self)
self.button.setText("Start worker A")
self.button.clicked.connect(self.start_worker_A)
self.thread_A = QThread()
self.thread_A.setObjectName("ThreadA")
self.worker_A = WorkerA()
self.progress_dialog_A = QProgressDialog("Work A in progress", None, 0, 0, self)
self.progress_dialog_A.setWindowModality(Qt.ApplicationModal)
self.progress_dialog_A.setCancelButton(None)
self.progress_dialog_A.setWindowTitle("Work A")
self.progress_dialog_A.setVisible(False)
self.progress_dialog_A.hide()
self.thread_B = QThread()
self.thread_B.setObjectName("ThreadB")
self.worker_B = None
self.progress_dialog_B = QProgressDialog("Work B in progress", None, 0, 0, self)
self.progress_dialog_B.setWindowModality(Qt.ApplicationModal)
self.progress_dialog_B.setCancelButton(None)
self.progress_dialog_B.setWindowTitle("Work B")
self.progress_dialog_B.setVisible(False)
self.progress_dialog_B.hide()
def start_worker_A(self):
if not self.thread_A.isRunning():
self.button.setEnabled(False)
self.worker_A.moveToThread(self.thread_A)
self.thread_A.started.connect(self.worker_A.do_it)
self.thread_A.started.connect(self.progress_dialog_A.show)
self.worker_A.finished.connect(self.thread_A.quit)
self.worker_A.finished.connect(self.progress_dialog_A.hide)
self.worker_A.finished.connect(self.enable_button)
self.thread_A.start()
else:
pass
def enable_button(self):
self.button.setEnabled(True)
if __name__ == "__main__":
app = QApplication(sys.argv)
win = Window()
win.show()
sys.exit(app.exec())
The code above always shows the QProgressDialog at app startup, even if their visibility is never explicitly invoked.
I tried the following variant, which works fine, but I don't understand the lifetime logic of QProgressDialog.
class Window(QMainWindow):
def __init__(self, parent=None):
super().__init__(parent)
self.setMinimumSize(250, 250)
self.button = QPushButton(self)
self.button.setText("Start worker A")
self.button.clicked.connect(self.start_worker_A)
self.thread_A = QThread()
self.thread_A.setObjectName("ThreadA")
self.worker_A = WorkerA()
self.progress_dialog_A : QProgressDialog = None
# obviously no further operations here on self.progress_dialog_A
# rest of init as first code sample above
def start_worker_A(self):
if not self.thread_A.isRunning():
self.button.setEnabled(False)
# QProgressDialog instantiated only when needed
# any previous reference to QProgressDialog is garbaged collected ?
# and a new instance created on the fly
self.progress_dialog_A = QProgressDialog("Work A in progress", None, 0, 0, self)
self.progress_dialog_A.setWindowModality(Qt.ApplicationModal)
self.progress_dialog_A.setCancelButton(None)
self.progress_dialog_A.setWindowTitle("Work A")
self.progress_dialog_A.setVisible(False)
self.progress_dialog_A.hide()
self.worker_A.moveToThread(self.thread_A)
self.thread_A.started.connect(self.worker_A.do_it)
self.thread_A.started.connect(self.progress_dialog_A.show)
self.worker_A.finished.connect(self.thread_A.quit)
self.worker_A.finished.connect(self.progress_dialog_A.hide)
self.worker_A.finished.connect(self.enable_button)
self.thread_A.start()
else:
pass
# rest of code unchanged
I also tried overriding the default window show() with no success:
def show(self):
super(Window, self).show()
self.progress_dialog_A.hide()
self.progress_dialog_B.hide()
This is the expected behavior, and while it might not be very clear at first, it's explained in the documentation.
From the detailed description:
Then,
minimumDuration()
:This is achieved by an internal QTimer that calls
forceShow()
as soon as it times out with the givenminimumDuration()
(4 seconds by default).A possible solution is to stop the timer as soon as the dialog instance is created: