Как обновить интерфейс с выходом из цикла QProcess без зависания интерфейса? - PullRequest
1 голос
/ 07 ноября 2019

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

Печать стандартного вывода QProcess, только если он содержит подстроку

https://nikolak.com/pyqt-threading-tutorial/

Итак, я попытался объединить эти два ....

import sys
from PySide import QtGui, QtCore

class commandThread(QtCore.QThread):
    def __init__(self):
        QtCore.QThread.__init__(self)
        self.cmdList = None
        self.process = QtCore.QProcess()

    def __del__(self):
        self.wait()

    def command(self):
        # print 'something'
        self.process.start('ping', ['127.0.0.1'])
        processStdout = str(self.process.readAll())
        return processStdout

    def run(self):
        for i in range(3):
            messages = self.command()
            self.emit(QtCore.SIGNAL('dataReady(QString)'), messages)
            # self.sleep(1)

class MainWindow(QtGui.QMainWindow):
    def __init__(self):
        super(MainWindow, self).__init__()
        self.initUI()


    def dataReady(self,outputMessage):
        cursorOutput = self.output.textCursor()
        cursorSummary = self.summary.textCursor()

        cursorOutput.movePosition(cursorOutput.End)
        cursorSummary.movePosition(cursorSummary.End)
        # Update self.output
        cursorOutput.insertText(outputMessage)

        # Update self.summary
        for line in outputMessage.split("\n"):
            if 'TTL' in line:
                cursorSummary.insertText(line)


        self.output.ensureCursorVisible()
        self.summary.ensureCursorVisible()


    def initUI(self):
        layout = QtGui.QHBoxLayout()
        self.runBtn = QtGui.QPushButton('Run')
        self.runBtn.clicked.connect(self.callThread)

        self.output = QtGui.QTextEdit()
        self.summary = QtGui.QTextEdit()

        layout.addWidget(self.runBtn)
        layout.addWidget(self.output)
        layout.addWidget(self.summary)

        centralWidget = QtGui.QWidget()
        centralWidget.setLayout(layout)
        self.setCentralWidget(centralWidget)


        # self.process.started.connect(lambda: self.runBtn.setEnabled(False))
        # self.process.finished.connect(lambda: self.runBtn.setEnabled(True))

    def callThread(self):
        self.runBtn.setEnabled(False)
        self.get_thread = commandThread()
        # print 'this this running?'
        self.connect(self.get_thread, QtCore.SIGNAL("dataReady(QString)"), self.dataReady)
        self.connect(self.get_thread, QtCore.SIGNAL("finished()"), self.done)

    def done(self):
        self.runBtn.setEnabled(True)


def main():
    app = QtGui.QApplication(sys.argv)
    mainWindow = MainWindow()
    mainWindow.show()
    sys.exit(app.exec_())


if __name__ == '__main__':
    main()

Проблема в том, что, как только я нажимаю кнопку «Выполнить»текстовое поле справа, кажется, не заполняется, и я больше не получаю никаких ошибок, поэтому я не уверен, что происходит.

Я также пытался сослаться на эту страницу, но мне кажется, что я уже эмулируючто он описывает ...?

https://www.qtcentre.org/threads/46056-QProcess-in-a-loop-works-but

В конечном счете, я хочу построить главное окно, чтобы отправить серию команд через подпроцесс / QProcess, и немного открытьокно журнала, которое постоянно обновляет информацию о ходе выполнения, отображая вывод консоли. Подобно тому, что вы видите в пакетах установщика ...

Я чувствую, что я так близок к ответу, но так далеко. Кто-нибудь может вмешаться в это?

РЕДАКТИРОВАТЬ: так, чтобы ответить на вопрос eyllanesc, список команд должен быть запущен один после завершения предыдущей, так как команда, которую я планирую использовать, будет очень ЦПинтенсивно, и я не могу иметь более одного процесса. также время завершения каждой команды будет полностью меняться, поэтому я не могу просто произвольно удерживать, как с time.sleep (), поскольку некоторые могут завершаться быстрее / медленнее, чем другие. поэтому в идеале выяснение того, когда процесс завершился, должно запустить другую команду (вот почему у меня есть цикл for в этом примере, чтобы представить это).

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

другая вещь в пользовательском интерфейсе, в идеале я бы добавилчтобы обновить текстовое поле с помощью журналов консоли, я бы хотел, чтобы у него была какая-то метка, которая будет обновляться, которая будет что-то вроде «2 из 10 выполненных заданий». что-то вроде этого:

enter image description here

Было бы неплохо, когда перед обработкой новой команды в текстовое поле можно было добавить собственное сообщение с указаниемкакая команда выполняется ...

1 Ответ

0 голосов
/ 07 ноября 2019

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

Учитывая вышеизложенное, решение имеет вид:

from PySide import QtCore, QtGui


class Task:
    def __init__(self, program, args=None):
        self._program = program
        self._args = args or []

    @property
    def program(self):
        return self._program

    @property
    def args(self):
        return self._args


class SequentialManager(QtCore.QObject):
    started = QtCore.Signal()
    finished = QtCore.Signal()
    progressChanged = QtCore.Signal(int)
    dataChanged = QtCore.Signal(str)

    def __init__(self, parent=None):
        super(SequentialManager, self).__init__(parent)

        self._progress = 0
        self._tasks = []
        self._process = QtCore.QProcess(self)
        self._process.setProcessChannelMode(QtCore.QProcess.MergedChannels)
        self._process.finished.connect(self._on_finished)
        self._process.readyReadStandardOutput.connect(self._on_readyReadStandardOutput)

    def execute(self, tasks):
        self._tasks = iter(tasks)
        self.started.emit()
        self._progress = 0
        self.progressChanged.emit(self._progress)
        self._execute_next()

    def _execute_next(self):
        try:
            task = next(self._tasks)
        except StopIteration:
            return False
        else:
            self._process.start(task.program, task.args)
            return True

    QtCore.Slot()

    def _on_finished(self):
        self._process_task()
        if not self._execute_next():
            self.finished.emit()

    @QtCore.Slot()
    def _on_readyReadStandardOutput(self):
        output = self._process.readAllStandardOutput()
        result = output.data().decode()
        self.dataChanged.emit(result)

    def _process_task(self):
        self._progress += 1
        self.progressChanged.emit(self._progress)


class MainWindow(QtGui.QMainWindow):
    def __init__(self, parent=None):
        super(MainWindow, self).__init__(parent)

        self._button = QtGui.QPushButton("Start")
        self._textedit = QtGui.QTextEdit(readOnly=True)
        self._progressbar = QtGui.QProgressBar()

        central_widget = QtGui.QWidget()
        lay = QtGui.QVBoxLayout(central_widget)
        lay.addWidget(self._button)
        lay.addWidget(self._textedit)
        lay.addWidget(self._progressbar)
        self.setCentralWidget(central_widget)

        self._manager = SequentialManager(self)

        self._manager.progressChanged.connect(self._progressbar.setValue)
        self._manager.dataChanged.connect(self.on_dataChanged)
        self._manager.started.connect(self.on_started)
        self._manager.finished.connect(self.on_finished)
        self._button.clicked.connect(self.on_clicked)

    @QtCore.Slot()
    def on_clicked(self):
        self._progressbar.setFormat("%v/%m")
        self._progressbar.setValue(0)
        tasks = [
            Task("ping", ["8.8.8.8"]),
            Task("ping", ["8.8.8.8"]),
            Task("ping", ["8.8.8.8"]),
        ]
        self._progressbar.setMaximum(len(tasks))
        self._manager.execute(tasks)

    @QtCore.Slot()
    def on_started(self):
        self._button.setEnabled(False)

    @QtCore.Slot()
    def on_finished(self):
        self._button.setEnabled(True)

    @QtCore.Slot(str)
    def on_dataChanged(self, message):
        if message:
            cursor = self._textedit.textCursor()
            cursor.movePosition(QtGui.QTextCursor.End)
            cursor.insertText(message)
            self._textedit.ensureCursorVisible()


if __name__ == "__main__":
    import sys

    app = QtGui.QApplication(sys.argv)
    w = MainWindow()
    w.show()
    sys.exit(app.exec_())
...