如何持续更新(类似终端)一个 wxPython TextCtrl

How to continuously update (terminal-like) a wxPython TextCtrl

我正在为 esptool.py i.e. the app will invoke that script. For starters I'd like to redirect the content that esptool.py prints to console to a TextCtrl. I followed a frequently referenced article 开发 wxPython GUI,它运行良好。

但是,我目前无法处理 progress monitor that esptool.py prints to console。它会打印类似“25%”的内容,然后是一些 \b,它会立即擦除打印的内容,然后是“26%”,它会再次被立即擦除,依此类推。

计划是解析字符串,TextCtrl.AppendText() 除了退格字符之外的所有内容,然后 TextCtrl.Remove() 有多少退格字符就多少字符。

下面的代码在我用调试器逐步执行时工作正常,但在 "let loose" 时它会严重崩溃。似乎有一些 timing/threading 问题。显然我不能在 TextCtrl.AppendText()?

之后立即调用 TextCtrl.Remove()
class RedirectText:
    def __init__(self, textCtrl):
        self.out = textCtrl

    def write(self, string):
        new_string = ""
        number_of_backspaces = 0
        # this could definitely be improved performance wise...
        for c in string:
            if c == "\b":
                number_of_backspaces += 1
            else:
                new_string += c

        self.out.AppendText(new_string)
        if number_of_backspaces > 0:
            last_position = self.out.GetLastPosition()
            self.out.Remove(last_position - number_of_backspaces, last_position)

    def flush(self):
        None

调用 esptool.py 的代码在其自己的线程中运行,以免占用主 UI 线程。

这是我的第一个真正的 Python 项目(当然也是第一个 w/ w/Python),我已经很多年没有为桌面编写代码了。所以,我完全有可能遗漏了一些明显的东西。

为了完整起见,这里是(其中一个)解决方案。

事实证明,与 GetLastPosition() 相比,使用 wx.CallAfter 快速连续地操作文本控件不太可靠。因此,它现在只是附加文本并记住在 下一次 调用 write() 时要删除多少个字符。然后在附加新文本之前删除这些字符。

class RedirectText:
    def __init__(self, text_ctrl):
        self.out = text_ctrl
        self.pending_backspaces = 0

    def write(self, string):
        new_string = ""
        number_of_backspaces = 0
        for c in string:
            if c == "\b":
                number_of_backspaces += 1
            else:
                new_string += c

        if self.pending_backspaces > 0:
            # current value minus pending backspaces plus new string
            new_value = self.out.GetValue()[:-1 * self.pending_backspaces] + new_string
            wx.CallAfter(self.out.SetValue, new_value)
        else:
            wx.CallAfter(self.out.AppendText, new_string)

        self.pending_backspaces = number_of_backspaces

    def flush(self):
        None