web-dev-qa-db-ja.com

Python PySideとプログレスバーのスレッド化

私はこのコードを持っています:

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

プログレスバーを別のスレッドに配置したいので、アプリケーションがフリーズすることはありませんが、その方法が見つからないようです。

誰か助けてもらえますか?

18
Benny

私はあなたが間違っているかもしれないと思います。アプリケーションがフリーズしないように、別のスレッドで実行している作業が必要です。ただし、プログレスバーを更新できるようにする必要もあります。これは、QThreadを使用してワーカークラスを作成することで実現できます。 QThreadsはシグナルを発することができ、UIはそれをリッスンして適切に動作させることができます。

まず、ワーカークラスを作成しましょう。

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

ワーカークラスができたので、それを利用する時が来ました。放出された信号を処理するために、_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'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には、自動的に発行されるいくつかの組み込みシグナルがあります。あなたはそれらを見ることができます、そしてQThreadsについてのより多くの情報 ドキュメントで

29
smont

このようなことには常にマルチスレッドを使用する必要があると考えるのは間違いです。

実行時間の長いタスクを一連の小さなステップに分割できる場合は、保留中のイベントが十分な頻度で処理され、GUIが応答し続けるようにするだけです。これは、次のように processEvents を使用して、メインGUIスレッドwithinから安全に実行できます。

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

単純であることを考えると、マルチスレッドやマルチプロセッシングのようなはるかに重いソリューションを選択する前に、少なくともこの手法を検討する価値は常にあります。

12
ekhumoro