Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Python PySide and Progress Bar Threading

I have this code:

from PySide import QtCore, QtGui
import time

class Ui_Dialog(object):
    def setupUi(self, Dialog):
        Dialog.setObjectName("Dialog")
        Dialog.resize(400, 133)
        self.progressBar = QtGui.QProgressBar(Dialog)
        self.progressBar.setGeometry(QtCore.QRect(20, 10, 361, 23))
        self.progressBar.setProperty("value", 24)
        self.progressBar.setObjectName("progressBar")
        self.pushButton = QtGui.QPushButton(Dialog)
        self.pushButton.setGeometry(QtCore.QRect(20, 40, 361, 61))
        self.pushButton.setObjectName("pushButton")

        self.retranslateUi(Dialog)
        QtCore.QMetaObject.connectSlotsByName(Dialog)

    def retranslateUi(self, Dialog):
        Dialog.setWindowTitle(QtGui.QApplication.translate("Dialog", "Dialog", None, QtGui.QApplication.UnicodeUTF8))
        self.pushButton.setText(QtGui.QApplication.translate("Dialog", "PushButton", None, QtGui.QApplication.UnicodeUTF8))
        self.progressBar.setValue(0)
        self.pushButton.clicked.connect(self.progress)

    def progress(self):
        self.progressBar.minimum = 1
        self.progressBar.maximum = 100
        for i in range(1, 101):
            self.progressBar.setValue(i)
            time.sleep(0.1)

if __name__ == "__main__":
    import sys
    app = QtGui.QApplication(sys.argv)
    Dialog = QtGui.QDialog()
    ui = Ui_Dialog()
    ui.setupUi(Dialog)
    Dialog.show()
    sys.exit(app.exec_())

I want to have the progress bar in a separate thread, so it doesn't freeze up the application, but I can't seem to find how to do so.

Can anyone please help?

like image 728
Benny Avatar asked Dec 18 '13 12:12

Benny


2 Answers

I think you may be mistaken. You want the work you're doing in a separate thread so it doesn't freeze the application. But you also want to be able to update the progress bar. You can achieve this by creating a worker class using a QThread. QThreads are able emit signals, which your UI can listen for and act appropriately.

First, let's create your worker class.

#Inherit from QThread
class Worker(QtCore.QThread):

    #This is the signal that will be emitted during the processing.
    #By including int as an argument, it lets the signal know to expect
    #an integer argument when emitting.
    updateProgress = QtCore.Signal(int)

    #You can do any extra things in this init you need, but for this example
    #nothing else needs to be done expect call the super's init
    def __init__(self):
        QtCore.QThread.__init__(self)

    #A QThread is run by calling it's start() function, which calls this run()
    #function in it's own "thread". 
    def run(self):
        #Notice this is the same thing you were doing in your progress() function
        for i in range(1, 101):
            #Emit the signal so it can be received on the UI side.
            self.updateProgress.emit(i)
            time.sleep(0.1)

So now that you have a worker class, it's time to make use of it. You will want to create a new function in your Ui_Dialog class to handle the emitted signals.

def setProgress(self, progress):
    self.progressBar.setValue(progress)

While you're there, you can remove your progress() function.

in retranslateUi() you will want to update the push button event handler from

self.pushButton.clicked.connect(self.progress)

to

self.pushButton.clicked.connect(self.worker.start)

Finally, in your setupUI() function, you will need to create an instance of your worker class and connect it's signal to your setProgress() function.

Before this:

self.retranslateUi(Dialog)

Add this:

self.worker = Worker()
self.worker.updateProgress.connect(self.setProgress)

Here is the final code:

from PySide import QtCore, QtGui
import time


class Ui_Dialog(object):
    def setupUi(self, Dialog):
        Dialog.setObjectName("Dialog")
        Dialog.resize(400, 133)
        self.progressBar = QtGui.QProgressBar(Dialog)
        self.progressBar.setGeometry(QtCore.QRect(20, 10, 361, 23))
        self.progressBar.setProperty("value", 24)
        self.progressBar.setObjectName("progressBar")
        self.pushButton = QtGui.QPushButton(Dialog)
        self.pushButton.setGeometry(QtCore.QRect(20, 40, 361, 61))
        self.pushButton.setObjectName("pushButton")

        self.worker = Worker()
        self.worker.updateProgress.connect(self.setProgress)

        self.retranslateUi(Dialog)
        QtCore.QMetaObject.connectSlotsByName(Dialog)

        self.progressBar.minimum = 1
        self.progressBar.maximum = 100

    def retranslateUi(self, Dialog):
        Dialog.setWindowTitle(QtGui.QApplication.translate("Dialog", "Dialog", None, QtGui.QApplication.UnicodeUTF8))
        self.pushButton.setText(QtGui.QApplication.translate("Dialog", "PushButton", None, QtGui.QApplication.UnicodeUTF8))
        self.progressBar.setValue(0)
        self.pushButton.clicked.connect(self.worker.start)

    def setProgress(self, progress):
        self.progressBar.setValue(progress)

#Inherit from QThread
class Worker(QtCore.QThread):

    #This is the signal that will be emitted during the processing.
    #By including int as an argument, it lets the signal know to expect
    #an integer argument when emitting.
    updateProgress = QtCore.Signal(int)

    #You can do any extra things in this init you need, but for this example
    #nothing else needs to be done expect call the super's init
    def __init__(self):
        QtCore.QThread.__init__(self)

    #A QThread is run by calling it's start() function, which calls this run()
    #function in it's own "thread". 
    def run(self):
        #Notice this is the same thing you were doing in your progress() function
        for i in range(1, 101):
            #Emit the signal so it can be received on the UI side.
            self.updateProgress.emit(i)
            time.sleep(0.1)

if __name__ == "__main__":
    import sys
    app = QtGui.QApplication(sys.argv)
    Dialog = QtGui.QDialog()
    ui = Ui_Dialog()
    ui.setupUi(Dialog)
    Dialog.show()
    sys.exit(app.exec_())

QThreads have some built in signals that are automatically emitted. You can see them, and more information about QThreads in the documentation

like image 89
smont Avatar answered Oct 15 '22 16:10

smont


It's a mistake to think you always need to use multi-threading for things like this.

If you can break up your long-running task into a series of small steps, all you need to do is ensure that any pending events are processed sufficiently often for the GUI to remain responsive. This can be done safely from the within the main GUI thread by using processEvents, like this:

    for i in range(1, 101):
        self.progressBar.setValue(i)
        QtGui.qApp.processEvents()
        time.sleep(0.1)

Given it's simplicity, it's always worth at least considering this technique before opting for a much more heavyweight solution like multi-threading or multi-processing.

like image 20
ekhumoro Avatar answered Oct 15 '22 16:10

ekhumoro