Updating Python GUI element from Qthread

2019-02-21 02:01发布

问题:

So I know there are a lot of posts about updating elements in a GUI with Qthread. I did my best to go over these, but still have a question.

I'm trying to create a GUI that runs a method when a button is clicked and that method then starts a new thread. Then that thread emits a signal to the GUI to change the value of the GUI element:

from PySide import QtCore, QtGui
import time

class WorkerThread(QtCore.QThread):
    updateProgress = QtCore.Signal(int)
    def __init__(self, countto):
        QtCore.QThread.__init__(self)
        self.countto = countto

    def run(self):
        i = 0
        while i <= self.countto:
            print(self.countto)
            self.updateProgress.emit(i)
            time.sleep(1)
            i += 1


class Ui_Dialog(object):
    def setupUi(self, Dialog):
        Dialog.setObjectName("Dialog")
        Dialog.resize(400, 300)
        self.progressBar = QtGui.QProgressBar(Dialog)
        self.progressBar.setGeometry(QtCore.QRect(110, 120, 118, 23))
        self.progressBar.setProperty("value", 0)
        self.progressBar.setObjectName("progressBar")
        self.lineEdit = QtGui.QLineEdit(Dialog)
        self.lineEdit.setGeometry(QtCore.QRect(50, 60, 113, 20))
        self.lineEdit.setObjectName("lineEdit")
        self.pushButton = QtGui.QPushButton(Dialog)
        self.pushButton.setGeometry(QtCore.QRect(190, 60, 75, 23))
        self.pushButton.setObjectName("pushButton")

        self.wt = WorkerThread(int)
        self.wt.updateProgress.connect(self.setProgress)

        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.pushButton.connect(QtCore.SIGNAL('clicked()'), self.get_time)

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

    def get_time(self):
        countto = self.lineEdit.text()
        countto = int(countto)
        print(countto)
        self.wt = WorkerThread(countto)
        self.wt.start()
        q = 0
        while q < 5:
            print(countto+2)
            time.sleep(2)
            q += 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_())

This GUI is supposed to run the main thread and do some arbitrary counting to make sure it's working. Then the second thread does some arbitrary counting as well to make sure it's working and then attempts to emit a signal to the GUI to update the progressbar element. I know both threads are running, but I think I'm having a problem receiving the signal because the progressbar isn't updating. I've tried a few things, but now I'm stuck. I think the problem comes up when I try to pass a value to the thread and when I try to create an instance of the thread in the `setupUi' method, only to redefine it later on.

Could anyone help and explain my error in thinking? I'm trying to wrap my head around using signals and slots with threading, so an explanation would be great.

回答1:

There are a few things wrong with your code, however you were pretty close.

The first obvious one is that you have a loop with a time.sleep() in your main thread. The Ui_Dialog.get_time() method runs in the main thread (as it should). You should not have any long running code there. However, the loop with the time.sleep(2) in it is long running code. As it stands now, your GUI locks up because control it not returned to the GUI event loop for 2*countto seconds. Just remove the whole while loop. I don't really know why it is there.

Delete this:

q = 0
while q < 5:
    print(countto+2)
    time.sleep(2)
    q += 1

The next issue comes because you recreate the QThread each time you click the button. As such, for each new object, you need to connect the updateProgress signal in that object to the setProgress slot. So change the code there to:

self.wt = WorkerThread(countto)        
self.wt.updateProgress.connect(self.setProgress)
self.wt.start()

At this point you will see the progress bar update correctly. However, the maximum value of the progress bar is set to 100 by default. So you might want to set the maximum value to countto just before you create the thread. For example:

self.progressBar.setMaximum(countto)
self.wt = WorkerThread(countto)    

Hope that explains things!