You can instantiate custom events from the non-GUI thread and wx.PostEvent them back to the GUI-thread. This is a thread-safe action. My use cases typically work like this:
- Start worker thread - Custom event 'Starting Action'
- Start processing
- Post events back updating progress 'Line 435 of 15000 is parsed'
- etc.
Then I bind the custom event to update a dialog or textctrl/log or whatever. It's surprisingly easy to do. If you'd like I can post some sample code of a little test case I wrote a while back when I was figuring this stuff out.
--Edit:
Okay here's some code, first the threading example:
#!usr/bin/env python
import wx
import threading
import Queue
import random
import time
TextEventType = wx.NewEventType()
EVT_THREAD_TEXT_EVENT = wx.PyEventBinder(TextEventType, 1)
global_queue = Queue.Queue()
def threadStart(numthrds, queue, window):
for i in range(numthrds):
i = TextThread(queue, window)
class TextThread(threading.Thread):
def __init__(self, queue, output_window):
threading.Thread.__init__(self)
self.inqueue = queue
self.output_window = output_window
self.start()
def run(self):
word = self.inqueue.get()
self.setName(word.upper())
wait = random.randrange(1, 10)
time.sleep(wait)
msg = 'Thread: ' + self.getName() + '--wait= ' + str(wait) + ' ' + word
evt = NewTextEvent(TextEventType, -1)
evt.setText(msg)
wx.PostEvent(self.output_window, evt) #post EVT_THREAD_TEXT_EVENT
#self.inqueue.task_done() #may not need this if non-blocking
class NewTextEvent(wx.PyCommandEvent):
def __init__(self, evtType, id):
wx.PyCommandEvent.__init__(self, evtType, id)
self.msg = ''
def setText(self, text):
self.msg = text
def getText(self):
return self.msg
class TextFrame(wx.Frame):
def __init__(self, parent, id, *args, **kwargs):
wx.Frame.__init__(self, parent, id, *args, **kwargs)
self.queue = Queue.Queue()
framesizer = wx.BoxSizer(wx.VERTICAL)
self.panel = ThreadPanel(self, wx.ID_ANY)
framesizer.Add(self.panel, 0, wx.EXPAND)
self.SetSizerAndFit(framesizer)
self.Bind(EVT_THREAD_TEXT_EVENT, self.OnThreadText)
def OnThreadText(self, evt):
msg = evt.getText()
self.panel.out_tc.AppendText(msg + '\n')
class ThreadPanel(wx.Panel):
def __init__(self, parent, id, *args, **kwargs):
wx.Panel.__init__(self, parent, *args, **kwargs)
vsizer = wx.BoxSizer(wx.VERTICAL)
self.wordtc = wx.TextCtrl(self, id=wx.ID_ANY, value='', size=(350, -1))
self.inst_text = wx.StaticText(self, wx.ID_ANY,
label='Enter a list of space-separated words')
self.out_tc = wx.TextCtrl(self, id=wx.ID_ANY, size=(350, 300),
value='', style=wx.TE_MULTILINE)
self.start_button = wx.Button(self, wx.ID_ANY, label='Start Threads')
vsizer.Add(self.inst_text, 0, wx.ALIGN_LEFT)
vsizer.Add(self.wordtc, 0, wx.EXPAND)
vsizer.Add(self.start_button)
vsizer.Add((100,100))
vsizer.Add(self.out_tc, 0, wx.EXPAND)
self.SetSizer(vsizer)
self.Bind(wx.EVT_BUTTON, self.OnStartButton, self.start_button)
def OnStartButton(self, evt):
self.out_tc.Clear()
text = self.wordtc.GetValue()
self.wordtc.Clear()
if not text.count(','):
text = text.split(' ')
num_thrds = len(text)
for word in text:
word = word.strip()
self.GetParent().queue.put(word)
threadStart(num_thrds, self.GetParent().queue, self.GetParent())
if __name__ == "__main__":
app = wx.App()
frame = TextFrame(None, wx.ID_ANY, 'Thread test')
frame.Show()
app.MainLoop()
And a second, more simple example with custom events:
#!usr/bin/env python
import wx
import random
colorEventType = wx.NewEventType()
EVT_COLOR_EVENT = wx.PyEventBinder(colorEventType, 1)
class ButtonPanel(wx.Panel):
def __init__(self, parent, *args, **kwargs):
wx.Panel.__init__(self, parent, *args, **kwargs)
vsizer = wx.BoxSizer(wx.VERTICAL)
self.rstbutt = wx.Button(self, wx.ID_ANY, label='Restore')
self.rstbutt.Disable()
self.Bind(wx.EVT_BUTTON, self.OnButt, self.rstbutt)
vsizer.Add(self.rstbutt, 0, wx.ALIGN_CENTER)
vsizer.Add((500,150), 0)
self.SetSizer(vsizer)
def OnButt(self, evt):
self.SetBackgroundColour(wx.NullColor)
self.GetParent().Refresh()
self.rstbutt.Disable()
class ColorEvent(wx.PyCommandEvent):
def __init__(self, evtType, id):
wx.PyCommandEvent.__init__(self, evtType, id)
self.color = None
def SetMyColor(self, color):
self.color = color
def GetMyColor(self):
return self.color
class MainFrame(wx.Frame):
def __init__(self, parent, *args, **kwargs):
wx.Frame.__init__(self, parent, *args, **kwargs)
framesizer = wx.BoxSizer(wx.VERTICAL)
self.panel = ButtonPanel(self, wx.ID_ANY)
framesizer.Add(self.panel, 1, wx.EXPAND)
menubar = wx.MenuBar()
filemenu = wx.Menu()
menuquit = filemenu.Append(wx.ID_ANY, '&Quit')
menubar.Append(filemenu, 'File')
colormenu = wx.Menu()
switch = colormenu.Append(wx.ID_ANY, '&Switch Color')
menubar.Append(colormenu, '&Color')
self.SetMenuBar(menubar)
self.Bind(wx.EVT_MENU, self.OnQuit, menuquit)
self.Bind(wx.EVT_MENU, self.OnColor, switch)
self.Bind(EVT_COLOR_EVENT, self.ColorSwitch)
self.SetSizerAndFit(framesizer)
def OnQuit(self, evt):
self.Close()
def OnColor(self, evt):
colevt = ColorEvent(colorEventType, -1)
colors = ['red', 'green', 'blue', 'white', 'black', 'pink',
(106, 90, 205), #slate blue
(64, 224, 208), #turquoise
]
choice = random.choice(colors)
colevt.SetMyColor(choice)
self.GetEventHandler().ProcessEvent(colevt)
#evt.Skip()
def ColorSwitch(self, evt):
color = evt.GetMyColor()
#print(color)
self.panel.SetBackgroundColour(color)
self.Refresh()
self.panel.rstbutt.Enable()
if __name__ == "__main__":
app = wx.App()
frame = MainFrame(None, wx.ID_ANY, title="Change Panel Color Custom Event")
frame.Show(True)
app.MainLoop()