views:

59

answers:

2

I'm trying to write a GUI that reads in settings for a python-script, then generates the script and runs it. The script can take dozens of minutes to run so in order to not block the GUI and frustrate the user I'm running it in a separate thread. Before I did this I used a separate class to redirect the std.out and std.err of the program to a TextCtrl. This worked fine except for the GUI getting blocked during execution.

Running the script from the thread with the redirection-class still blocks the GUI. In order not to block the GUI I need to turn the redirection off. All std.out/err from both the script and the gui then goes into the console.

Here is the class that redirects and how I call it.

# For redirecting stdout/stderr to txtctrl.
class RedirectText(object):
    def __init__(self,aWxTextCtrl):
        self.out=aWxTextCtrl

    def write(self,string):
        self.out.WriteText(string) 

self.redir=RedirectText(self.bottom_text)
sys.stdout=self.redir
sys.stderr=self.redir
sys.stdin=self.redir

I've tried using some kind of a communication class from the thread to the GUI without success. That is, the GUI still gets blocked.

Does anyone have some hints or a solution for this problem, that is to get the stdout/err from the script to the GUI without blocking the GUI?

+1  A: 

Yeah. From the thread, use wx.CallAfter to send the text to the GUI to a thread-safe way. Then it can take the text and display it. Another way to do it would be to use subprocess and communicate with that. There's an example of that here:

http://www.blog.pythonlibrary.org/2010/06/05/python-running-ping-traceroute-and-more/

There are also some methods listed in the comments of this article:

http://www.blog.pythonlibrary.org/2009/01/01/wxpython-redirecting-stdout-stderr/

Unfortunately, my commenting system at that time didn't do a good job with indentation.

Mike Driscoll
+1  A: 

Another solution I have used with success would be to use python logging instead of stdout/stderr. In order to do that, you write a subclass that extends logging.Handler, to customize the font and the text color to be presented in a wx.TextCtrl in your wx application:

import logging
from logging import Handler

class WxHandler(Handler):
    def __init__(self, logCtrl):
        """
        Initialize the handler.
        logCtrl = an instance of wx.TextCtrl
        """
        self.logCtrl = logCtrl
        Handler.__init__(self)

    def flush(self):
        pass

    def emit(self, record):
        """
        Emit a record.

        If a formatter is specified, it is used to format the record.
        The record is then written to the stream with a trailing newline.  If
        exception information is present, it is formatted using
        traceback.print_exception and appended to the stream.  If the stream
        has an 'encoding' attribute, it is used to encode the message before
        output to the stream.
        """
        try:
            lastPos = self.logCtrl.GetLastPosition()
            msg = self.format(record)
            self.logCtrl.WriteText(msg)
            self.logCtrl.WriteText('\r\n')
            f = wx.Font(10, wx.MODERN, wx.NORMAL, wx.NORMAL, False, u'Arial', wx.FONTENCODING_ISO8859_1)
            if record.levelno == logging.INFO:
                textColour = wx.Colour(0, 0, 205)
            elif record.levelno == logging.WARN: 
                textColour = wx.Colour(250, 128, 114)
            elif record.levelno >= logging.ERROR:
                textColour = wx.Colour(220, 20, 60)
            else:
                textColour = wx.Colour(0, 0, 0)
            self.logCtrl.SetStyle(lastPos, lastPos + len(msg), wx.TextAttr(textColour, wx.NullColour, f))
        except:
            self.handleError(record)

In order to configure the logger:

def configureWxLogger(logCtrl, loggingLevel):
    """
        Wx Logger config
    """
    logger = logging.getLogger()
    logger.setLevel(loggingLevel)
    ch = WxHandler(logCtrl)
    formatter = logging.Formatter("%(asctime)-20s - %(levelname)-8s - %(message)s")
    formatter.datefmt = '%d/%m/%Y-%H:%M:%S'
    ch.setFormatter(formatter)
    logger.addHandler(ch)
    return logger

And, finally, to bind the text control to the log output:

self.logCtrl = wx.TextCtrl(self, -1, "", size=(600, 200), style=wx.TE_MULTILINE|wx.TE_RICH2)
wxLoggingHelper.configureWxLogger(self.logCtrl, logging.DEBUG)
Victor Ionescu