Reputation: 6655
I have a pyside application which runs a function in a QThread. This function often uses print
. How can I redirect the stdout to a dialog (containing a qtextedit
or similar) which will display when the function is run.
Here is a minimal example:
class Main(QtGui.QWindow):
def __init__(self):
super(Main, self).__init__()
self.run_button = QtGui.QPushButton("Run")
mainLayout = QtGui.QVBoxLayout()
mainLayout.addWidget(self.run_button)
self.setLayout(mainLayout)
self.run_button.clicked.connect(self.run_event)
def run_event(self):
# Create the dialog to display output
self.viewer = OutputDialog()
# Create the worker and put it in a thread
self.worker = Worker()
self.thread = QtCore.QThread()
self.worker.moveToThread(self.thread)
self.thread.started.connect(self.worker.run)
self.worker.finished.connect(self.thread.quit)
self.thread.start()
class Worker(QtCore.QObject):
finished = QtCore.Signal()
def run(self):
for i in range(10):
print "Im doing stuff"
time.sleep(1)
self.finished.emit()
class OutputDialog(QtGui.QDialog):
def __init__(self, parent=None):
super(OutputDialog, self).__init__(parent)
self.text_edit = QtGui.QTextEdit()
mainLayout = QtGui.QVBoxLayout()
mainLayout.addWidget(self.text_edit)
self.setLayout(vbox)
I can modify the worker to redirect stdout to itself, and then emit the text as a signal:
class Worker(QtCore.QObject):
finished = QtCore.Signal()
message = QtCore.Signal(str)
def __init__(self):
super(Worker, self).__init__()
sys.stdout = self
def run(self):
for i in range(10):
print "Im doing stuff"
time.sleep(1)
self.finished.emit()
def write(self, text):
self.message.emit(text)
But when I connect this signal to the OutputDialog
instance, the text is only displayed once the worker has finished.
I have also tried implementing the method here: Redirecting stdout and stderr to a PyQt4 QTextEdit from a secondary thread
But it just causes my app to freeze.
Any ideas?
Upvotes: 1
Views: 2219
Reputation: 11849
The reason your print lines only show up once the worker has finished is explained by this stack overflow answer: https://stackoverflow.com/a/20818401/1994235
To summarise, when dealing with signals/slots across threads, you need to decorate the slots with @pyqtslot(types)
to make sure they are actually run in the thread you intended.
Upvotes: 1
Reputation: 6655
It seems like this can be done easily by subclassing QThread
:
class Main(QtGui.QWindow):
def __init__(self):
super(Main, self).__init__()
self.run_button = QtGui.QPushButton("Run")
mainLayout = QtGui.QVBoxLayout()
mainLayout.addWidget(self.run_button)
self.setLayout(mainLayout)
self.run_button.clicked.connect(self.run_event)
def run_event(self):
# Create the dialog to display output
self.viewer = OutputDialog()
# Create the worker thread and run it
self.thread = WorkerThread()
self.thread.message.connect(self.viewer.write)
self.thread.start()
class WorkerThread(QtCore.QThread):
message = QtCore.Signal(str)
def run(self):
sys.stdout = self
for i in range(10):
print "Im doing stuff"
time.sleep(1)
def write(self, text):
self.message.emit(text)
However, most documentation on the web seems to recommend that you do not subclass QThread and instead use the moveToThread
function for processing tasks.
Also, I don't know how you would distinguish between stdout
and stderr
in the above method (assuming that you redirect both to the workerthread)
Upvotes: 0