Shane
Shane

Reputation: 4983

wxpython frame initialization error with threads

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

Answers (2)

Shane
Shane

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

Rostyslav Dzinko
Rostyslav Dzinko

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

Related Questions