How do I use PushEventHandler?

Rick Zoerner wrote:

First of all, in probably 85% of the cases where you would use PushEventHandler in C++ you do not need to do it in Python because you do not have to attach events to methods of the same class where the events happen.

But since you asked: PushEventHandler allows you to define event handlers in a class separate from the window class where they happen, and then set things up such that the new class gets first crack at handling the events for the class. They are often used when you want to add same or similar functionality to a variety of existing classes, without having to derive a new subclass of each of them. For example, suppose you wanted to track mouse down events for all the controls on a panel, and also on the panel itself. In the following code we push a new instance of MouseDownTracker on to each window we are interested in. Since MouseDownTracker binds to the EVT_LEFT_DOWN then its OnMouseDown will be called for each of the controls first, and then since it calls evt.Skip() the system will continue processing the event and default behaviour will happen for the mouse event too.

Since the development cycle 2.9, you will also need to pop the event handler with PopEventHandler. This needs to be done before the wx.Window is destroyed, the example code below illustrates how this can be done. The example was written using wxPython 4.0 using Python 3.6.

Of course, since this is Python we could have done almost exactly the same thing simply by putting OnMouseDown in the MyPanel class, and then using EVT_LEFT_DOWN to hook each of the controls to it, but using a separate wxEvtHandler helps separate the code a bit and helps promote reusability.

   1 #!/usr/bin/env python3
   2 # -*- coding: utf-8 -*-
   3 """Illustration of wx.Window.PushEventHandler and wx.Window.PopEventHandler
   4 
   5 Adds an EvtHandler to the controls in MyPanel. In addition, it also has some
   6 extra handling of the wx.EVT_BUTTON to show how the propagation can become
   7 somewhat more complex. When clicking the button, the button sends the
   8 wx.EVT_BUTTON event to the MouseDownTracker EvtHandler and then propagates to
   9 the MyPanel parent. When the panel gets it then it first sends the event to its
  10 pushed MouseDownTracker EvtHandler and then the panel's own EVT_BUTTON event
  11 binding gets the event and the panel's OnButton is called.
  12 
  13 """
  14 
  15 import sys
  16 import wx
  17 
  18 ID_FOR_THE_BTN = wx.NewIdRef()
  19 
  20 
  21 class MouseDownTracker(wx.EvtHandler):
  22     def __init__(self, log):
  23         wx.EvtHandler.__init__(self)
  24         self.log = log
  25         self.Bind(wx.EVT_LEFT_DOWN, self.OnMouseDown)
  26         # Both MyPanel and MouseDownTracker will handle EVT_BUTTON
  27         self.Bind(wx.EVT_BUTTON, self.OnButton, id=ID_FOR_THE_BTN)
  28 
  29     def OnMouseDown(self, event):
  30         pos = event.GetPosition()
  31         window = self.GetNextHandler()
  32         self.log.write("Mouse down at %s on %s\n" % (pos, window.__class__.__name__))
  33         event.Skip()
  34 
  35     def OnButton(self, event):
  36         wx.MessageBox("Button clicked in EvtHandler")
  37         self.log.write("Button clicked in EvtHandler")
  38         event.Skip()
  39 
  40 
  41 class MyPanel(wx.Panel):
  42     def __init__(self, parent, log):
  43         wx.Panel.__init__(self, parent)
  44 
  45         self.log = log
  46 
  47         self.stxt = wx.StaticText(self, label="Hello")
  48         self.tctl = wx.TextCtrl(self, value="What's up Doc?", size=(150, -1))
  49         self.bttn = wx.Button(self, label="Click Me!", id=ID_FOR_THE_BTN)
  50 
  51         self.stxt.PushEventHandler(MouseDownTracker(log))
  52         self.tctl.PushEventHandler(MouseDownTracker(log))
  53         self.bttn.PushEventHandler(MouseDownTracker(log))
  54 
  55         self.PushEventHandler(MouseDownTracker(log))
  56 
  57         # The pushed handlers should be popped, do so when the Panel is closed
  58         # Define the OnClose function for that, but since the Panel receives no
  59         # EVT_CLOSE, EVT_WINDOW_DESTROY is used instead
  60         self.Bind(wx.EVT_WINDOW_DESTROY, self.OnClose)  # toggle this line when needed, see below
  61 
  62         # Both MyPanel and MouseDownTracker will handle EVT_BUTTON
  63         self.Bind(wx.EVT_BUTTON, self.OnButton, self.bttn)
  64 
  65         sizer = wx.BoxSizer(wx.VERTICAL)
  66         sizer.Add((25, 25))
  67 
  68         row = wx.BoxSizer(wx.HORIZONTAL)
  69         row.Add((25, 1))
  70         row.Add(self.stxt, 0, wx.ALL, 5)
  71         row.Add(self.tctl, 0, wx.ALL, 5)
  72         row.Add(self.bttn, 0, wx.ALL, 5)
  73         sizer.Add(row)
  74         self.SetSizer(sizer)
  75 
  76     def OnButton(self, event):
  77         wx.MessageBox("Button clicked in Panel")
  78         self.log.write("Button clicked in Panel")
  79 
  80     def OnClose(self, event):
  81         self.stxt.PopEventHandler()
  82         self.tctl.PopEventHandler()
  83         self.bttn.PopEventHandler()
  84 
  85         self.PopEventHandler()
  86 
  87 
  88 class MyFrame(wx.Frame):
  89     def __init__(self, parent, log, title="PushEventHandler Tester", size=(400, 350)):
  90         wx.Frame.__init__(self, parent=parent, title=title, size=size)
  91 
  92         self.panel = MyPanel(self, log)
  93 
  94         # The pushed handlers should be popped, do so when the Panel is closed
  95         self.Bind(wx.EVT_CLOSE, self.OnClose)
  96 
  97     def OnClose(self, event):
  98         self.panel.OnClose(event)
  99         event.Skip()
 100 
 101 
 102 def run_with_standard_frame():
 103     app = wx.App(False)
 104     f = wx.Frame(None, title="PushEventHandler Tester", size=(400, 350))
 105     p = MyPanel(f, sys.stdout)
 106     f.Show()
 107     app.MainLoop()
 108 
 109 
 110 def run_with_myframe():
 111     app = wx.App(False)
 112     f = MyFrame(None, sys.stdout)
 113     f.Show()
 114     app.MainLoop()
 115 
 116 
 117 if __name__ == '__main__':
 118     # when running this, make sure line 60 is not commented
 119     # self.Bind(wx.EVT_WINDOW_DESTROY, self.OnClose)
 120     run_with_standard_frame()
 121     # when running this, comment out line 60
 122     # self.Bind(wx.EVT_WINDOW_DESTROY, self.OnClose)
 123     # run_with_myframe()

Comments

Thank you. Very helpful. Here is code updated to wxPython 2.8.9.x

   1 import sys
   2 import wx
   3 
   4 class MouseDownTracker(wx.EvtHandler):
   5     def __init__(self, log):
   6         wx.EvtHandler.__init__(self)
   7         self.log = log
   8         wx.EVT_LEFT_DOWN(self, self.OnMouseDown)
   9 
  10     def OnMouseDown(self, evt):
  11         pos = evt.GetPosition()
  12         window = self.GetNextHandler()
  13         self.log.write("Mouse down at %s on %s\n" % (pos, window.__class__.__name__))
  14         evt.Skip()
  15 
  16 
  17 class MyPanel(wx.Panel):
  18     def __init__(self, parent, log):
  19         wx.Panel.__init__(self, parent)
  20 
  21         stxt = wx.StaticText(self, label="Hello")
  22         tctl = wx.TextCtrl(self, value="What's up Doc?", size=(150, -1))
  23         btn  = wx.Button(self, label="Click Me!")
  24 
  25         stxt.PushEventHandler(MouseDownTracker(sys.stdout))
  26         tctl.PushEventHandler(MouseDownTracker(sys.stdout))
  27         btn.PushEventHandler(MouseDownTracker(sys.stdout))
  28 
  29         sizer = wx.BoxSizer(wx.VERTICAL)
  30         sizer.Add((25, 25))
  31 
  32         row = wx.BoxSizer(wx.HORIZONTAL)
  33         row.Add((25,1))
  34         row.Add(stxt, 0, wx.ALL, 5)
  35         row.Add(tctl, 0, wx.ALL, 5)
  36         row.Add(btn, 0, wx.ALL, 5)
  37         sizer.Add(row)
  38         self.SetSizer(sizer)
  39 
  40 
  41 app = wx.App(False)
  42 f = wx.Frame(None, title="PushEventHandler Tester", size=(400, 350))
  43 p = MyPanel(f, sys.stdout)
  44 p.PushEventHandler(MouseDownTracker(sys.stdout))
  45 f.Show()
  46 app.MainLoop()

PushEventHandler (last edited 2018-07-15 09:14:46 by JanLeys)

NOTE: To edit pages in this wiki you must be a member of the TrustedEditorsGroup.