簡體   English   中英

PyQt5:使用 QThread 彈出進度條

[英]PyQt5: pop-up progressbar using QThread

如何在彈出窗口中實現一個進度條,該窗口通過QThread監視來自所謂的 Worker 類(即時間/CPU 消耗任務)的運行函數的進度?

我已經檢查了無數示例和教程,但進度條顯示在彈出窗口中的事實似乎使一切變得更加困難。 我相信我想要的是一件相當簡單的事情,但我一直在失敗,而且我的想法也用完了。

我有一個我想要實現的例子,它基於這個答案

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()以便在每次迭代時有效更新進度條。 以這種方式做事顯然是次優的。 它仍然是我現在想要實現的更好的選擇嗎?

如果我遺漏了一些明顯的東西,或者已經在某處得到了回答(重復),請原諒:我無法找到這個問題的答案。 非常感謝您提前。

解釋:

要理解這個問題,你必須知道以下幾點:

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 中報告的許多錯誤都涉及變量的范圍,因此我建議您分析每個變量應該是多少,例如,如果您希望一個對象與類相同,則創建該變量是類的一個屬性,如果你只在方法中使用它,那么它只是一個局部變量,等等。

基於eyllanesc 的回答,這是一個工作代碼的樣子:

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_())

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM