G-71
G-71

Reputation: 3682

Non-blocking realtime redirection stdout from process to wx.TextCtrl control

I've got the countdown.exe file (source code of this file is below). When this files is executed he write in console every second text. I start execution of this file when my GUI python app is executed:

self.countdown_process = subprocess.Popen("countdown.exe", shell=True, stdout=subprocess.PIPE)

I redirect stdout in subprocess.PIPE and start thread out_thread which read stdout of this process and add to TextCtrl:

out_thread = OutTextThread(self.countdown_process.stdout, self.AddText)
out_thread.start()

This is the full code of my python app:

import os
import sys
import wx

import subprocess, threading

class MyFrame(wx.Frame):
    def __init__(self):
        super(MyFrame, self).__init__(None)
        self._init_ctrls()

    def _init_ctrls(self):
        self.OutText = wx.TextCtrl(id=wx.NewId(), value='', name='OutText',
                                   parent=self, pos=wx.Point(0, 0),
                                   size=wx.Size(0, 0), style=wx.TE_MULTILINE|wx.TE_RICH2)

        self.OutText.AppendText("Starting process...\n")
        self.OutText.AppendText("Waiting 10 seconds...\n")
        self.countdown_process = subprocess.Popen("countdown.exe", shell = True, stdout=subprocess.PIPE)
        out_thread = OutTextThread(self.countdown_process.stdout, self.AddText)
        out_thread.start()

    def AddText(self, text):
        self.OutText.AppendText(text)

class OutTextThread(threading.Thread):
    def __init__(self, std_out, cb):
        super(OutTextThread, self).__init__()
        self.std_out = std_out
        self.cb = cb

    def run(self):
        text = None
        while text != '':
            text = self.std_out.readline()
            self.cb(text)

if __name__ == '__main__':
    app = wx.App(False)
    frame = MyFrame()
    frame.Show(True)
    app.MainLoop()

The C++ code of countdown.exe is simple:

#include <stdio.h>
#include <time.h>

void wait ( int seconds )
{
  clock_t endwait;
  endwait = clock () + seconds * CLOCKS_PER_SEC ;
  while (clock() < endwait) {}
}

int main ()
{
  int n;
  printf ("Starting countdown...\n");
  for (n=10; n>0; n--)
  {
    printf ("%d\n",n);
    wait (1);
  }
  printf ("FIRE!!!\n");
  return 0;
}

But i have some problem. I start my python app and i must wait 10 second and only 10 seconds the stdout of countdown.exe is written in TextCtrl as you can see on picture below: enter image description here I want the realtime writing the stdout of countdown.exe in TextCtrl (self.OutText). How I can do this? I tried using wx.CallAfter in AddText method:

def AddText(self, text):
    wx.CallAfter(self.OutText.AppendText, text)

but it's useless.

Upvotes: 0

Views: 1603

Answers (1)

Mike Driscoll
Mike Driscoll

Reputation: 33111

You cannot call wxPython methods directly from a thread. So the line

self.cb(text)

won't work. But if you put that into a threadsafe method, such as wx.CallAfter, then it should work. See the following links:

I also wrote a tutorial on redirecting stuff from stdout to a text control here:

Upvotes: 1

Related Questions