Search code examples
pythonpyqtpyqt5qthreadqprogressbar

How to repeating using Qthread with responsive progressbar


When using python pyqt, I made a Qprogressbar with an updating the number using Q thread. The code shown below is successful in first time, howerver it will crash with doing another run.

This is for Python PyQt5, in Python 3.5 system. And I have try reset the progressbar to 0 but it didn't seem right

This is the call function after I click the load file button, the load file button and progressbar is located the in QtWidgets.Qmainwindows

loading_event_name = QtWidgets.QFileDialog.getOpenFileNames(self, 'CSV File', 'C:\\', '*.csv')
self.loading_progress_bar.reset()
self.loading_thread = QThread()
self.loading_worker = Load_Task_Thread()

self.loading_worker.moveToThread(self.loading_thread)
self.loading_thread.started.connect(self.loading_worker.run)
self.loading_thread.start()
self.loading_worker.Load_taskFinished.connect(self.Load_onFinished)
self.loading_worker.Loading_progressChanged.connect(self.loading_progress_bar.setValue, Qt.QueuedConnection)'''

The first load file thread will work perfectly, but the next click if I want to load new file, software will crash, thread is not updating,progressbar is not updating the number like the first time and keep in zero since I reset it. In debug mode I can not emit any number to the progressbar too:

class Load_Task_Thread(QThread):
  Load_taskFinished = pyqtSignal()
  Loading_progressChanged = pyqtSignal(int)
  def run(self):
    progress = 0
    for file_name_num in range(len(loading_event[0])):
       progress = 90 * file_name_num/len(loading_event_name[0])
       self.Loading_progressChanged.emit(progress)
       1+1.....
    self.Loading_progressChanged.emit(100)
    self.Load_taskFinished.emit()

I expected that no matter how many clicks for selecting the new file, the progress bar can be updated. Or is there any other easy way that can show a progressbar to show the process of loading.


Solution

  • You may have other errors but from the code you provide I observe the following errors:

    • You have created a QThread (Load_Task_Thread) and you have moved it to another QThread, that is unnecessary since it is enough that Load_Task_Thread is a QObject.

    • It is not necessary to create a QThread every time you start a process because a QObject that lives in a thread is enough.

    Considering the above I have implemented the following example:

    from functools import partial
    
    from PyQt5 import QtCore, QtGui, QtWidgets
    
    
    class Load_Task_Worker(QtCore.QObject):
        started = QtCore.pyqtSignal()
        finished = QtCore.pyqtSignal()
        progressChanged = QtCore.pyqtSignal(int)
    
        @QtCore.pyqtSlot(list)
        def run(self, filenames):
            total = len(filenames)
            self.started.emit()
            self.progressChanged.emit(0)
            for i, filename in enumerate(filenames, 1):
                # start process
                # ...
                # end process
                progress = 100 * i // total
                self.progressChanged.emit(progress)
            self.finished.emit()
    
    
    class Widget(QtWidgets.QWidget):
        def __init__(self, parent=None):
            super().__init__(parent)
    
            self.button = QtWidgets.QPushButton("Start Task")
            self.loading_progress_bar = QtWidgets.QProgressBar()
    
            lay = QtWidgets.QVBoxLayout(self)
            lay.addWidget(self.button)
            lay.addWidget(self.loading_progress_bar)
    
            thread = QtCore.QThread(self)
            thread.start()
    
            self.worker = Load_Task_Worker()
            self.worker.moveToThread(thread)
    
            self.button.clicked.connect(self.onClicked)
            self.worker.progressChanged.connect(self.loading_progress_bar.setValue)
            self.worker.finished.connect(self.onFinished)
    
        @QtCore.pyqtSlot()
        def onFinished(self):
            self.button.setEnabled(True)
    
        @QtCore.pyqtSlot()
        def onClicked(self):
            filenames, _ = QtWidgets.QFileDialog.getOpenFileNames(
                self, "CSV File", "C:\\", "*.csv"
            )
            if filenames:
                self.button.setEnabled(False)
                self.loading_progress_bar.reset()
                wrapper = partial(self.worker.run, filenames)
                QtCore.QTimer.singleShot(0, wrapper)
            else:
                print("not selected files")
    
    
    if __name__ == "__main__":
        import sys
    
        app = QtWidgets.QApplication(sys.argv)
        w = Widget()
        w.show()
        sys.exit(app.exec_())