How do I use PushEventHandler?
Rick Zoerner wrote:
- Can someone explain to a non-C++ and new-to-Python programmer how to
implement the concept of PushEventHandler? Slowly, as if to a child, because every attempted explanation I've seen so far only seems to re-iterate exactly what it says in the wxWindows Reference (which I quite obviously don't understand).
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()