Handle custom events

I'll be running the wxPython main loop in it's own thread. When the
application generates events, they must fire in the wxPython thread
(both because wxPython is not thread safe, and because accessing
things from many threads when one will work is bad design.)

I could think of a number of ways to do that, but I doubt I will do as
good a job as those who know wxPython intimately.

One way of doing it:
For the event "client connected" I call:

gui.clientConnected(...)

Where gui is a object with a __getattr__ method that simply returns a
function invoke(*args, **kwargs) which will add something like
('clientConnected', args, kwargs) to a Queue.

Now in the onIdle event, I can pull events out of the Queue, and
trigger them on the application object (perhaps by calling a method
named onClientConnected, if it exists)

Simple, and it should work, but there is probably an better way. I
also don't like how the events go to the application object, when I
might want to handle them in a frame nested somewhere.

Thanks,
-Dan

Dan Eloff wrote:

That will certainly eliminate most of the complexity. Now I need a way
to create a custom event that can be handled by any of my wx frames
and add that to the event loop.

As limodou wrote, CallAfter is used in cases where you want to avoid doing that. It simply sets up "deferred" calls to a routine, which the wx main loop processes after it's done with other stuff, so the call happens in the wx thread and not the thread which called CallAfter. Note that this doesn't entirely eliminate the possibility of race conditions (threading synchronization problems), but merely ensures that calls to methods in your widgets happen from the wx thread.

To do custom events is pretty simple (and can be simpler even than my code here):

EVT_MYEVENT = wx.PyEventBinder(wx.NewId(), 0)

class MyEvent(wx.PyEvent):
     '''wrapper object to message into the wx event loop'''

     ID_EVT_MYEVENT = EVT_MYEVENT.evtType[0]

     def __init__(self, msg, *args, **kwargs):
         MyEvent.__init__(self, *args, **kwargs)
         self.SetEventType(self.ID_EVT_MYEVENT)
         self.wrapped = msg

With that you can wrap an arbitrary Python object and have it sent in via the wx event processing like this:

First, make sure someone binds to that event in the usual manner:

     self.Bind(EVT_MYEVENT, self.OnMyEvent) # no extra args needed

Then, from any other thread, create one of these objects and "post" it to your widget:

     event = MyEvent(some_object_to_wrap)
     widget.AddPendingEvent(event)
     # or use something like wx.PostEvent(myapp, event)

The wx MainLoop will now process that in turn, just like other events, so in your OnMyEvent() handler you just do this:

     def OnMyEvent(self, event):
         wrapped_object = event.wrapped
         # process wrapped_object in whatever way you see fit

I have no idea how to use wx.CallAfter to insert a custom event into
the event loop. I also have no idea about the arguments to Bind.

The only thing you were really missing is AddPendingEvent(), or wx.PostEvent(). Note that CallAfter is merely using something very similar to the above code to do its work... and of course you can read the source to see how (in _core.py). Fundamentally AddPendingEvent() is, I believe, *the* mechanism in wxWidgets for having outside threads trigger activity in the GUI, other than using a generic Queue and polling for changes in an OnIdle() handler.

Reading the C++ docs on wxEvtHandler::AddPendingEvent and ProcessEvent, and ::wxPostEvent (in the Miscellaneous functions section) gives good background material.

-Peter

Thanks for your responses limodou and Peter. It clarified a lot of
things for me. I can see there are two mechanisms (really one under
the hood, but two interfaces) for dealing with application events in
the GUI when the GUI is running in a seperate thread. It would seem
that the choice of interface depends on what needs to be done.

To use CallAfter to invoke a method of SomeFrame, I need a reference
to SomeFrame where I signal the event. This usually requires knowing
intimate details about the GUI in the application code, not very
pretty. I imagine it might also be difficult if later SomeOtherFrame
also wants to know about the event, because now I have to edit the
application code to notify both.

It seems that with simply posting an event, the event loop does the
hard work for me, any GUI objects that wants to know about the event
only needs to bind a handler for it. The application doesn't need to
know anything about the GUI (in fact if you replace the
AddPendingEvent() calls with something else, you could swap the GUI
with a web based or console based interface with the application not
caring at all.

It would seem like this is an ideal solution.

Thanks,
-Dan