This is a short overview of how event propagation works in wxPython, demonstrated on a small example.
What Objects are Involved
- An event is raised.
- It propagates around.
As soon as something handles it, it stops propagating. (It's done.)
- But if the handler calls "Skip" on the event, it keeps on going.
Technically, what happens is this:
An event handler is raised, and posted to a wxEventHandler, such as a wxWindow.
The wxEventHandler goes through a series of steps, (in ProcessEvent,) looking for something to handle the event.
- When a qualified handler (matching the Event Type, with a valid matching ID) is found, it gets to handle the event.
- Unless the handler called ".Skip()" on the event, event propagation ends.
If something did call Skip, event propagation continues.
What are the steps that the wxEventHandler goes through?
If the handler is disabled, it sends the event to wxApp::ProcessEvent, and stops.
If the handler is attached to a wxWindow, and the window has a validator attached, then the validator gets a crack at the event.
- Then the handler's event table is searched.
- Then the base classes event handler's event table is searched. (And so on.)
If it's a wxWindow that has had additional wxEventHandler instances attached to it with PushEventHandler, then those get their turn.
If the event ShouldPropagate, (which is true of, by default, only wxCommandEvents, such as button clicks, then the event is propagated to parent windows. (Within limits, described below.)
Finally, wxApp::ProcessEvent gets a crack at the event.
This is fairly complicated; If you are not using validators, and you are not using subclasses, and if you are not pushing special purpose event handlers, or any other of these weird things, then simply consider:
- The event goes to the window.
- If the event isn't handled, it goes to the parent window.
Finally, wxApp::ProcessEvent has a go at it.
- The first handler that gets the event will stop the event.
- Unless the handler calls ".Skip()" on the event.
Some Things about Window Hierarchy Propagation
Events do not propagate beyond dialogs. They do propagate beyond Frames. This is because there are so many dialogs, but Frames tend to be a bit more "controlled."
You can SetExtraStyle(wx.WS_EX_BLOCK_EVENTS) to make another window block events, as if it were a dialog.
Propagation Level. In wxPython Events will propagate up the window hierarchy if they have a propagation level >0. If that is the case, then the ShouldPropagate method of the wx.Event returns True. If you want to get the numerical propagation level of the event, use the StopPropagation method. It returns the propagation level. (Don't forget to call ResumePropagation afterwards).
The propagation level determines how far the event will propagate. Suppose the propagation level is 1. Then the event will only propagate to a window's parent. If it has a level of 2, it will propagate to the window's parent and to its grandparent. If it has a level of sys.maxint, it will propagate up the whole window hierarchy. (Stopping at Dialog boundaries, but not Frame boundaries.)
By default, only wx.CommandEvents will propagate.
A typical example of an event not propagated is the wx.EVT_KEY_DOWN. It is send only to the control having the focus, and will not propagate to its parent.
To see what is happening have a look at the following example and play with it. Just set some different propagation levels in the OnKeyText method and see what happens.
1 import wx 2 3 class MainFrame(wx.Frame): 4 def __init__(self, parent, ID, title): 5 wx.Frame.__init__(self, parent, ID, title, 6 wx.DefaultPosition, wx.Size(200, 100)) 7 8 Panel = wx.Panel(self, -1) 9 TopSizer = wx.BoxSizer(wx.VERTICAL) 10 Panel.SetSizer(TopSizer) 11 12 Text = wx.TextCtrl(Panel, -1, "Type text here") 13 TopSizer.Add(Text, 1, wx.EXPAND) 14 15 Text.Bind(wx.EVT_KEY_DOWN, self.OnKeyText) 16 Panel.Bind(wx.EVT_KEY_DOWN, self.OnKeyPanel) 17 self.Bind(wx.EVT_KEY_DOWN, self.OnKeyFrame) 18 19 def OnKeyText(self, event): 20 print "OnKeyText" 21 print "\tShould Propagate %i" % event.ShouldPropagate() 22 Level = event.StopPropagation() 23 print "\tPropagate level %i" % Level 24 # Try: event.ResumePropagation(x), x=1,2,3,... 25 event.ResumePropagation(Level) 26 event.Skip() 27 28 def OnKeyPanel(self, event): 29 print "OnKeyPanel" 30 print "\tShould Propagate %i" % event.ShouldPropagate() 31 Level = event.StopPropagation() 32 print "\tPropagate level %i" % Level 33 event.ResumePropagation(Level) 34 event.Skip() 35 36 def OnKeyFrame(self, event): 37 print "OnKeyFrame" 38 print "\tShould Propagate %i" % event.ShouldPropagate() 39 Level = event.StopPropagation() 40 print "\tPropagate level %i" % Level 41 event.ResumePropagation(Level) 42 event.Skip() 43 44 class MyApp(wx.App): 45 def OnInit(self): 46 Frame = MainFrame(None, -1, "Event Propagation Demo") 47 Frame.Show(True) 48 self.SetTopWindow(Frame) 49 return True 50 51 if __name__ == '__main__': 52 App = MyApp(0) 53 App.MainLoop()
LionsGiantNotes -- a giant diagram that loosely describes a lot of this
Any comments and/or corrections welcome. Feel free to edit this page, or send comments to firstname.lastname@example.org.