jramm
jramm

Reputation: 6655

PySide: redirect stdout to a dialog

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

Answers (2)

three_pineapples
three_pineapples

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

jramm
jramm

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

Related Questions