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?"