在线程中运行Python脚本并将std.out/std.err重定向到GUI的wx.TextCtrl
我正在尝试写一个图形界面(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 个回答
我用过的另一个成功的解决方案是使用 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)
是的。从这个讨论来看,可以使用 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/
不幸的是,我当时的评论系统在缩进方面做得不太好。