Threadsafe capturing stdout to a TextCtrl

342 views
Skip to first unread message

Chris Weisiger

unread,
Nov 15, 2012, 4:11:59 PM11/15/12
to wxpytho...@googlegroups.com
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

Robin Dunn

unread,
Nov 16, 2012, 12:53:04 AM11/16/12
to wxpytho...@googlegroups.com
On 11/15/12 1:11 PM, Chris Weisiger wrote:
> 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.


--
Robin Dunn
Software Craftsman
http://wxPython.org

Chris Weisiger

unread,
Nov 16, 2012, 12:04:45 PM11/16/12
to wxpytho...@googlegroups.com
On Thu, Nov 15, 2012 at 9:53 PM, Robin Dunn <ro...@alldunn.com> wrote:
> On 11/15/12 1:11 PM, Chris Weisiger wrote:
>>
>> 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
Reply all
Reply to author
Forward
0 new messages