Marcel Stör
Marcel Stör

Reputation: 23535

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

I'm working on a wxPython GUI for 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 for that and it works well.

However, I'm currently stuck dealing with the progress monitor that esptool.py prints to console. It prints something like "25%" followed by a number of \b which immediately erase what was printed, then "26%" which again is erased immediately and so on.

The plan was to parse the string, TextCtrl.AppendText() everything but the backspace characters and then TextCtrl.Remove() as many characters as there are backspace characters.

The below code works fine when I step through it with the debugger but it crashes hard when "let loose". There seems to be some timing/threading issue. Apparently I can't call TextCtrl.Remove() right after TextCtrl.AppendText()?

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

The code that calls esptool.py runs in its own thread as to not hog the main UI thread.

This is my first real Python project (and thus the first w/ wxPython of course) and I haven't coded for the desktop in many years. So, it's entirely possible I'm missing something obvious.

Upvotes: 0

Views: 628

Answers (1)

Marcel Stör
Marcel Stör

Reputation: 23535

For the sake of completeness here's (one of) the solution(s).

Turns out that manipulating the text control with wx.CallAfter in quick succession isn't too reliable wrt GetLastPosition(). So, it now just appends the text and remembers how many characters to remove the next time write() is invoked. It then removes those characters before appending the new text.

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

Upvotes: 2

Related Questions