Python 从用作进度监视器的QDialog中启动QProcess

Python 从用作进度监视器的QDialog中启动QProcess,python,pyqt,qthread,qprocess,Python,Pyqt,Qthread,Qprocess,我有一个主pyqt程序,需要运行带有参数的外部程序。我想使用QDialog作为一种状态监视器,它可以在外部程序执行时捕获其标准输出,并将它们显示在QDialog中的文本框中。我有以下状态监视器代码: class ProgressInfo(QtGui.QDialog): def __init__(self, cmd, args, parent=None): #super(self).__init__(parent) QDialog.__init__(self

我有一个主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 box仅在瞬间打开,然后消失

问题3。显然,运行程序中没有突出显示

最后,我编写的代码借鉴了很多人的想法和经验教训,但我看了看,它似乎只能在程序完成后打印出所有突出的内容,但我希望它能在程序运行时逐行显示出来

我的工具链: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
。但是
信号
是线程安全的,因此您可以使用一个信号将监控线程的输出发送回主线程,主线程中的
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不需要交互。如果你想添加按钮等等,那么你应该考虑去好的旧代码> QTox运行QT.< /P>提供的<代码> QObjult模型。
#!/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
)运行一次(它是一个Linux命令,显示硬盘上的磁盘使用情况),然后就结束了。您还可以将其替换为可以无限期运行的
Execute.exe
。我已经用
htop
(一种基于终端的高级任务管理器)对它进行了测试,它一旦启动就不会停止,除非用户希望它停止或系统停止(崩溃、关机等)

请注意,您必须确保以干净的方式停止外部进程。这可以在
\uu del\uu
(析构函数)或在给定小部件生命周期结束时调用的另一个函数中完成。我所做的基本上是将一个
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()
进一步澄清
正如我在@BrendanAbel回答的评论部分中所说的,将插槽与
QThread
一起使用的问题是,插槽具有相同的线程关联(=它们所属的线程)作为
QThread
实例本身,它是创建
QThread
的同一个线程。当涉及
QThread
时,唯一一个在单独线程中运行的事情是它的事件循环,由
QThread.run()表示
。如果你在互联网上浏览,你会发现这种做法是不可取的(除非你真的,真的知道你必须子类化
QThread
),因为Qt 4的早期版本
run()
是抽象的,为了使用
QThread
,必须对
QThread子类化
得到了一个具体的实现,因此不再需要子类化
QThread
。关于线程安全和@BrendanAbel编写的信号只部分正确。它归结为连接类型(默认值为
自动连接
)。如果手动指定连接类型,实际上可能会使信号线程不安全。请在中阅读有关此问题的详细信息。

为什么要在事件启动前等待它完成?如果要监视某个内容,通常最好在单独的线程中执行此操作。这允许UI在监视时保持交互g是在后台完成的。您可以使用
QProcess.readyRead
信号将其连接到UI中的一个插槽,该插槽将在每次子进程生成某些内容时被触发。@user3528438,不确定您的意思,但正如我刚才所说的,如果我不使用waitForFinished行,外部程序甚至不会执行……可以吗你能详细说明你的意思吗?@rbaleksandar,是的,我同意,根据python文档,QProcess是异步执行的,不是吗?那么,你能详细说明一下吗?@ForComment.On(2):这很可能是因为您没有保留对对话框的引用,因此在对话框显示后会立即对其进行垃圾收集。请避免向子类添加插槽和信号