RealTime output from a subprogram to stdout of a pyQT Widget - python

Hi I have seen there are already many questions on this issue however none of them seems to answer my query .
As per below link i even tried winpexpect as i am using windows , however it dosent seems to e working for me .
Getting realtime output from ffmpeg to be used in progress bar (PyQt4, stdout)
I am running a subprogram with subprocess.Popen and want to see the real time result in a pyQt Widget. Currently it shows the result in the pyQt widget but only after the sub command is finished executing . I need to know if there s a way when we can get the output from a subprocess at real time into the window . See the code below which i tried for all this .
import sys
import os
from PyQt4 import QtGui,QtCore
from threading import Thread
import subprocess
#from winpexpect import winspawn
class EmittingStream(QtCore.QObject):
textWritten = QtCore.pyqtSignal(str)
def write(self, text):
self.textWritten.emit(str(text))
class gui(QtGui.QMainWindow):
def __init__(self):
# ...
super(gui, self).__init__()
# Install the custom output stream
sys.stdout = EmittingStream(textWritten=self.normalOutputWritten)
self.initUI()
def normalOutputWritten(self, text):
cursor = self.textEdit.textCursor()
cursor.movePosition(QtGui.QTextCursor.End)
cursor.insertText(text)
self.textEdit.ensureCursorVisible()
def callProgram(self):
command="ping 127.0.0.1"
#winspawn(command)
py=subprocess.Popen(command.split(),stdout=subprocess.PIPE,stderr=subprocess.PIPE,shell=True)
result,_=py.communicate()
for line in result:
print line
print result
def initUI(self):
self.setGeometry(100,100,300,300)
self.show()
self.textEdit=QtGui.QTextEdit(self)
self.textEdit.show()
self.textEdit.setGeometry(20,40,200,200)
print "changing sys.out"
print "hello"
thread = Thread(target = self.callProgram)
thread.start()
#Function Main Start
def main():
app = QtGui.QApplication(sys.argv)
ui=gui()
sys.exit(app.exec_())
#Function Main END
if __name__ == '__main__':
main()

QProcess is very similar to subprocess, but it's much more convenient to use in (Py)Qt code. Because it utilizes signals/slots. Also, it runs the process asynchronously so you don't have use QThread.
I've modified (and cleaned) your code for QProcess:
import sys
from PyQt4 import QtGui,QtCore
class gui(QtGui.QMainWindow):
def __init__(self):
super(gui, self).__init__()
self.initUI()
def dataReady(self):
cursor = self.output.textCursor()
cursor.movePosition(cursor.End)
cursor.insertText(str(self.process.readAll()))
self.output.ensureCursorVisible()
def callProgram(self):
# run the process
# `start` takes the exec and a list of arguments
self.process.start('ping',['127.0.0.1'])
def initUI(self):
# Layout are better for placing widgets
layout = QtGui.QHBoxLayout()
self.runButton = QtGui.QPushButton('Run')
self.runButton.clicked.connect(self.callProgram)
self.output = QtGui.QTextEdit()
layout.addWidget(self.output)
layout.addWidget(self.runButton)
centralWidget = QtGui.QWidget()
centralWidget.setLayout(layout)
self.setCentralWidget(centralWidget)
# QProcess object for external app
self.process = QtCore.QProcess(self)
# QProcess emits `readyRead` when there is data to be read
self.process.readyRead.connect(self.dataReady)
# Just to prevent accidentally running multiple times
# Disable the button when process starts, and enable it when it finishes
self.process.started.connect(lambda: self.runButton.setEnabled(False))
self.process.finished.connect(lambda: self.runButton.setEnabled(True))
#Function Main Start
def main():
app = QtGui.QApplication(sys.argv)
ui=gui()
ui.show()
sys.exit(app.exec_())
#Function Main END
if __name__ == '__main__':
main()

Here is an adaptation of the accepted answer for PyQt6 and PyQt5.
PyQt6:
from PyQt6 import QtCore, QtWidgets
import sys
# On Windows it looks like cp850 is used for my console. We need it to decode the QByteArray correctly.
# Based on https://forum.qt.io/topic/85064/qbytearray-to-string/2
import ctypes
CP_console = f"cp{ctypes.cdll.kernel32.GetConsoleOutputCP()}"
class gui(QtWidgets.QMainWindow):
def __init__(self):
super(gui, self).__init__()
self.initUI()
def dataReady(self):
cursor = self.output.textCursor()
cursor.movePosition(cursor.MoveOperation.End)
# Here we have to decode the QByteArray
cursor.insertText(
str(self.process.readAll().data().decode(CP_console)))
self.output.ensureCursorVisible()
def callProgram(self):
# run the process
# `start` takes the exec and a list of arguments
self.process.start('ping', ['127.0.0.1'])
def initUI(self):
# Layout are better for placing widgets
layout = QtWidgets.QVBoxLayout()
self.runButton = QtWidgets.QPushButton('Run')
self.runButton.clicked.connect(self.callProgram)
self.output = QtWidgets.QTextEdit()
layout.addWidget(self.output)
layout.addWidget(self.runButton)
centralWidget = QtWidgets.QWidget()
centralWidget.setLayout(layout)
self.setCentralWidget(centralWidget)
# QProcess object for external app
self.process = QtCore.QProcess(self)
# QProcess emits `readyRead` when there is data to be read
self.process.readyRead.connect(self.dataReady)
# Just to prevent accidentally running multiple times
# Disable the button when process starts, and enable it when it finishes
self.process.started.connect(lambda: self.runButton.setEnabled(False))
self.process.finished.connect(lambda: self.runButton.setEnabled(True))
# Function Main Start
def main():
app = QtWidgets.QApplication(sys.argv)
ui = gui()
ui.show()
sys.exit(app.exec())
# Function Main END
if __name__ == '__main__':
main()
PyQt5:
from PyQt5 import QtCore, QtWidgets
import sys
# On Windows it looks like cp850 is used for my console. We need it to decode the QByteArray correctly.
# Based on https://forum.qt.io/topic/85064/qbytearray-to-string/2
import ctypes
CP_console = f"cp{ctypes.cdll.kernel32.GetConsoleOutputCP()}"
class gui(QtWidgets.QMainWindow):
def __init__(self):
super(gui, self).__init__()
self.initUI()
def dataReady(self):
cursor = self.output.textCursor()
cursor.movePosition(cursor.End)
# Here we have to decode the QByteArray
cursor.insertText(
str(self.process.readAll().data().decode(CP_console)))
self.output.ensureCursorVisible()
def callProgram(self):
# run the process
# `start` takes the exec and a list of arguments
self.process.start('ping', ['127.0.0.1'])
def initUI(self):
# Layout are better for placing widgets
layout = QtWidgets.QVBoxLayout()
self.runButton = QtWidgets.QPushButton('Run')
self.runButton.clicked.connect(self.callProgram)
self.output = QtWidgets.QTextEdit()
layout.addWidget(self.output)
layout.addWidget(self.runButton)
centralWidget = QtWidgets.QWidget()
centralWidget.setLayout(layout)
self.setCentralWidget(centralWidget)
# QProcess object for external app
self.process = QtCore.QProcess(self)
# QProcess emits `readyRead` when there is data to be read
self.process.readyRead.connect(self.dataReady)
# Just to prevent accidentally running multiple times
# Disable the button when process starts, and enable it when it finishes
self.process.started.connect(lambda: self.runButton.setEnabled(False))
self.process.finished.connect(lambda: self.runButton.setEnabled(True))
# Function Main Start
def main():
app = QtWidgets.QApplication(sys.argv)
ui = gui()
ui.show()
sys.exit(app.exec_())
# Function Main END
if __name__ == '__main__':
main()

Related

Trying to copy text from other threads using a button

I am trying to figure out a way to make a decent copy to clipboard button function where I can copy text values from other threads inside a QTextEdit widget.
I pull the values from other threads and display them in a QTextEdit widget and have been trying to successfully copy to clipboard the contents of QTextEdit with a button but the only way I have been able to is my code below.
What I don't like is that the copy button makes a second call in order to copy the contents.
Is there some way that the values can be stored from the first request using the get_button button and be available to copy with the copy button without having to make the same call all over again? Thanks.
from PyQt4 import QtCore, QtGui
import sys
class WorkerThread(QtCore.QThread):
get_String = QtCore.pyqtSignal(str)
def __init__(self):
super(WorkerThread, self).__init__()
def run(self):
self.get_String.emit('Some text.')
class GUI(QtGui.QWidget):
def __init__(self):
super(GUI, self).__init__()
self.get_Button = QtGui.QPushButton('Get')
self.c_Button = QtGui.QPushButton('Copy')
self.text_Box = QtGui.QTextEdit(self)
vbox = QtGui.QVBoxLayout(self)
vbox.addWidget(self.get_Button)
vbox.addWidget(self.c_Button)
vbox.addWidget(self.text_Box)
vbox.addStretch()
self.setGeometry(300, 300, 300, 300)
self.show()
self._thread = WorkerThread()
self.get_Button.clicked.connect(self.doIt)
self._thread.get_String.connect(self.text_Box.append)
self.c_Button.clicked.connect(self.copy_Stuff)
def copy_Stuff(self):
self.clipboard = QtGui.QApplication.clipboard()
self._thread.get_String.connect(self.clipboard.setText)
self._thread.start()
def doIt(self):
self.text_Box.clear()
self.text_Box.setText('')
self._thread.start()
def main():
app = QtGui.QApplication(sys.argv)
gui = GUI()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
It turns out this was much easier than I initially thought. I was confused with how to copy text correctly because of all of the different ways I seen it done in other examples and posts. After reading the docs for awhile and trying to learn a bit more about pyqt I seen that it was as easy as using these 2 lines...
self.text_Box.selectAll()
self.text_Box.copy()
with those 2 lines and my button it seems to copy all the contents of the QTextEdit widget which is all I really needed. Here is my final working code in case someone else may find it useful.
from PyQt4 import QtCore, QtGui
import sys
class WorkerThread(QtCore.QThread):
get_String = QtCore.pyqtSignal(str)
def __init__(self):
super(WorkerThread, self).__init__()
def run(self):
self.get_String.emit('Some text.')
class GUI(QtGui.QWidget):
def __init__(self):
super(GUI, self).__init__()
self.get_Button = QtGui.QPushButton('Get')
self.c_Button = QtGui.QPushButton('Copy')
self.text_Box = QtGui.QTextEdit(self)
vbox = QtGui.QVBoxLayout(self)
vbox.addWidget(self.get_Button)
vbox.addWidget(self.c_Button)
vbox.addWidget(self.text_Box)
vbox.addStretch()
self.setGeometry(300, 300, 300, 300)
self.show()
self._thread = WorkerThread()
self.get_Button.clicked.connect(self.doIt)
self._thread.get_String.connect(self.text_Box.append)
self.c_Button.clicked.connect(self.copy_Stuff)
def copy_Stuff(self):
self.text_Box.selectAll()
self.text_Box.copy()
def doIt(self):
self.text_Box.clear()
self.text_Box.setText('')
self._thread.start()
def main():
app = QtGui.QApplication(sys.argv)
gui = GUI()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
This code example shows a simple working thread with signal that returns a value to the main, displays the value in a QTextEdit widget and how to copy all of the contents of a QTextEdit widget with a single button click.

PyQt program running from command line not routing the console output in real time

Hi I am a newbie in PyQt.
Can someone suggest why the below program is not populating the stdout of print.py in real time to the textbox widget.This problem exists when it is running from command line.If we are running the samecode in pycharm we will get realtime outputs. Command line execution will show the data as a chunk(approx 4K) to the text box.
from PyQt4 import QtGui,QtCore
import sys
class gui(QtGui.QMainWindow):
def __init__(self):
super(gui, self).__init__()
self.initUI()
def dataReady(self):
cursor = self.output.textCursor()
cursor.movePosition(cursor.End)
cursor.insertText(str(self.process.readAll()))
self.output.ensureCursorVisible()
def initUI(self):
# Layout are better for placing widgets
layout = QtGui.QHBoxLayout()
self.output = QtGui.QTextEdit()
layout.addWidget(self.output)
centralWidget = QtGui.QWidget()
centralWidget.setLayout(layout)
self.setCentralWidget(centralWidget)
# QProcess object for external app
self.process = QtCore.QProcess(self)
# QProcess emits `readyRead` when there is data to be read
self.process.readyRead.connect(self.dataReady)
# Run program
self.process.start('python', ['./print.py'])
#Function Main Start
def main():
app = QtGui.QApplication(sys.argv)
ui=gui()
ui.show()
sys.exit(app.exec_())
#Function Main END
if __name__ == '__main__':
main()
Here is the code for print.py
import time
n=10
while(n):
print "Hello world"
time.sleep(1)
n = n-1
You should explicitly flush the output buffer of the print.py script (the child script) running in the subprocess. The default behaviour of print is to send the output to standard out (stdout). As such, you should explicitly call sys.sydout.flush() in the child script, after each print statement, to ensure the content of the print statements from the child script are made available to the parent script in a timely manner.

How to stop running PyQt5 program without closing the GUI window?

The following code pings a website and prints the result in QTextEdit. One button "Run" is used to start the ping. I want to have another button "End", which could stop the ping process while it is running without closing the GUI. But currently, the "End" button closes the whole GUI window. Do you have any thoughts on how to stop the ping but keep the GUI, so I can start the ping again by pressing the "Run" button.
import sys
from PyQt5 import QtCore,QtWidgets
class gui(QtWidgets.QMainWindow):
def __init__(self):
super(gui, self).__init__()
self.initUI()
def dataReady(self):
cursor = self.output.textCursor()
cursor.movePosition(cursor.End)
cursor.insertText(str(self.process.readAll()))
self.output.ensureCursorVisible()
def callProgram(self):
# run the process
# `start` takes the exec and a list of arguments
self.process.start('ping',['127.0.0.1'])
def initUI(self):
# Layout are better for placing widgets
layout = QtWidgets.QHBoxLayout()
self.runButton = QtWidgets.QPushButton('Run')
self.runButton.clicked.connect(self.callProgram)
self.runButton1 = QtWidgets.QPushButton('End')
self.runButton1.clicked.connect(self.close)
self.output = QtWidgets.QTextEdit()
layout.addWidget(self.output)
layout.addWidget(self.runButton)
layout.addWidget(self.runButton1)
centralWidget = QtWidgets.QWidget()
centralWidget.setLayout(layout)
self.setCentralWidget(centralWidget)
# QProcess object for external app
self.process = QtCore.QProcess(self)
# QProcess emits `readyRead` when there is data to be read
self.process.readyRead.connect(self.dataReady)
# Just to prevent accidentally running multiple times
# Disable the button when process starts, and enable it when it finishes
self.process.started.connect(lambda: self.runButton.setEnabled(False))
self.process.finished.connect(lambda: self.runButton.setEnabled(True))
#Function Main Start
def main():
app = QtWidgets.QApplication(sys.argv)
ui=gui()
ui.show()
sys.exit(app.exec_())
#Function Main END
if __name__ == '__main__':
main()
You must connect the clicked signal with the QProcess kill slot:
def initUI(self):
[...]
self.runButton1 = QtWidgets.QPushButton('End')
# self.runButton1.clicked.connect(self.close)
[...]
# QProcess object for external app
self.process = QtCore.QProcess(self)
# QProcess emits `readyRead` when there is data to be read
self.process.readyRead.connect(self.dataReady)
self.runButton1.clicked.connect(self.process.kill)
# Just to prevent accidentally running multiple times
# Disable the button when process starts, and enable it when it finishes
self.process.started.connect(lambda: self.runButton.setEnabled(False))
self.process.finished.connect(lambda: self.runButton.setEnabled(True))

Python crashes with 2 worker threads in PyQt5 application

I would like to have 2 worker threads in my application. One should start running as soon as the GUI is loaded and another one should start later by some signal. Let's say it's a button click.
I came across a weird behavior when my Python interpreter crashes(as in shows me Windows error "Python stopped working", no stack trace) on executing the second thread.
Here is an example, that will crash right after I click the button.
class Worker(QtCore.QThread):
def __init__(self, method_to_run):
super().__init__()
self.method = method_to_run
def run(self):
self.method()
class Window(QWidget):
def __init__(self):
super(Window, self).__init__()
self.button = QPushButton('Test', self)
self.label = QLabel(self)
self.button.clicked.connect(self.handleButton)
layout = QVBoxLayout(self)
layout.addWidget(self.label)
layout.addWidget(self.button)
self.worker = Worker(self.test_method)
self.worker.start()
def handleButton(self):
self.label.setText('Button Clicked!')
worker = Worker(self.test_method)
worker.start()
#staticmethod
def test_method():
res = [i*i for i in range(100500)]
if __name__ == '__main__':
import sys
app = QApplication(sys.argv)
window = Window()
window.show()
sys.exit(app.exec_())
What's more weird is that it doesn't crash when you debug the application for some reason.
What am I missing here?
Edit
I could get much from the crashdump, as I don't have symbols for QT. But it looks like the crash happens inside QtCore.dll
ExceptionAddress: 00000000632d4669 (Qt5Core!QThread::start+0x0000000000000229)
The problem is that you didn't save a reference to the thread so it was deleted as soon as you exited handleButton. If you save a reference, that begs the question of how to handle its lifespan.
QThread is more than just a wrapper to a system thread - it implements other services that let you wire a thread into your GUI. You can use its finished handler to signal the widget when it terminates to do any cleanup.
In this example, I save the worker as self.worker2 and block starting the worker a second time until the first one completes.
import PyQt5
import PyQt5.QtCore as QtCore
from PyQt5.QtWidgets import *
import time
class Worker(QtCore.QThread):
def __init__(self, method_to_run):
super(Worker, self).__init__()
self.method = method_to_run
def run(self):
self.method()
class Window(QWidget):
def __init__(self):
super().__init__()
self.button = QPushButton('Test', self)
self.label = QLabel(self)
self.button.clicked.connect(self.handleButton)
layout = QVBoxLayout(self)
layout.addWidget(self.label)
layout.addWidget(self.button)
self.worker = Worker(self.test_method)
self.worker.start()
self.worker2 = None
def handleButton(self):
self.label.setText('Button Clicked!')
# likely better to disable the button instead... but
# this shows events in action.
if self.worker2:
self.label.setText('Worker already running')
else:
self.worker2 = Worker(self.test_method)
self.worker2.finished.connect(self.handle_worker2_done)
self.worker2.start()
def handle_worker2_done(self):
self.worker2 = None
self.label.setText('Worker done')
#staticmethod
def test_method():
#res = [i*i for i in range(100500)]
time.sleep(3)
if __name__ == '__main__':
import sys
app = QApplication(sys.argv)
window = Window()
window.show()
sys.exit(app.exec_())
Your click triggered thread isn't actually trying to do work in a thread. You called the method, rather than passing the method as an argument, so you're trying to use the return value of test_method (None) as the method to run.
Change:
worker = Worker(self.test_method()) # Calls test_method and tries to run None
to:
worker = Worker(self.test_method) # Don't call test_method

Quiting the print dialog-pyqt

I defined a print function using QPrinter and QDialog. However, when I launch the printer dialog and then press cancel, the entire mainwindow goes into not responding mode. I have tried to do QtGui.QPrintDialog.close() but does not work.
Code:
import sys
from PyQt4 import QtCore
from PyQt4 import QtGui
class QButton(QtGui.QWidget):
def __init__(self, parent=None):
QtGui.QWidget.__init__(self, parent)
self.button = QtGui.QPushButton('Button', self)
self.name='me'
self.button.clicked.connect(self.calluser)
def calluser(self):
Appli=QtGui.QApplication(sys.argv)
printer= QtGui.QPrinter()
doc=QtGui.QTextDocument("Set local variables in this printing slot." )
dialog = QtGui.QPrintDialog(printer)
dialog.setModal(True)
dialog.setWindowTitle("Print Document" )
if dialog.exec_() == True:
doc.print_(printer)
# dialog.addEnabledOption(QAbstractPrintDialog.PrintSelection)
def demo_QButton():
app = QtGui.QApplication(sys.argv)
tb = QButton()
tb.show()
app.exec_()
if __name__=='__main__':
demo_QButton()
You created a new application in the calluser method. Delete or comment the line:
Appli=QtGui.QApplication(sys.argv)
and try again. I think this time your main window will remain responsive.

Categories