views:

535

answers:

3

I am trying to learn how to run a thread off the main GUI app to do my serial port sending/receiving while keeping my GUI alive. My best Googling attempts have landed me at the wxpython wiki on: http://wiki.wxpython.org/LongRunningTasks which provides several examples. I have settled on learning the first example, involving starting a worker thread when the particular button is selected.

I am having trouble understanding the custom-event-definition:

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):
        """Init Result Event."""
        wx.PyEvent.__init__(self)
        self.SetEventType(EVT_RESULT_ID)
        self.data = data

Primarily the

def EVT_RESULT(win, func):
    """Define Result Event."""
    win.Connect(-1, -1, EVT_RESULT_ID, func)

I think EVT_RESULT is placed outside the classes so as to make it call-able by both classes (making it global?)

And.. the main GUI app monitors the thread's progress via:

# Set up event handler for any worker thread results
EVT_RESULT(self,self.OnResult)

I also notice that in a lot of examples, when the writer uses

from wx import *

they simply bind things by

EVT_SOME_NEW_EVENT(self, self.handler)

as opposed to

wx.Bind(EVT_SOME_NEW_EVENT, self.handler)

Which doesn't help me understand it any faster. Thanks,

+3  A: 

That's the old style of defining custom events. See the migration guide for more information.

Taken from the migration guide:

If you create your own custom event types and EVT_* functions, and you want to be able to use them with the Bind method above then you should change your EVT_* to be an instance of wx.PyEventBinder instead of a function. For example, if you used to have something like this:

myCustomEventType = wxNewEventType() def EVT_MY_CUSTOM_EVENT(win, id, func): win.Connect(id, -1, myCustomEventType, func)

Change it like so:

myCustomEventType = wx.NewEventType() EVT_MY_CUSTOM_EVENT = wx.PyEventBinder(myCustomEventType, 1)

Here is another post that I made with a couple of example programs that do exactly what you are looking for.

DrBloodmoney
Thanks, I'll be taking a look at that
PPTim
+2  A: 

You can define events like this:

from wx.lib.newevent import NewEvent

ResultEvent, EVT_RESULT = NewEvent()

You post the event like this:

wx.PostEvent(handler, ResultEvent(data=data))

Bind it like this:

def OnResult(event):
    event.data

handler.Bind(EVT_RESULT, OnResult)

But if you just need to make a call from a non-main thread in the main thread you can use wx.CallAfter, here is an example.

Custom events are useful when you don't want to hard code who is responsible for what (see the observer design pattern). For example, lets say you have a main window and a couple of child windows. Suppose that some of the child windows need to be refreshed when a certain change occurs in the main window. The main window could directly refresh those child windows in such a case but a more elegant approach would be to define a custom event and have the main window post it to itself (and not bother who needs to react to it). Then the children that need to react to that event can do it them selves by binding to it (and if there is more than one it is important that they call event.Skip() so that all of the bound methods get called).

Toni Ruža
thanks for the answer, i'll be referring to it as I work on it.
PPTim
A: 

You may want to use Python threads and queues and not custom events. I have a wxPython program (OpenSTV) that loads large files that caused the gui to freeze during the loading. To prevent the freezing, I dispatch a thread to load the file and use a queue to communicate between the gui and the thread (e.g., to communicate an exception to the GUI).

  def loadBallots(self):
    self.dirtyBallots = Ballots()
    self.dirtyBallots.exceptionQueue = Queue(1)
    loadThread = Thread(target=self.dirtyBallots.loadUnknown, args=(self.filename,))
    loadThread.start()

    # Display a progress dialog
    dlg = wx.ProgressDialog(\
      "Loading ballots",
      "Loading ballots from %s\nNumber of ballots: %d" % 
      (os.path.basename(self.filename), self.dirtyBallots.numBallots),
      parent=self.frame, style = wx.PD_APP_MODAL | wx.PD_ELAPSED_TIME
    )
    while loadThread.isAlive():
      sleep(0.1)
      dlg.Pulse("Loading ballots from %s\nNumber of ballots: %d" %
                (os.path.basename(self.filename), self.dirtyBallots.numBallots))
    dlg.Destroy()

if not self.dirtyBallots.exceptionQueue.empty():
  raise RuntimeError(self.dirtyBallots.exceptionQueue.get())
Jeff
I guess queues are the standard way to go for multi-threads; but is it safe to pass a flag around if all i need is a simple 'done' signal? I haven't used queues much; i need to create one for each direction and basically have the receiving end poll the queue regularly?
PPTim
You don't need a queue to determine when the thread is finished. You can use isAlive() as used in the example above. You do need a queue for communication in each direction, and you can see an example of that here (http://code.google.com/p/stv/source/browse/trunk/openstv/OpenSTV.py) in runElection().
Jeff