0

I have a PyQt GUI with a QTextEdit box that displays print statements during code execution. I chose to do this by redirecting sys.stdout, following posts like this one:

Print out python console output to Qtextedit

It works great for logging print statements so that the user can see what happened during execution, however I'd like to have it update as the code is running, rather than writing to the buffer and printing everything out at the end. In this code, for example, clicking the 'Run' button will wait 5 seconds to print both 'Running...' and 'Done.' after the function runs, whereas I'd like it to display 'Running...', wait 5 seconds, then display 'Done.'

Here are the basics of the code as I have it written:

import sys
import time
from PyQt5.QtWidgets import QMainWindow, QPushButton, QApplication, QTextEdit
from PyQt5 import QtCore, QtGui


class Stream(QtCore.QObject):
    """Redirects console output to text widget."""
    newText = QtCore.pyqtSignal(str)

    def write(self, text):
        self.newText.emit(str(text))


class GenMast(QMainWindow):
    """Main application window."""
    def __init__(self):
        super().__init__()

        self.initUI()

        # Custom output stream.
        sys.stdout = Stream(newText=self.onUpdateText)

    def onUpdateText(self, text):
        """Write console output to text widget."""
        cursor = self.process.textCursor()
        cursor.movePosition(QtGui.QTextCursor.End)
        cursor.insertText(text)
        self.process.setTextCursor(cursor)
        self.process.ensureCursorVisible()

    def closeEvent(self, event):
        """Shuts down application on close."""
        # Return stdout to defaults.
        sys.stdout = sys.__stdout__
        super().closeEvent(event)

    def initUI(self):
        """Creates UI window on launch."""
        # Button for generating the master list.
        btnGenMast = QPushButton('Run', self)
        btnGenMast.move(450, 100)
        btnGenMast.resize(100, 100)
        btnGenMast.clicked.connect(self.genMastClicked)

        # Create the text output widget.
        self.process = QTextEdit(self, readOnly=True)
        self.process.ensureCursorVisible()
        self.process.setLineWrapColumnOrWidth(500)
        self.process.setLineWrapMode(QTextEdit.FixedPixelWidth)
        self.process.setFixedWidth(400)
        self.process.setFixedHeight(150)
        self.process.move(30, 100)

        # Set window size and title, then show the window.
        self.setGeometry(300, 300, 600, 300)
        self.setWindowTitle('Generate Master')
        self.show()

    def genMastClicked(self):
        """Runs the main function."""
        print('Running...')
        time.sleep(5)
        print('Done.')


if __name__ == '__main__':
    # Run the application.
    app = QApplication(sys.argv)
    app.aboutToQuit.connect(app.deleteLater)
    gui = GenMast()
    sys.exit(app.exec_())

I first tried setting flush=True in the print statements. However, I was met with the error 'Stream' object has no attribute 'flush', so I moved on to defining flush in the Stream class I created, as follows:

class Stream(QtCore.QObject):
    """Redirects console output to text widget."""
    newText = QtCore.pyqtSignal(str)

    def write(self, text):
        self.newText.emit(str(text))

    def flush(self):
        sys.stdout.flush()

Which produced the error RecursionError: maximum recursion depth exceeded. This is where my understanding runs out, as I've tried other ways to flush the print buffer but they all seem to have this problem. Any advice on what I'm doing wrong?

  • I don't know Qt, so I'm not qualified to write an answer, but you need to run the function that does the printing in a separate thread. – PM 2Ring Jun 08 '18 at 19:34
  • Gotcha, I was wondering if it was going to be a threading issue. It seemed like flushing the print buffer could work but I'm getting the impression that that isn't the solution. – Alexander Trevelyan Jun 08 '18 at 19:52
  • @AlexanderTrevelyan Did threading work ? I'm currently experiencing the same problem – ಠ_ಠ Jul 26 '18 at 19:57
  • Yes! Threading was the solution. I followed some threading tutorials and added a worker class to my module. My worker class looks exactly like the one found here: https://stackoverflow.com/questions/50855210/how-to-pass-parameters-into-qrunnable-for-pyqt5 – Alexander Trevelyan Jul 26 '18 at 21:45

0 Answers0