Here is an example of the code I finally used. It is a combination of the answers by Mike Driscoll and Yoriz. Both their answers are great and valid for the question that was asked. However they stop working whenever the rotating character needs to indicate a calculation in progress. To prevent this I basically start two threads, one dealing with the actual calculation and another dealing with the rotating character. When the calculation is finished, its thread posts an event to the main frame. This latter can use this event to report the result of the calculation and to abort the progress thread. The progress thread in turn regularly posts events (mediated by a wx.Timer) to update the TextCtrl.
The code is the following:
import wx
from threading import Thread
from itertools import cycle
# Define notification event for thread completion
EVT_RESULT_ID = wx.NewId()
EVT_PROGRESSUPDATE_ID = wx.NewId()
def EVT_RESULT(win, func):
"""Define Result Event."""
win.Connect(-1, -1, EVT_RESULT_ID, func)
class ResultEvent(wx.PyEvent):
"""Simple event to carry arbitrary result data."""
def __init__(self, data):
wx.PyEvent.__init__(self)
self.SetEventType(EVT_RESULT_ID)
self.data = data
def EVT_PROGRESSUPDATE(win, func):
win.Connect(-1,-1,EVT_PROGRESSUPDATE_ID,func)
class ProgressUpdateEvent(wx.PyEvent):
def __init__(self,data):
wx.PyEvent.__init__(self)
self.SetEventType(EVT_PROGRESSUPDATE_ID)
self.data = data
# Thread class that shows progress
class ProgressThread(Thread):
def __init__(self,notify_window):
Thread.__init__(self)
self._notify_window = notify_window
self.chars = cycle(('|','/','-','\\'))
self.tinyTimer = wx.Timer(notify_window)
notify_window.Bind(wx.EVT_TIMER, self.updateTextCtrl, self.tinyTimer)
self.tinyTimer.Start(100)
def updateTextCtrl(self,event):
wx.PostEvent(self._notify_window, ProgressUpdateEvent(next(self.chars)))
def abort(self):
self.tinyTimer.Stop()
return
# Thread class that executes processing
class WorkerThread(Thread):
"""Worker Thread Class."""
def __init__(self, notify_window):
"""Init Worker Thread Class."""
Thread.__init__(self)
self._notify_window = notify_window
self.start()
def run(self):
"""Run Worker Thread."""
x = 0
for i in range(100000000):
x += i
wx.PostEvent(self._notify_window, ResultEvent(x))
class MainFrame(wx.Frame):
def __init__(self, parent, id):
wx.Frame.__init__(self, parent, id, 'Thread Test')
self.start_button = wx.Button(self, -1, 'Start', pos=(0,0))
self.progress = wx.TextCtrl(self,-1,'',pos=(0,50))
self.status = wx.StaticText(self, -1, '', pos=(0,100))
self.Bind(wx.EVT_BUTTON, self.OnStart, self.start_button)
# Set up event handlers
EVT_RESULT(self,self.OnResult)
EVT_PROGRESSUPDATE(self,self.OnProgressUpdate)
# And indicate we don't have a worker thread yet
self.worker = None
def OnStart(self, event):
"""Start Computation."""
# Trigger the worker thread unless it's already busy
if not self.worker:
self.status.SetLabel('Starting computation')
self.worker = WorkerThread(self)
self.p = ProgressThread(self)
def OnResult(self, event):
"""Show Result status."""
self.p.abort()
self.status.SetLabel('Computation Result: %s' % event.data)
self.worker = None
def OnProgressUpdate(self,event):
self.progress.ChangeValue(event.data)
class MainApp(wx.App):
def OnInit(self):
self.frame = MainFrame(None, -1)
self.frame.Show(True)
self.SetTopWindow(self.frame)
return True
if __name__ == '__main__':
app = MainApp(0)
app.MainLoop()