Search code examples
pythonpyqtpyqt4qthreadqprogressbar

PyQt4 threading properly


I want to make a progressbar which runs on a thread and I want to be able to move the widget during the process:

import sys
from PyQt4.QtGui import QApplication, QMainWindow, QPushButton, QLineEdit, QLabel, QComboBox, QProgressBar, QFileDialog
from PyQt4.QtCore import QSize, pyqtSlot, QCoreApplication, SIGNAL, QThread

class App(QMainWindow):

    def __init__(self):
        super(App, self).__init__()
        self.setGeometry(500, 300, 820, 350)
        self.setWindowTitle("Program")
        self.initUI()

    def initUI(self):

        #Buttons
        btnposx = 30
        btnposy = 50


        self.btn4 = QPushButton('Load', self)
        self.btn4.move(btnposx,btnposy+220)       
        self.connect(self.btn4, SIGNAL("released()"), self.test)

        #ProgressBar
        self.pb = QProgressBar(self)
        self.pb.move(btnposx+150,btnposy+220)
        self.pb.resize(470,27)        

        self.show()

    def load(self, val):
        self.pb.setValue(val)

    def test(self):

        self.workThread = WorkThread()
        self.connect( self.workThread, SIGNAL('pb_update'), self.load)
        self.workThread.start()

class WorkThread(QThread):

    def __init__(self):
        super(WorkThread, self).__init__()
        QThread.__init__(self)

    def __del__(self):
        self.wait()

    @pyqtSlot()    
    def run(self):
        val = 0
        l = range(1000000)
        for i in l:
            if i < len(l):
                val += 100/len(l)           
                self.emit(SIGNAL('pb_update'), val)
        return

if __name__ == '__main__':
    app = QApplication(sys.argv)
    ex = App()
    ex.show()
    sys.exit(app.exec_())

So far this works, but it does very poorly. The Widget does barely run on my machine, when I try to move it during the process. Is there a way to make this work better so that the Widget doesn't lag or stop responding?


Solution

  • The improvements that your code can have are the following:

    • Use the new connection style between signals and slots
    • You must leave a little time for the secondary thread to send the information to the primary thread.
    • You must indicate the type of connection, in your case Qt::QueuedConnection.
    • Use pyqtSlot decorator.
    • You only have to emit the signal when it is necessary, in your case when the whole value of the value changes since the QProgressBar does not recognize floating.

    import sys
    from PyQt4.QtGui import QApplication, QMainWindow, QPushButton, QLineEdit, QLabel, QComboBox, QProgressBar, QFileDialog
    from PyQt4.QtCore import QSize, pyqtSlot, pyqtSignal, QThread, Qt
    
    class App(QMainWindow):
    
        def __init__(self):
            super(App, self).__init__()
            self.setGeometry(500, 300, 820, 350)
            self.setWindowTitle("Program")
            self.initUI()
    
        def initUI(self):
    
            #Buttons
            btnposx = 30
            btnposy = 50
    
    
            self.btn4 = QPushButton('Load', self)
            self.btn4.move(btnposx,btnposy+220)       
            self.btn4.released.connect(self.test)
    
            #ProgressBar
            self.pb = QProgressBar(self)
            self.pb.move(btnposx+150,btnposy+220)
            self.pb.resize(470,27)        
    
            self.show()
    
        @pyqtSlot(int)
        def load(self, val):
            self.pb.setValue(val)
    
        def test(self):
    
            self.workThread = WorkThread()
            self.workThread.pb_update.connect(self.load, Qt.QueuedConnection)
            #self.workThread.pb_update.connect(self.pb.setValue)
            self.workThread.start()
    
    class WorkThread(QThread):
        pb_update = pyqtSignal(float)
        def __init__(self, *args, **kwargs):
            QThread.__init__(self, *args, **kwargs)
            self.value = 0
    
        def __del__(self):
            self.wait()
    
        @pyqtSlot()    
        def run(self):
            val = 0
            l = range(1000000)
            for i in l:
                if i < len(l):
                    val += 100/len(l)
                    int_val = int(val)
                    if int_val != self.value:
                        self.value = int_val     
                        self.pb_update.emit(self.value)
                        QThread.msleep(1)
            return
    
    if __name__ == '__main__':
        app = QApplication(sys.argv)
        ex = App()
        ex.show()
        sys.exit(app.exec_())