【问题标题】:PyQt5: pop-up progressbar using QThreadPyQt5:使用 QThread 弹出进度条
【发布时间】:2020-05-14 12:09:34
【问题描述】:

我怎样才能在一个弹出窗口中实现一个进度条来监控所谓的Worker类(即时间/CPU)的正在运行的函数的进度-消费任务)通过QThread

我查看了无数示例和教程,但进度条显示在弹出窗口中的事实似乎让一切变得更加困难。我相信我想要的是一件相当简单的事情,但我一直在做这件事,而且我没有想法。

我有一个基于this answer的示例来说明我想要实现的目标:

import sys
import time
from PyQt5.QtCore import QThread, pyqtSignal, QObject, pyqtSlot
from PyQt5.QtWidgets import QApplication, QPushButton, QWidget, QHBoxLayout, QProgressBar, QVBoxLayout


class MainWindow(QWidget):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("Widget")
        self.h_box = QHBoxLayout(self)
        self.main_window_button = QPushButton("Start")
        self.main_window_button.clicked.connect(PopUpProgressB)
        self.h_box.addWidget(self.main_window_button)
        self.setLayout(self.h_box)
        self.show()


class Worker(QObject):
    finished = pyqtSignal()
    intReady = pyqtSignal(int)

    @pyqtSlot()
    def proc_counter(self):  # A slot takes no params
        for i in range(1, 100):
            time.sleep(1)
            self.intReady.emit(i)

        self.finished.emit()


class PopUpProgressB(QWidget):

    def __init__(self):
        super().__init__()
        self.pbar = QProgressBar(self)
        self.pbar.setGeometry(30, 40, 500, 75)
        self.layout = QVBoxLayout()
        self.layout.addWidget(self.pbar)
        self.setLayout(self.layout)
        self.setGeometry(300, 300, 550, 100)
        self.setWindowTitle('Progress Bar')
        self.show()

        self.obj = Worker()
        self.thread = QThread()
        self.obj.intReady.connect(self.on_count_changed)
        self.obj.moveToThread(self.thread)
        self.obj.finished.connect(self.thread.quit)
        self.thread.started.connect(self.obj.proc_counter)
        self.thread.start()

    def on_count_changed(self, value):
        self.pbar.setValue(value)


if __name__ == '__main__':
    app = QApplication(sys.argv)
    main_window = MainWindow()
    sys.exit(app.exec_())

当我运行后者时(例如在 PyCharm Community 2019.3 中),程序崩溃并且我没有收到任何明确的错误消息。

不过,当我调试它时,它看起来可以工作,因为我能够看到我想要实现的目标:

我有一系列问题:

  1. 为什么会崩溃?
  2. 为什么它在调试过程中起作用?
  3. 我是否应该放弃并实现进度条(锚定) 应用的主窗口?
  4. 我过去已经实现了类似的东西,但没有线程:在工作函数的循环中(即消耗 CPU 的函数),我必须添加 QApplication.processEvents() 以便在每次迭代时有效地更新进度条。以这种方式做事显然不是最理想的。与我现在努力实现的目标相比,它仍然是一个更好的选择吗?

如果我遗漏了一些明显的东西,或者已经在某处得到了回答(重复),请原谅:我无法找到这个问题的答案。非常感谢您。

【问题讨论】:

    标签: python pyqt pyqt5 qthread qprogressbar


    【解决方案1】:

    说明:

    要理解这个问题,你必须知道以下几点:

    self.main_window_button.clicked.connect(PopUpProgressB)
    

    相当于:

    self.main_window_button.clicked.connect(foo)
    # ...
    def foo():
        PopUpProgressB()
    

    据观察,当按下按钮时,会创建一个 PopUpProgressB 对象,该对象没有生命周期,就像实际上是瞬时执行的“foo”函数一样,因此弹出窗口将在很短的时间内显示和隐藏时间。

    解决办法:

    这个想法是弹出窗口有一个范围,允许它有一个足够大的生命周期来显示它应该对类属性弹出对象进行的进度。

    # ...
    self.main_window_button = QPushButton("Start")
    self.popup = PopUpProgressB()
    self.main_window_button.clicked.connect(self.popup.show)
    self.h_box.addWidget(self.main_window_button)
    # ...

    为了不显示,您必须删除对 PopUpProgressB 的 show() 方法的调用:

    class PopUpProgressB(QWidget):
        def __init__(self):
            super().__init__()
            # ...
            self.setWindowTitle('Progress Bar')
            # self.show() # <--- remove this line
            self.obj = Worker()
            # ...

    既然我已经解释了你的问题的失败,我会回答你的问题:

    1. 为什么会崩溃? 当弹出对象被删除时,创建的 QThread 也被删除,但 Qt 不再访问分配的内存(核心转储)导致应用程序关闭而不抛出任何例外。

    2. 为什么它在调试过程中起作用? 许多像 PyCharm 这样的 IDE 不处理 Qt 错误,所以恕我直言,当他们遇到此类错误时,他们在终端/CMD 中执行他们的代码,例如当我执行我获得的代码:

      QThread: Destroyed while thread is still running
      Aborted (core dumped)
      
    3. 我应该放弃并在应用程序的主窗口中实现进度条(锚定)吗?不。

    4. 我过去已经实现了类似的东西,但没有线程:在工作函数的循环中(即 CPU 消耗函数)我必须添加 QApplication.processEvents() 以便在每次迭代时进度条已有效更新。以这种方式做事显然不是最理想的。它仍然是我现在尝试实现的更好的替代方案吗?如果有更好的替代方案,请不要使用 QApplication::processEvents(),在这种情况下线程是最好的,因为它使主线程不那么忙.


    最后,很多初学者在 Qt 中报告的错误都涉及到变量的范围,所以我建议你分析每个变量应该是多少,例如如果你希望一个对象与然后该类使该变量成为该类的属性,如果您只在方法中使用它,那么它只是一个局部变量,等等。

    【讨论】:

    • 您的解决方案完美运行!非常感谢您出色而广泛的回答!我只是觉得很遗憾,我不能每次点击self.main_window_button 时都创建一个class PopUpProgressB 的实例,因为在class MainWindow 中已经有一个实例意味着我必须照顾它“手动”完成进度后,self.popup 隐藏并重置进度,方法是在每次按下按钮时调用的 class PopUpProgressB 中的函数中运行 self.show()self.thread.start()
    • @rubebop 是的,你可以,在你的问题中你没有明确指出这个要求,所以我没有把我的答案集中在它上面,以后有时间我会添加那个部分。
    • 是的,你是对的,我没有指定它。我根据您的解决方案使用适用于我的代码回答了我的问题。不要犹豫,让我知道您的想法:也许有更好或替代的方法来做到这一点。
    • 只有在你有时间的时候,我才会真正感兴趣地看到我提到的工作实现:“每次单击 self.main_window_button 时创建一个类 PopUpProgressB 的实例”。非常感谢您。
    【解决方案2】:

    基于eyllanesc's answer,工作代码如下所示:

    import sys
    import time
    from PyQt5.QtCore import QThread, pyqtSignal, QObject, pyqtSlot
    from PyQt5.QtWidgets import QApplication, QPushButton, QWidget, QHBoxLayout, QProgressBar, QVBoxLayout
    
    
    class MainWindow(QWidget):
        def __init__(self):
            super().__init__()
            self.setWindowTitle("Widget")
            self.h_box = QHBoxLayout(self)
            self.main_window_button = QPushButton("Start")
            self.popup = PopUpProgressB()  # Creating an instance instead as an attribute instead of creating one 
            # everytime the button is pressed 
            self.main_window_button.clicked.connect(self.popup.start_progress)  # To (re)start the progress
            self.h_box.addWidget(self.main_window_button)
            self.setLayout(self.h_box)
            self.show()
    
    
    class Worker(QObject):
        finished = pyqtSignal()
        intReady = pyqtSignal(int)
    
        @pyqtSlot()
        def proc_counter(self):  # A slot takes no params
            for i in range(1, 100):
                time.sleep(0.1)
                self.intReady.emit(i)
    
            self.finished.emit()
    
    
    class PopUpProgressB(QWidget):
    
        def __init__(self):
            super().__init__()
            self.pbar = QProgressBar(self)
            self.pbar.setGeometry(30, 40, 500, 75)
            self.layout = QVBoxLayout()
            self.layout.addWidget(self.pbar)
            self.setLayout(self.layout)
            self.setGeometry(300, 300, 550, 100)
            self.setWindowTitle('Progress Bar')
            # self.show()
    
            self.obj = Worker()
            self.thread = QThread()
            self.obj.intReady.connect(self.on_count_changed)
            self.obj.moveToThread(self.thread)
            self.obj.finished.connect(self.thread.quit)
            self.obj.finished.connect(self.hide)  # To hide the progress bar after the progress is completed
            self.thread.started.connect(self.obj.proc_counter)
            # self.thread.start()  # This was moved to start_progress
    
        def start_progress(self):  # To restart the progress every time
            self.show()
            self.thread.start()
    
        def on_count_changed(self, value):
            self.pbar.setValue(value)
    
    
    if __name__ == '__main__':
        app = QApplication(sys.argv)
        main_window = MainWindow()
        sys.exit(app.exec_())
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2018-12-11
      • 1970-01-01
      • 1970-01-01
      • 2017-11-30
      • 2019-06-24
      • 1970-01-01
      • 2023-03-02
      • 1970-01-01
      相关资源
      最近更新 更多