如何在运行的QThread中向启动它的PyQt GUI发出信号?

7
我正在尝试理解如何从QThread向启动的Gui界面发送信号。设置:我有一个需要几乎无限运行(或至少长时间运行)的进程(模拟)。在运行时,它执行各种计算,并且其中一些结果必须发送回GUI,该GUI将实时适当地显示它们。我正在使用PyQt进行GUI设计。我最初尝试使用Python的线程模块,然后在阅读了这里和其他地方的多篇文章后切换到QThreads。根据Qt Blog上的这篇文章You're doing it wrong,使用QThread的首选方法是创建QObject,然后将其移动到QThread中。因此,我遵循了这个SO问题Background thread with QThread in PyQt">的建议,并尝试了一个简单的测试应用程序(下面是代码):它打开一个简单的GUI,让您启动后台进程,并应该更新SpinBox中的步骤值。但它不起作用。GUI从未更新。我做错了什么?
import time, sys
from PyQt4.QtCore  import *
from PyQt4.QtGui import * 

class SimulRunner(QObject):
    'Object managing the simulation'

    stepIncreased = pyqtSignal(int, name = 'stepIncreased')
    def __init__(self):
        super(SimulRunner, self).__init__()
        self._step = 0
        self._isRunning = True
        self._maxSteps = 20

    def longRunning(self):
        while self._step  < self._maxSteps  and self._isRunning == True:
            self._step += 1
            self.stepIncreased.emit(self._step)
            time.sleep(0.1)

    def stop(self):
        self._isRunning = False

class SimulationUi(QDialog):
    'PyQt interface'

    def __init__(self):
        super(SimulationUi, self).__init__()

        self.goButton = QPushButton('Go')
        self.stopButton = QPushButton('Stop')
        self.currentStep = QSpinBox()

        self.layout = QHBoxLayout()
        self.layout.addWidget(self.goButton)
        self.layout.addWidget(self.stopButton)
        self.layout.addWidget(self.currentStep)
        self.setLayout(self.layout)

        self.simulRunner = SimulRunner()
        self.simulThread = QThread()
        self.simulRunner.moveToThread(self.simulThread)
        self.simulRunner.stepIncreased.connect(self.currentStep.setValue)


        self.connect(self.stopButton, SIGNAL('clicked()'), self.simulRunner.stop)
        self.connect(self.goButton, SIGNAL('clicked()'), self.simulThread.start)
        self.connect(self.simulRunner,SIGNAL('stepIncreased'), self.currentStep.setValue)


if __name__ == '__main__':
    app = QApplication(sys.argv)
    simul = SimulationUi()
    simul.show()
    sys.exit(app.exec_())
1个回答

7
这里的问题很简单:你的SimulRunner从未收到导致其开始工作的信号。一种方法是将它连接到线程的started信号上。
另外,在Python中,您应该使用新式连接信号的方式。
...
self.simulRunner = SimulRunner()
self.simulThread = QThread()
self.simulRunner.moveToThread(self.simulThread)
self.simulRunner.stepIncreased.connect(self.currentStep.setValue)
self.stopButton.clicked.connect(self.simulRunner.stop)
self.goButton.clicked.connect(self.simulThread.start)
# start the execution loop with the thread:
self.simulThread.started.connect(self.simulRunner.longRunning)
...

1
你说得完全正确,我刚刚意识到了错误。在之前的一个版本中,我从子类化QThread的代码中剪切和粘贴了一部分,而完全忘记了started()方法。感谢你指出了这个问题。 - stefano
这是一个非常有趣的例子。然而,按照这个建议,线程开始了但是停止按钮却不能停止它。有一个工作的例子会很好。也许加一个“开始”按钮可以重新启动整个程序。 - Fabrizio
停止只是发送一个信号,但由于simulThread正在运行longRunning,因此该信号只能在该方法退出后才能被处理,这就是为什么它实际上并没有停止计数器。要这样做,必须从不同的线程(例如主GUI线程)调用“stop”。如果已停止,则需要重置_isRunning标志才能重新启动计数器,但这超出了问题的原始范围。尽管如此,我在这里发布了一个更完整的示例(仍然有一些可以改进的地方...)。 - mata
当我关闭QDialog时,出现了这个错误信息:QThread: Destroyed while thread is still running。这是有问题吗? - GSandro_Strongs
你确定这个程序实际上是按照你想的那样工作的吗?self.simulRunner实际上是由创建它的线程拥有的(因为它是其成员所属类的实例)。你应该创建simulRunner的实例(不带self),连接所有需要连接的内容(包括添加self.simulThread.finished.connect(simulRunner.deleteLater)以正确清理),将其移动到self.simulThread,就这样。至少这是C++ API(和文档)告诉你应该这样做的方式。 - rbaleksandar

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接