Skip to content Skip to sidebar Skip to footer

How To Update UI With Output From QProcess Loop Without The UI Freezing?

I am wanting to have a list of commands being processed through a QProcess and have its output be appended to a textfield I have. I've found a these two pages that seems to do each

Solution 1:

It is not necessary to use threads in this case since QProcess is executed using the event loop. The procedure is to launch a task, wait for the finishes signal, get the result, send the result, and execute the next task until all the tasks are finished. The key to the solution is to use the signals and distribute the tasks with an iterator.

Considering the above, the solution is:

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_())

Post a Comment for "How To Update UI With Output From QProcess Loop Without The UI Freezing?"