wxPython: Calling an event manually

2019-01-13 11:47发布

问题:

How can I call a specific event manually from my own code?

回答1:

I think you want wx.PostEvent.

There's also some info about posting events from other thread for long running tasks on the wxPython wiki.



回答2:

Old topic, but I think I've got this figured out after being confused about it for a long time, so if anyone else comes through here looking for the answer, this might help.

To manually post an event, you can use

self.GetEventHandler().ProcessEvent(event)

(wxWidgets docs here, wxPython docs here)

or

wx.PostEvent(self.GetEventHandler(), event)

(wxWidgets docs, wxPython docs)

where event is the event you want to post. Construct the event with e.g.

wx.PyCommandEvent(wx.EVT_BUTTON.typeId, self.GetId())

if you want to post a EVT_BUTTON event. Making it a PyCommandEvent means that it will propagate upwards; other event types don't propagate by default.

You can also create custom events that can carry whatever data you want them to. Here's an example:

myEVT_CUSTOM = wx.NewEventType()
EVT_CUSTOM = wx.PyEventBinder(myEVT_CUSTOM, 1)

class MyEvent(wx.PyCommandEvent):
    def __init__(self, evtType, id):
        wx.PyCommandEvent.__init__(self, evtType, id)
        myVal = None

    def SetMyVal(self, val):
        self.myVal = val

    def GetMyVal(self):
        return self.myVal

(I think I found this code in a mailing list archive somewhere, but I can't seem to find it again. If this is your example, thanks! Please add a comment and take credit for it!)

So now, to Post a custom event:

event = MyEvent(myEVT_CUSTOM, self.GetId())
event.SetMyVal('here is some custom data')
self.GetEventHandler().ProcessEvent(event)

and you can bind it just like any other event

self.Bind(EVT_CUSTOM, self.on_event)

and get the custom data in the event handler

def on_event(self, e):
    data = e.GetMyVal()
    print 'custom data is: {0}'.format(data)

Or include the custom data in the event constructor and save a step:

class MyEvent(wx.PyCommandEvent):
    def __init__(self, evtType, id, val = None):
        wx.PyCommandEvent.__init__(self, evtType, id)
        self.myVal = val

etc.

Hope this is helpful to someone.



回答3:

There's a simple, straightforward way to do it with recent versions of wxPython (see http://wiki.wxpython.org/CustomEventClasses):

   # create event class
   import wx.lib.newevent
   SomeNewEvent, EVT_SOME_NEW_EVENT = wx.lib.newevent.NewEvent()

   # post it, with arbitrary data attached
   wx.PostEvent(target, SomeNewEvent(attr1=foo, attr2=bar))

   # bind it as usual
   target.Bind(EVT_SOME_NEW_EVENT, target.handler)


回答4:

While methods like PostEvent and ProcessEvent, mentioned in other answers, can sort-of do this, be aware that they have significant limitations:

  • You're limited in what events you can easily create. For instance, there's no way to create a wx.KeyEvent with a particular key code, because the constructor doesn't take a key code and there's no setter. Maybe there are workarounds for this (like subclassing KeyEvent), but you're essentially having to hack around the limitations of the framework.
  • Even for events that you can create, like a click event, the behavior you get from programmatically dispatching the event won't match the behaviour you'd get from actually performing the action as a user, because the native handling of the event won't fire. For instance, programmatically firing a click event on a text field won't give the text field focus.

I'd recommend against using PostEvent or ProcessEvent in most circumstances. If you just to make your event handling function to run, then manually creating events is pointless; instead just call the function explicitly. If you want them because you're trying to write an automated UI test, then you probably aren't going to end up with something that satisfies you due to the limitations listed above; instead, I'd recommend writing something that actually simulates clicks and keypresses at the level of the OS/desktop manager using wx.UIActionSimulator. It's kind of gross, and will take control of your system's mouse and keyboard which usually makes such tests inappropriate for a test suite that you run on a development machine... but at least it works, which is more than can be said for dispatching events with PostEvent.



回答5:

You mean you want to have an event dispatch?

::wxPostEvent void wxPostEvent(wxEvtHandler *dest, wxEvent& event)

In a GUI application, this function posts event to the specified dest object using wxEvtHandler::AddPendingEvent. Otherwise, it dispatches event immediately using wxEvtHandler::ProcessEvent. See the respective documentation for details (and caveats).

Include files

<wx/app.h>

wxPython API docs



回答6:

I suppose it depends on what your event is tied to but I needed to do the same with a LabelFrame that displayed data when you selected an item in a List. This is the way I was able to do it:

'This is the line that binds my function "get_selected_location()"
to the action of selecting an item in my List box.'

self.list1_scoring.bind('<<ListboxSelect>>', self.get_selected_location)

So whenever you click on an item in my 'list1_scoring' box, get_selected_location() will run and details regarding the item selected will appear in another LabelFrame. I needed to call this event after an update process is completed to refresh my display window. So I added this line at the end of the function that runs the update.

self.get_selected_location('<<ListboxSelect>>')

This runs get_selected_location() again as if I had just clicked on my List again.