2016-02-18 5 views
2

У меня есть основная программа pyqt, которая должна запускать внешнюю программу с аргументами. Я хотел бы использовать QDialog как своего рода монитор состояния, который будет захватывать stdout внешней программы во время ее выполнения и отображать их в текстовом поле внутри QDialog. У меня есть следующий код монитора состояния:Запустите QProcess из 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. Очевидно, что не отображается выдающийся из текущей программы.

Наконец, код, который я собрал, опирается на идеи и уроки многих людей, но я смотрю на это, кажется, что он может распечатать весь выдающийся после завершения программы, но я надеялся, что он отобразит строку по линии, поскольку программа записывает их во время выполнения.

Моего инструмент цепь: Python 64-разрядная версия 2.7.5 и я разрабатываю на Windows 7 поля

('Qt version:', '4.8.5') 
('SIP version:', '4.14.7') 
('PyQt version:', '4.10.2') 

Спасибо за любую помощь.

+1

почему вы дождались его завершения до того, как оно началось? – user3528438

+0

Если вы хотите следить за чем-то, это, как правило, хорошая практика сделать это в отдельном потоке. Это позволяет пользовательскому интерфейсу оставаться интерактивным, в то время как мониторинг выполняется в фоновом режиме. Вы можете использовать сигнал 'QProcess.readyRead', чтобы подключить его к слоту в пользовательском интерфейсе, который будет запускаться каждый раз, когда подпроцесс что-то произведет. – rbaleksandar

+0

@ user3528438, не уверен, что вы имеете в виду, но, как я уже говорил, если я не использую эту waitForFinished, внешняя программа даже не выполнит ... не могли бы вы уточнить, что вы имеете в виду? –

ответ

2

Здесь есть пример, как вы можете это сделать (я использую QWidget, но вы также можете использовать QDialog или что-то еще). Я не использую отдельный поток, потому что пользовательский интерфейс не должен быть интерактивным. Если вы хотите добавить кнопки и т. Д., То вы должны подумать о том, чтобы пойти на старый добрый QThread, работающий по модели QObject, предоставленной Qt.

#!/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 (расширенный диспетчер задач на терминале), который когда-то запускался, не останавливается, если пользователь не хочет его или система не останавливается (сбой, выключение и т. Д.).

Обратите внимание, что вы должны убедиться, что внешний процесс остановлен чистым способом.Это можно сделать внутри __del__ (деструктор) или другой функции, вызванной в конце срока службы данного виджета. То, что я сделал, в основном посылает SIGTERM () по внешнему процессу и как только пройденное заданное количество времени, но процесс все еще работает. Поднимаю ситуацию до SIGKILL (kill).

Код требует больше работы, но этого должно быть достаточно, чтобы дать вам представление о том, как все работает.

Здесь такая же версия приведенного выше кода, но с дополнительной темой. Обратите внимание, что я перенаправляю вывод из внешнего процесса в слот моего рабочего. Вам не обязательно это делать, если вы не хотите работать над этим выходом. Таким образом, вы можете пропустить это и подключить свой технологический сигнал к слоту вашего виджета, который получает его и выводит его содержимое. Обработка выходных данных будет сделана снова в отдельном потоке, так что вы можете пойти расстояние вместо замораживания вашего пользовательского интерфейса (который произойдет, если вы будете следовать

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. Позже реферат run() получил конкретную реализацию, поэтому была устранена необходимость подкласса QThread. О потокобезопасности и сигналах, которые написал @BrendanAbel, отчасти верно. Оно сводится к типу соединения (по умолчанию AutoConnection). Если вы вручную укажете тип соединения, вы можете фактически сделать сигналы небезопасными. Узнайте больше об этом в Qt documentation.

+0

. Я просто попробовал этот« дополнительный поток », он высветил открытие виджета MyQProcess, после чего он почти сразу переходит к __del__, чтобы закончить сам, отсюда и внешний внешний процесс, они связаны с крюком «finished.connect». Таким образом, это не сработает. –

+0

Он работает, и я могу предоставить вам скриншот, если хотите. Пожалуйста, разместите свой 'main()' или везде, где вы создаете свой виджет и показываете его. Как я уже упоминал в своем комментарии по вашему вопросу, я верю, что вы делаете что-то не так со всем исполнением своего приложения. – rbaleksandar

+0

Я не сомневаюсь, что ваша демо-программа работает вообще. У меня есть QMainWindow в качестве основной формы, у которой есть кнопка, на которую я нажимаю, чтобы перейти к функции, где я просто создаю экземпляр MyQProcess при передаче во внешней программе (exe exe) и ее трех параметрах, которые экземпляр MyQProcess передайте их экземпляру «Рабочий» в качестве параметров, которые используются для запуска экземпляра 'QProcess'. Я думаю, что может быть что-то проблематичное с 'QProcess' на окнах, или может быть, что Windows Exe просто не может быть запущена, как это, используя QProcess ... –

0

Вы не можете ждать по процессу и обновите графический интерфейс в той же теме. 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_() 
+0

Пожалуйста, не добавляйте слоты и сигналы к подклассу 'QThread'. Это не так, как это делается в современном Qt. Единственная часть, которая фактически выполняется в отдельном цикле, представляет собой цикл событий «run()». Остальные (слоты и сигналы) являются частью экземпляра 'QThread', который находится в том же потоке, где он был создан. В вашем случае - основной поток. Если вы хотите использовать функциональные возможности слотов, используйте 'QObject', а затем переместите этот' QObject' в отдельный стандартный поток. – rbaleksandar

+0

Сигналы являются потокобезопасными, прекрасно испускать их из 'run'. Не важно, в каком потоке они находятся. Правда, вы могли бы создать отдельный «QObject» с сигналами и передать это как цель регулярному 'QThread', но это необязательно. –

+0

** Слоты ** являются основной проблемой здесь. Слоты RUN внутри одной и той же нити создаются в вашем 'QThread'. Поэтому, если вы добавите какую-то тяжелую обработку, ваш пользовательский интерфейс замерзнет. Пожалуйста, обратитесь к документации Qt, чтобы узнать больше, почему функциональность слотов с добавлением подкласса «QThread» является плохим. – rbaleksandar