Reputation: 4983
Here's an example:
class DemoFrame(wx.Frame):
def __init__(self, parent):
wx.Frame.__init__(self, parent)
self.panel = wx.Panel(self, -1)
...
initialize other elements
...
self.DoStuff()
def DoStuff(self):
self.panel.SetBackGroundColour(wx.Colour(240, 240, 240))
...
do something
...
Now as you know this is definitely not a good example of initializing your GUI since do something
would most probably freeze the GUI while it's running, so I tweaked it to this:
import threading
class DemoFrame(wx.Frame):
def __init__(self, parent):
wx.Frame.__init__(self, parent)
self.panel = wx.Panel(self, -1)
...
initialize other elements
...
DoStuffThead = threading.Thread(target = self.DoStuff, ())
DoStuffThead.start()
def DoStuff(self):
wx.CallAfter(self.ChangeBG, )
...
do something
...
def ChangeBG(self):
self.panel.SetBackGroundColour(wx.Colour(240, 240, 240))
Above code should work exactly the same as the first one does when do something
is blank, but to my surprise I noticed there's little background drawing glitches when running the latter codes.
What part went wrong? Isn't this the right way to update GUI in threads?
Upvotes: 2
Views: 512
Reputation: 4983
After searching and playing with wxpython for a while, I finally found a solution for this, and it's actually quite simple, just refresh the panel and everything will be all right(add this line into ChangeBG
method): self.panel.refresh()
. I've no idea why the glitch exists though.
As to Rostyslav's answer, thanks a lot mate!
"It's a bad approach to update GUI from worker thread:", I think you mean it's rude to directly insert GUI codes into worker thread(which is exactly what I did in the first example) in terms of thread-safety concern, basically those GUI codes should be wrapped into thread-safe method(which is exactly what I was trying to do in my second example) and then queued into GUI main thread.
I found that there are basically three thread-safe methods as to GUI updating in worker thread: wx.PostEvent
, wx.CallAfter
and wx.CallLater
, but I never liked wx.PostEvent
, it's kind of cumbersome and you have to come up with your own event too, that's why wx.CallAfter
is a better choice for me, it's more pythonic and easy to use, and actually wx.CallAfter
is like a high level wrapper for wx.PostEvent
if you check out the source code in _core.py
:
def CallAfter(callable, *args, **kw):
"""
Call the specified function after the current and pending event
handlers have been completed. This is also good for making GUI
method calls from non-GUI threads. Any extra positional or
keyword args are passed on to the callable when it is called.
:see: `wx.CallLater`
"""
app = wx.GetApp()
assert app is not None, 'No wx.App created yet'
if not hasattr(app, "_CallAfterId"):
app._CallAfterId = wx.NewEventType()
app.Connect(-1, -1, app._CallAfterId,
lambda event: event.callable(*event.args, **event.kw) )
evt = wx.PyEvent()
evt.SetEventType(app._CallAfterId)
evt.callable = callable
evt.args = args
evt.kw = kw
wx.PostEvent(app, evt)
Well, I never tried wx.PostEvent
implementation in my app, but I'm sure it would work as well.
Oh also I found this article very helpful: wxPython and Threads
Upvotes: 1
Reputation: 40755
It's a bad approach to update GUI from worker thread, not event saying it's not thread safe. You have to communicate with main thread to update GUI.
The best way to achieve desired result is to user wx.PostEvent
method. You can create custom events for your needs, inheriting from wx.PyEvent
, and you better inherit threading.Thread
to keep window you want to communicate within that thread class as an instance variable.
The best illustration of how to update GUI having long-running task can be found in wxPython wiki (first example).
Upvotes: 1