从用作进度监视器的 QDialog 中启动 QProcess
Start QProcess from within QDialog that is used as a progress monitor
我有一个主要的 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()
然后我将使用以下命令实例化它:
prog='C:/Program Files (x86)/My Program/Execute.exe'
margs=['D:/Data/Input1.txt', 'D:/Data/Input2.txt']
status = ProgressInfo(prog, margs, self)
到目前为止这还没有奏效。
问题 1:只有在我取消注释 waitForFinished(-1) 行后,外部程序才会 运行。
问题2.QDialog框只是一闪而过,然后就消失了。
问题3,很明显,运行ning程序没有突出显示。
最后,我整理的代码吸取了很多人的想法和教训,但是我看了一下,好像只能在程序结束后打印出所有的standout,但我希望它能显示出来程序在 运行 时间逐行写出它们。
我的工具链:Python 64 位版本 2.7.5,我正在 Windows 7 box
上开发
('Qt version:', '4.8.5')
('SIP version:', '4.14.7')
('PyQt version:', '4.10.2')
感谢您的帮助。
您不能等待进程和在同一个线程中更新 GUI。 GUI 仅在事件循环的每次迭代时更新。如果事件循环卡在等待某个进程,则无法更新 GUI。
解决方案是在单独的线程中监视进程,释放主线程以继续更新 GUI。大多数 GUI 元素不是线程安全的,因此您不能直接从监视线程将输出写入 QTextEdit
。但是 Signals
是线程安全的,因此您可以使用信号将监视线程的输出发送回主线程,主线程中的 QDialog
可以处理并将输出打印到 QTextEdit
import subprocess
import sys
from PyQt4.QtCore import QObject, QThread, pyqtSignal
from PyQt4.QtGui import QDialog, QTextEdit, QVBoxLayout, QPushButton, QApplication
class MyDialog(QDialog):
def __init__(self):
super(MyDialog, self).__init__()
self.ui_lay = QVBoxLayout()
self.setLayout(self.ui_lay)
self.ui_txt = QTextEdit(self)
self.ui_lay.addWidget(self.ui_txt)
self.ui_btn = QPushButton('Ping', self)
self.ui_lay.addWidget(self.ui_btn)
self.thread = MyThread(self)
self.thread.line_printed.connect(self.handle_line)
self.ui_btn.clicked.connect(self.run_thread)
def run_thread(self):
self.thread.start_command('ping google.com')
def handle_line(self, line):
cursor = self.ui_txt.textCursor()
cursor.movePosition(cursor.End)
cursor.insertText(line)
self.ui_txt.ensureCursorVisible()
class MyThread(QThread):
line_printed = pyqtSignal(str)
def __init__(self, parent):
super(MyThread, self).__init__(parent)
self.cmd = None
def start_command(self, cmd):
self.cmd = cmd
self.start()
def run(self):
if self.cmd:
popen = subprocess.Popen(self.cmd, stdout=subprocess.PIPE, shell=True)
lines_iterator = iter(popen.stdout.readline, b"")
for line in lines_iterator:
self.line_printed.emit(line)
if __name__ == '__main__':
app = QApplication(sys.argv)
dlg = MyDialog()
dlg.show()
app.exec_()
这里是一个例子,你可以如何做到这一点(我使用 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()
请注意,我使用的命令 (df -h
) 运行s 一次(这是一个 Linux 命令,显示硬盘上的磁盘使用情况)然后就结束了.您也可以用 Execute.exe
无限期地 运行 替换它。我已经用 htop
(基于终端的高级任务管理器)对其进行了测试,它一旦启动就不会停止,除非用户希望它停止或系统停止(崩溃、关机等)。
请注意,您必须确保以干净的方式停止外部进程。这可以在 __del__
(析构函数)或在给定小部件生命周期结束时调用的另一个函数中完成。我所做的基本上是将 SIGTERM
(terminate
) 发送到外部进程,一旦经过了给定的时间但进程仍在 运行ning 我将情况提升到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()
进一步说明:
正如我在他的回答的评论部分告诉@BrendanAbel 的那样,将插槽与 QThread
一起使用的问题是插槽具有与 QThread
实例相同的线程亲和性(=它们所属的线程)本身,这是创建 QThread
的同一个线程。唯一的 - 我重复 唯一的 - 当涉及到 QThread
时,运行 在单独的线程中是它的事件循环,由 [=28= 表示].如果你在 Internet 上查看,你会发现这种做事方式是不鼓励的(除非你真的,真的知道你必须子类化 QThread
)因为 Qt 4 run()
的早期版本是abstract,你必须继承 QThread
才能使用 QThread
。后来抽象 run()
得到了一个具体的实现,因此子类化 QThread
的需要被删除了。关于线程安全和信号,@BrendanAbel 所写的只是部分正确。它归结为连接类型(默认为 AutoConnection
)。如果您手动指定连接类型,您实际上可能会使信号成为线程不安全的。在 Qt documentation.
中阅读更多相关信息
我有一个主要的 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()
然后我将使用以下命令实例化它:
prog='C:/Program Files (x86)/My Program/Execute.exe'
margs=['D:/Data/Input1.txt', 'D:/Data/Input2.txt']
status = ProgressInfo(prog, margs, self)
到目前为止这还没有奏效。
问题 1:只有在我取消注释 waitForFinished(-1) 行后,外部程序才会 运行。
问题2.QDialog框只是一闪而过,然后就消失了。
问题3,很明显,运行ning程序没有突出显示。
最后,我整理的代码吸取了很多人的想法和教训,但是我看了一下,好像只能在程序结束后打印出所有的standout,但我希望它能显示出来程序在 运行 时间逐行写出它们。
我的工具链:Python 64 位版本 2.7.5,我正在 Windows 7 box
上开发('Qt version:', '4.8.5')
('SIP version:', '4.14.7')
('PyQt version:', '4.10.2')
感谢您的帮助。
您不能等待进程和在同一个线程中更新 GUI。 GUI 仅在事件循环的每次迭代时更新。如果事件循环卡在等待某个进程,则无法更新 GUI。
解决方案是在单独的线程中监视进程,释放主线程以继续更新 GUI。大多数 GUI 元素不是线程安全的,因此您不能直接从监视线程将输出写入 QTextEdit
。但是 Signals
是线程安全的,因此您可以使用信号将监视线程的输出发送回主线程,主线程中的 QDialog
可以处理并将输出打印到 QTextEdit
import subprocess
import sys
from PyQt4.QtCore import QObject, QThread, pyqtSignal
from PyQt4.QtGui import QDialog, QTextEdit, QVBoxLayout, QPushButton, QApplication
class MyDialog(QDialog):
def __init__(self):
super(MyDialog, self).__init__()
self.ui_lay = QVBoxLayout()
self.setLayout(self.ui_lay)
self.ui_txt = QTextEdit(self)
self.ui_lay.addWidget(self.ui_txt)
self.ui_btn = QPushButton('Ping', self)
self.ui_lay.addWidget(self.ui_btn)
self.thread = MyThread(self)
self.thread.line_printed.connect(self.handle_line)
self.ui_btn.clicked.connect(self.run_thread)
def run_thread(self):
self.thread.start_command('ping google.com')
def handle_line(self, line):
cursor = self.ui_txt.textCursor()
cursor.movePosition(cursor.End)
cursor.insertText(line)
self.ui_txt.ensureCursorVisible()
class MyThread(QThread):
line_printed = pyqtSignal(str)
def __init__(self, parent):
super(MyThread, self).__init__(parent)
self.cmd = None
def start_command(self, cmd):
self.cmd = cmd
self.start()
def run(self):
if self.cmd:
popen = subprocess.Popen(self.cmd, stdout=subprocess.PIPE, shell=True)
lines_iterator = iter(popen.stdout.readline, b"")
for line in lines_iterator:
self.line_printed.emit(line)
if __name__ == '__main__':
app = QApplication(sys.argv)
dlg = MyDialog()
dlg.show()
app.exec_()
这里是一个例子,你可以如何做到这一点(我使用 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()
请注意,我使用的命令 (df -h
) 运行s 一次(这是一个 Linux 命令,显示硬盘上的磁盘使用情况)然后就结束了.您也可以用 Execute.exe
无限期地 运行 替换它。我已经用 htop
(基于终端的高级任务管理器)对其进行了测试,它一旦启动就不会停止,除非用户希望它停止或系统停止(崩溃、关机等)。
请注意,您必须确保以干净的方式停止外部进程。这可以在 __del__
(析构函数)或在给定小部件生命周期结束时调用的另一个函数中完成。我所做的基本上是将 SIGTERM
(terminate
) 发送到外部进程,一旦经过了给定的时间但进程仍在 运行ning 我将情况提升到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()
进一步说明:
正如我在他的回答的评论部分告诉@BrendanAbel 的那样,将插槽与 QThread
一起使用的问题是插槽具有与 QThread
实例相同的线程亲和性(=它们所属的线程)本身,这是创建 QThread
的同一个线程。唯一的 - 我重复 唯一的 - 当涉及到 QThread
时,运行 在单独的线程中是它的事件循环,由 [=28= 表示].如果你在 Internet 上查看,你会发现这种做事方式是不鼓励的(除非你真的,真的知道你必须子类化 QThread
)因为 Qt 4 run()
的早期版本是abstract,你必须继承 QThread
才能使用 QThread
。后来抽象 run()
得到了一个具体的实现,因此子类化 QThread
的需要被删除了。关于线程安全和信号,@BrendanAbel 所写的只是部分正确。它归结为连接类型(默认为 AutoConnection
)。如果您手动指定连接类型,您实际上可能会使信号成为线程不安全的。在 Qt documentation.