我有一个GUI,需要执行需要一些时间的工作,我想展示这项工作的进度,类似于以下内容:
import sys
import time
from PyQt4 import QtGui, QtCore
class MyProgress(QtGui.QWidget):
def __init__(self, parent=None):
QtGui.QWidget.__init__(self, parent)
# start loop with signal
self.button = QtGui.QPushButton('loop', self)
self.connect(self.button, QtCore.SIGNAL('clicked()'), self.loop)
# test button
self.test_button = QtGui.QPushButton('test')
self.connect(self.test_button, QtCore.SIGNAL('clicked()'), self.test)
self.pbar = QtGui.QProgressBar(self)
self.pbar.setMinimum(0)
self.pbar.setMaximum(100)
# layout
vbox = QtGui.QVBoxLayout()
vbox.addWidget(self.test_button)
vbox.addWidget(self.button)
vbox.addWidget(self.pbar)
self.setLayout(vbox)
self.show()
def update(self):
self.pbar.setValue(self.pbar.value() + 1)
def loop(self):
for step in range(100):
self.update()
print step
time.sleep(1)
def test(self):
if self.test_button.text() == 'test':
self.test_button.setText('ok')
else:
self.test_button.setText('test')
app = QtGui.QApplication(sys.argv)
view = MyProgress()
view.loop() # call loop directly to check whether view is displayed
sys.exit(app.exec_())
当我执行代码时,调用loop
方法并打印出值并更新进度条。但是view
窗口小部件在执行loop
期间将被阻止,虽然这对我的应用程序来说很好,但它对Ubuntu来说并不好看。所以我决定将工作转移到这样一个单独的线程:
import sys
import time
from PyQt4 import QtGui, QtCore
class Worker(QtCore.QObject):
def __init__(self, parent=None):
QtCore.QObject.__init__(self, parent)
def loop(self):
for step in range(10):
print step
time.sleep(1)
class MyProgress(QtGui.QWidget):
def __init__(self, parent=None):
QtGui.QWidget.__init__(self, parent)
# test button
self.test_button = QtGui.QPushButton('test')
self.connect(self.test_button, QtCore.SIGNAL('clicked()'), self.test)
self.pbar = QtGui.QProgressBar(self)
self.pbar.setMinimum(0)
self.pbar.setMaximum(100)
# layout
vbox = QtGui.QVBoxLayout()
vbox.addWidget(self.test_button)
vbox.addWidget(self.pbar)
self.setLayout(vbox)
self.show()
def test(self):
if self.test_button.text() == 'test':
self.test_button.setText('ok')
else:
self.test_button.setText('test')
app = QtGui.QApplication(sys.argv)
view = MyProgress()
work = Worker()
thread = QtCore.QThread()
work.moveToThread(thread)
# app.connect(thread, QtCore.SIGNAL('started()'), work.loop) # alternative
thread.start()
work.loop() # not called if thread started() connected to loop
sys.exit(app.exec_())
当我运行此版本的脚本时,循环开始运行(步骤显示在终端中),但view
小部件未显示。这是我不能完全遵循的第一件事。因为与此前版本的唯一区别在于循环在不同的对象中运行,但view
小部件之前已创建,因此应该显示(就像前一个脚本的情况一样)。
然而,当我将信号started()
从thread
连接到loop
的{{1}}函数时,worker
永远不会执行,尽管我启动了线程(在这种情况我没有在loop
上拨打loop
。另一方面,显示worker
让我认为它取决于view
是否被调用。但是,在app.exec_()
上调用loop
的脚本的第一个版本中,它显示了小部件,但它无法到达view
。
有谁知道这里发生了什么,可以解释如何执行app.exec_()
(在一个单独的线程中)而不冻结loop
?
编辑:如果我添加view
,应用程序会立即退出,而不会执行thread.finished.connect(app.exit)
。我查看了this answer的第二个版本,这与我的基本相同。但在这两种情况下,它都能立即完成工作而不执行所需的方法,我无法真正发现原因。
答案 0 :(得分:0)
这个例子不起作用,因为工作线程和GUI线程之间的通信都是单向的。
跨线程通信通常使用信号完成,因为这是一种简单的方法,可以确保一切都是以线程安全的方式异步完成的。 Qt通过将信号包装为事件来实现这一点,这意味着必须运行事件循环才能使一切正常工作。
要修复您的示例,请使用线程的started
信号告诉工作人员开始工作,然后定期从工作人员发出自定义信号,告诉GUI更新进度条: / p>
class Worker(QtCore.QObject):
valueChanged = QtCore.pyqtSignal(int)
def loop(self):
for step in range(0, 10):
print step
time.sleep(1)
self.valueChanged.emit((step + 1) * 10)
...
thread = QtCore.QThread()
work.moveToThread(thread)
thread.started.connect(work.loop)
work.valueChanged.connect(view.pbar.setValue)
thread.start()
sys.exit(app.exec_())