For*_*ent 3 python pyqt qprocess qthread
我有一个主要的pyqt程序,需要运行带有参数的外部程序。我想将QDialog用作一种状态监视器,它将在执行外部程序时捕获外部程序的标准输出,并将其显示在QDialog内的文本框中。我有以下状态监视器代码:
class ProgressInfo(QtGui.QDialog):
def __init__(self, cmd, args, parent=None):
#super(self).__init__(parent)
QDialog.__init__(self)
self.ui = Ui_Dialog()
self.ui.setupUi(self)
self.cmd = cmd
self.args = args
self.keepGoing = True
layout = QFormLayout()
layout.setContentsMargins(10, 10, 10, 10)
self.output = QtGui.QTextEdit()
layout.addRow(self.output)
layout.addRow(self.ui.buttonBox)
self.setLayout(layout)
self.ext_process = QtCore.QProcess(self)
#self.ext_process.waitForFinished(-1)
#self.ext_process.waitForStarted()
#self.ext_process.readyRead.connect(self.dataReady)
self.ext_process.started.connect(self.open)
self.ext_process.readyReadStandardOutput.connect(self.dataReady)
self.ext_process.finished.connect(self.onProcessFinished)
self.ext_process.start(self.cmd, self.args)
def dataReady(self):
cursor = self.output.textCursor()
cursor.movePosition(cursor.End)
cursor.insertText(str(self.ext_process.readAll()))
self.output.ensureCursorVisible()
def onProcessFinished(self):
cursor = self.output.textCursor()
cursor.movePosition(cursor.End)
#cursor.insertText(str(self.ext_process.readAll()))
cursor.insertText(str(self.ext_process.readAllStandardOutput()))
self.output.ensureCursorVisible()
Run Code Online (Sandbox Code Playgroud)
然后,我将使用以下命令实例化该命令:
prog='C:/Program Files (x86)/My Program/Execute.exe'
margs=['D:/Data/Input1.txt', 'D:/Data/Input2.txt']
status = ProgressInfo(prog, margs, self)
Run Code Online (Sandbox Code Playgroud)
到目前为止,这还没有奏效。
问题1:仅在取消注释waitForFinished(-1)行之后,外部程序才会运行。
问题2。QDialog框仅在一瞬间打开,然后消失。
问题3.显然,没有显示正在运行的程序的任何异常。
最后,我编写的代码借鉴了很多人的想法和教训,但是我看一下,看来它只能在程序完成后打印出所有突出的记录,但我希望它将逐行显示为程序正在运行时将其写出。
我的工具链:Python 64位版本2.7.5,我正在Windows 7机器上进行开发
('Qt version:', '4.8.5')
('SIP version:', '4.14.7')
('PyQt version:', '4.10.2')
Run Code Online (Sandbox Code Playgroud)
谢谢你的帮助。
这是一个示例,您可以如何做(我使用,QWidget
但您也可以使用QDialog
或其他方法)。我不使用单独的线程,因为UI不需要是交互式的。如果您想添加按钮等,那么您应该考虑使用Qt提供QThread
的QObject
模型。
#!/usr/bin/python
from PyQt4.QtGui import *
from PyQt4.QtCore import *
import sys
class MyQProcess(QWidget):
def __init__(self):
super(QWidget, self).__init__()
# Add the UI components (here we use a QTextEdit to display the stdout from the process)
layout = QVBoxLayout()
self.edit = QTextEdit()
self.edit.setWindowTitle("QTextEdit Standard Output Redirection")
layout.addWidget(self.edit)
self.setLayout(layout)
# Add the process and start it
self.process = QProcess()
self.setupProcess()
# Show the widget
self.show()
def setupProcess(self):
# Set the channels
self.process.setProcessChannelMode(QProcess.MergedChannels)
# Connect the signal readyReadStandardOutput to the slot of the widget
self.process.readyReadStandardOutput.connect(self.readStdOutput)
# Run the process with a given command
self.process.start("df -h")
def __del__(self):
# If QApplication is closed attempt to kill the process
self.process.terminate()
# Wait for Xms and then elevate the situation to terminate
if not self.process.waitForFinished(10000):
self.process.kill()
@pyqtSlot()
def readStdOutput(self):
# Every time the process has something to output we attach it to the QTextEdit
self.edit.append(QString(self.process.readAllStandardOutput()))
def main():
app = QApplication(sys.argv)
w = MyQProcess()
return app.exec_()
if __name__ == '__main__':
main()
Run Code Online (Sandbox Code Playgroud)
注意,我正在使用的命令(df -h
)运行一次(这是一个Linux命令,显示硬盘上的磁盘使用情况),然后结束。您也可以将其替换为Execute.exe
无限期运行。我已经使用htop
(基于终端的高级任务管理器)对其进行了测试,该应用程序一旦启动就不会停止,除非用户希望它停止或系统停止(崩溃,关机等)。
请注意,您必须确保以干净的方式停止外部进程。这可以在内部__del__
(析构函数)中完成,也可以在给定窗口小部件寿命结束时调用另一个函数来完成。我所做的基本上是向外部进程发送SIGTERM
(terminate
),并且在经过给定时间但该进程仍在运行时,我将情况提升为SIGKILL
(kill
)。
代码显然需要更多的工作,但足以让您了解事情的工作原理。
这是上面代码的相同版本,但有一个额外的线程。请注意,我将输出从外部进程重定向到我的工作器中的插槽。除非您想在该输出上工作,否则不必这样做。因此,您可以跳过此步骤,并将过程信号连接到小部件上的插槽,该插槽将其接收并输出其内容。输出的处理将在单独的线程中再次完成,因此您可以走一段距离而不是冻结UI(如果遵循
from PyQt4.QtGui import *
from PyQt4.QtCore import *
import sys
class Worker(QObject):
sendOutput = pyqtSignal(QString)
def __init__(self):
super(Worker, self).__init__()
self.process = QProcess()
self.setupProcess()
def __del__(self):
self.process.terminate()
if not self.process.waitForFinished(10000):
self.process.kill()
def setupProcess(self):
self.process.setProcessChannelMode(QProcess.MergedChannels)
self.process.readyReadStandardOutput.connect(self.readStdOutput)
self.process.start("htop")
@pyqtSlot()
def readStdOutput(self):
output = QString(self.process.readAllStandardOutput())
# Do some extra processing of the output here if required
# ...
self.sendOutput.emit(output)
class MyQProcess(QWidget):
def __init__(self):
super(QWidget, self).__init__()
layout = QVBoxLayout()
self.edit = QTextEdit()
self.thread = QThread()
self.setupConnections()
self.edit.setWindowTitle("QTextEdit Standard Output Redirection")
layout.addWidget(self.edit)
self.setLayout(layout)
self.show()
def setupConnections(self):
self.worker = Worker()
self.thread.finished.connect(self.worker.deleteLater)
self.worker.sendOutput.connect(self.showOutput)
self.worker.moveToThread(self.thread)
self.thread.start()
def __del__(self):
if self.thread.isRunning():
self.thread.quit()
# Do some extra checking if thread has finished or not here if you want to
#Define Slot Here
@pyqtSlot(QString)
def showOutput(self, output):
#self.edit.clear()
self.edit.append(output)
def main():
app = QApplication(sys.argv)
w = MyQProcess()
return app.exec_()
if __name__ == '__main__':
main()
Run Code Online (Sandbox Code Playgroud)
进一步说明:正如我在答案的注释部分中告诉@BrendanAbel的那样,使用slot with的问题QThread
是slot与QThread
实例本身具有相同的线程亲和力(=它们所属的线程)。QThread
是从哪里创建的。我唯一重复的是在一个线程中运行的唯一QThread
事件是它的事件循环,由表示QThread.run()
。如果您在Internet上浏览,您会发现不建议使用这种方式(除非您真的非常了解必须继承QThread
),因为自从Qt 4的早期版本run()
是抽象的以来 ,您就必须继承QThread
才能使用一个QThread
。后来的摘要run()
得到了具体的实现,因此QThread
消除了子类化的需要。关于线程安全并发出@BrendanAbel所写的内容只是部分正确。它归结为连接类型(默认为AutoConnection
)。如果您手动指定连接类型,则实际上可能会使信号不安全。在Qt文档中阅读有关此内容的更多信息。