Threadsafe capturing stdout to a TextCtrl

I have a program where I want to be able to have print statements get
written to a TextCtrl, and errors show up in another. So I made my two
TextCtrls and assigned them to sys.stdout and sys.stderr. So far so
good. However, sometimes when I print to them, I get this error:

Traceback (most recent call last):
  File "C:\Python27x64\lib\site-packages\wx-2.8-msw-unicode\wx\_core.py",
line 14660, in <lambda>
    lambda event: event.callable(*event.args, **event.kw) )
  File "C:\mui\gui\loggingWindow.py", line 48, in stdOut
    window.stdOut.write(' '.join([str(s) for s in args]))
  File "C:\Python27x64\lib\site-packages\wx-2.8-msw-unicode\wx\_controls.py",
line 1990, in write
    return _controls_.TextCtrl_write(*args, **kwargs)
wx._core.PyAssertionError: C++ assertion "m_count == -1 || m_count ==
-2" failed at ..\..\src\msw\textctrl.cpp(140) in
UpdatesCountFilter::UpdatesCountFilter(): wrong initial m_updatesCount
value

I'm guessing the problem is that print statements made outside of the
main thread are causing UI updates when those should only happen in
the main thread. I tried working around it by overriding the
TextCtrl.write method:

        self.stdOut.__labWrite = self.stdOut.write
        self.stdOut.write = lambda *args:
wx.CallAfter(self.stdOut.__labWrite(*args))

but this results in massive error spew about NoneType not being
callable. Oddly this shows up in my stderr window just fine, but the
spew locks up the program and I can't copy&paste it.

So what's the proper way to capture stdout and stderr to a text control?

-Chris

I'm guessing the problem is that print statements made outside of the
main thread are causing UI updates when those should only happen in
the main thread. I tried working around it by overriding the
TextCtrl.write method:

         self.stdOut.__labWrite = self.stdOut.write
         self.stdOut.write = lambda *args:
wx.CallAfter(self.stdOut.__labWrite(*args))

but this results in massive error spew about NoneType not being
callable.

Because you are passing the wx.CallAfter the return value from self.stdOut.__labWrite(*args). I expect that you wanted to use (self.stdOut.__labWrite, *args)

  Oddly this shows up in my stderr window just fine, but the

spew locks up the program and I can't copy&paste it.

So what's the proper way to capture stdout and stderr to a text control?

For a slightly different approach take a look at how it is done in PyOnDemandOuputWindow, which is what is used when you pass redirect=True to wx.App.

···

On 11/15/12 1:11 PM, Chris Weisiger wrote:

--
Robin Dunn
Software Craftsman

I'm guessing the problem is that print statements made outside of the
main thread are causing UI updates when those should only happen in
the main thread. I tried working around it by overriding the
TextCtrl.write method:

         self.stdOut.__labWrite = self.stdOut.write
         self.stdOut.write = lambda *args:
wx.CallAfter(self.stdOut.__labWrite(*args))

but this results in massive error spew about NoneType not being
callable.

Because you are passing the wx.CallAfter the return value from
self.stdOut.__labWrite(*args). I expect that you wanted to use
(self.stdOut.__labWrite, *args)

D'oh! That's what I get for sending a help-me email late in the day
when I'm tired. Stupid mistake; thanks for the catch.

For a slightly different approach take a look at how it is done in
PyOnDemandOuputWindow, which is what is used when you pass redirect=True to
wx.App.

(Note it's PyOnDemandOutputWindow; you missed a 't'). With that term
to google, I found some other threads detailing alternate approaches.
The PyOnDemandOutputWindow doesn't quite suit my needs since it's only
created when output is first provided and I want more control over the
UI. But replacing TextCtrl.write with
wx.CallAfter(TextCtrl.AppendText) did the trick for me:

        self.stdOut.write = lambda *args:
wx.CallAfter(self.stdOut.AppendText, *args)
        self.stdErr.write = lambda *args:
wx.CallAfter(self.stdErr.AppendText, *args)
        sys.stdout = self.stdOut
        sys.stderr = self.stdErr

My original approach of just doing
wx.CallAfter(self.stdOut.__labWrite, *args) still resulted in that
"Count" error from time to time, but this seems to be working
flawlessly.

Thanks for the assistance!

-Chris

···

On Thu, Nov 15, 2012 at 9:53 PM, Robin Dunn <robin@alldunn.com> wrote:

On 11/15/12 1:11 PM, Chris Weisiger wrote: