为什么 QProgressDialog 总是在应用程序启动时显示?

Why is QProgressDialog always showing at app startup?

在 PyQt GUI 中,我将有几个工作人员(与 QThread 关联的 QObject),每个工作人员有 1 个 QProgressDialog。每一个的寿命都可能与另一个不同。

我天真地做了下面的例子,我在 GUI init 时间创建了所有必要的 QProgressDialog。

但是,在 init 时间定义的每个 QProgressDialog 都会在启动时显示,即使我明确地将可见性设置为 False。我多次尝试使用 QProgressDialog,似乎工作版本都是基于每次我们需要时创建的 new QProgressDialog 实例。

为什么 QProgressDialog 在启动时显示?即时实例化是否更可取? (我不是在寻找意见,但对于来自 Qt 代码或 Qt 文档的正式元素,我可能已经错过了)

various questions dealing with progress dialog/progress bar management,但其中 none 似乎解决了我的问题。 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())

上面的代码总是在应用程序启动时显示 QProgressDialog,即使它们的可见性从未被显式调用。


我尝试了以下变体,效果很好,但我不明白 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

我也尝试覆盖默认的 window show() 但没有成功:

def show(self):
    super(Window, self).show()
    self.progress_dialog_A.hide()
    self.progress_dialog_B.hide()

这是预期的行为,虽然一开始可能不是很清楚,但文档中对此进行了解释。

来自detailed description

it estimates the time the operation will take (based on time for steps), and only shows itself if that estimate is beyond minimumDuration()

然后,minimumDuration()

This property holds the time that must pass before the dialog appears.

If the expected duration of the task is less than the minimumDuration, the dialog will not appear at all. This prevents the dialog popping up for tasks that are quickly over. For tasks that are expected to exceed the minimumDuration, the dialog will pop up after the minimumDuration time or as soon as any progress is set.

If set to 0, the dialog is always shown as soon as any progress is set. The default is 4000 milliseconds.

这是通过内部 QTimer 实现的,它会在给定的 minimumDuration()(默认为 4 秒)超时后立即调用 forceShow()

一个可能的解决方案是在创建对话框实例后立即停止计时器:

self.progress_dialog_A.findChild(QTimer).stop()
self.progress_dialog_B.findChild(QTimer).stop()