在线程中运行Python脚本并将std.out/std.err重定向到GUI的wx.TextCtrl

3 投票
2 回答
1349 浏览
提问于 2025-04-16 03:13

我正在尝试写一个图形界面(GUI),这个界面可以读取一些设置,然后生成一个Python脚本并运行它。因为这个脚本可能需要几十分钟才能完成,所以为了不让界面卡住,让用户感到烦躁,我决定在一个单独的线程中运行它。在这之前,我用一个单独的类把程序的标准输出(std.out)和标准错误(std.err)重定向到一个文本框(TextCtrl)。这样做效果不错,但在执行期间界面还是会被阻塞。

现在我在线程中运行脚本,并且使用了重定向的类,但界面依然被阻塞。为了不让界面卡住,我需要关闭重定向。这样一来,脚本和图形界面的所有输出都会直接显示在控制台上。

下面是我用来重定向的类,以及我如何调用它的代码。

# 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

我尝试过使用某种通信类来在线程和图形界面之间传递信息,但没有成功。也就是说,界面还是会被阻塞。

有没有人能给我一些建议或者解决方案,让我可以在不阻塞界面的情况下,把脚本的标准输出和标准错误信息显示到图形界面上?

2 个回答

2

我用过的另一个成功的解决方案是使用 Python 的日志功能,而不是直接输出到屏幕上。为了做到这一点,你需要写一个子类,扩展 logging.Handler,这样就可以自定义在你的 wx 应用程序中的 wx.TextCtrl 中显示的字体和文本颜色:

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)

接下来,你需要配置这个日志记录器:

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

最后,将文本控件与日志输出绑定在一起:

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

是的。从这个讨论来看,可以使用 wx.CallAfter 来把文本安全地发送到图形界面(GUI)。这样就可以把文本拿来显示出来。还有一种方法是使用 subprocess 来进行通信。这里有一个相关的例子:

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

这篇文章的评论中也列出了一些方法:

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

不幸的是,我当时的评论系统在缩进方面做得不太好。

撰写回答