PyQt5: pop-up progressbar using QThread

前端 未结 2 1549
臣服心动
臣服心动 2021-01-25 02:24

How can I implement a progress bar in a pop-up window that monitors the progress of a running function from a so-called Worker class (i.e. time

相关标签:
2条回答
  • 2021-01-25 02:52

    Explanation:

    To understand the problem you must know that the following:

    self.main_window_button.clicked.connect(PopUpProgressB)
    

    Equivalent to:

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

    Where it is observed that when pressing the button a PopUpProgressB object is created that does not have a life cycle just like the execution of the "foo" function that is practically instantaneous so the popup will be shown and hidden in a very short time.

    Solution:

    The idea is that the popup has a scope that allows it to have a life cycle large enough to show the progress for it should be made to the class attribute popup object.

    # ...
    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)
    # ...

    And so that it doesn't show you must remove the call to the show() method of PopUpProgressB:

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

    Since I already explained the failure of your problem I will answer your questions:

    1. Why does it crash? When the popup object is deleted, the created QThread is also deleted but Qt accesses no longer allocated memory (core dumped) causing the application to close without throwing any exceptions.

    2. Why does it work during debugging? Many IDEs like PyCharm do not handle Qt errors, so IMHO recommends that when they have such errors they execute their code in the terminal/CMD, for example when I execute your code I obtained:

      QThread: Destroyed while thread is still running
      Aborted (core dumped)
      
    3. Should I just give up and implement the progress bar (anchored) in the main window of the app? No.

    4. I already implemented a similar thing in the past but without threading: within the loop of the worker function (i.e. CPU-consuming function) I had to add QApplication.processEvents() so that at each iteration the progressbar was effectively updated. It is apparently suboptimal to do things this way. Is it still a better alternative to what I am trying to achieve now? Do not use QApplication::processEvents() if there are better alternatives, in this case the threads is the best since it makes the main thread less busy.


    Finally, many of the errors that beginners report in Qt refer to the scope of the variables so I recommend you analyze how much it should be for each variable, for example if you want an object to live the same as the class then make that variable is an attribute of the class, if instead you only use it in a method then it is only a local variable, etc.

    0 讨论(0)
  • 2021-01-25 03:04

    Based on eyllanesc's answer, this is how a working code could look:

    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_())
    
    0 讨论(0)
提交回复
热议问题