Python PySide и Progress Bar Threading

У меня есть этот код:

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_())

Я хочу иметь индикатор выполнения в отдельном потоке, поэтому он не замерзает приложение, но я не могу найти, как это сделать.

Кто-нибудь может помочь?

Ответы

Ответ 1

Я думаю, вы можете ошибаться. Вам нужна работа, которую вы делаете в отдельном потоке, чтобы она не затормозила приложение. Но вы также хотите обновить индикатор выполнения. Вы можете достичь этого, создав рабочий класс, используя QThread. QThreads могут излучать сигналы, которые ваш пользовательский интерфейс может прослушивать и действовать соответствующим образом.

Сначала создайте свой рабочий класс.

#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 init
    def __init__(self):
        QtCore.QThread.__init__(self)

    #A QThread is run by calling it start() function, which calls this run()
    #function in it 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)

Итак, теперь, когда у вас есть рабочий класс, пришло время его использовать. Вам понадобится создать новую функцию в классе Ui_Dialog для обработки испускаемых сигналов.

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

Пока вы там, вы можете удалить свою функцию progress().

в retranslateUi() вам нужно будет обновить обработчик событий кнопки с

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

к

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

Наконец, в вашей функции setupUI() вам нужно будет создать экземпляр вашего рабочего класса и подключить его к вашей функции setProgress().

До этого:

self.retranslateUi(Dialog)

Добавьте это:

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

Вот окончательный код:

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 init
    def __init__(self):
        QtCore.QThread.__init__(self)

    #A QThread is run by calling it start() function, which calls this run()
    #function in it 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 есть встроенные сигналы, которые автоматически испускаются. Вы можете увидеть их и получить дополнительную информацию о QThreads в документации

Ответ 2

Ошибочно думать, что вам всегда нужно использовать многопоточность для таких вещей.

Если вы можете разбить свою долгосрочную задачу на ряд небольших шагов, все, что вам нужно сделать, это обеспечить, чтобы все ожидающие события обрабатывались достаточно часто, чтобы графический интерфейс оставался отзывчивым. Это можно сделать безопасно изнутри основного потока GUI, используя processEvents, например:

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

Учитывая его простоту, он всегда стоит, по крайней мере, рассматривать этот метод, прежде чем выбирать гораздо более тяжелое решение, такое как многопоточность или многопроцессорная обработка.