== 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.


{{{#!highlight python
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""Illustration of wx.Window.PushEventHandler and wx.Window.PopEventHandler

Adds an EvtHandler to the controls in MyPanel. In addition, it also has some
extra handling of the wx.EVT_BUTTON to show how the propagation can become
somewhat more complex. When clicking the button, the button sends the
wx.EVT_BUTTON event to the MouseDownTracker EvtHandler and then propagates to
the MyPanel parent. When the panel gets it then it first sends the event to its
pushed MouseDownTracker EvtHandler and then the panel's own EVT_BUTTON event
binding gets the event and the panel's OnButton is called.

"""

import sys
import wx

ID_FOR_THE_BTN = wx.NewIdRef()


class MouseDownTracker(wx.EvtHandler):
    def __init__(self, log):
        wx.EvtHandler.__init__(self)
        self.log = log
        self.Bind(wx.EVT_LEFT_DOWN, self.OnMouseDown)
        # Both MyPanel and MouseDownTracker will handle EVT_BUTTON
        self.Bind(wx.EVT_BUTTON, self.OnButton, id=ID_FOR_THE_BTN)

    def OnMouseDown(self, event):
        pos = event.GetPosition()
        window = self.GetNextHandler()
        self.log.write("Mouse down at %s on %s\n" % (pos, window.__class__.__name__))
        event.Skip()

    def OnButton(self, event):
        wx.MessageBox("Button clicked in EvtHandler")
        self.log.write("Button clicked in EvtHandler")
        event.Skip()


class MyPanel(wx.Panel):
    def __init__(self, parent, log):
        wx.Panel.__init__(self, parent)

        self.log = log

        self.stxt = wx.StaticText(self, label="Hello")
        self.tctl = wx.TextCtrl(self, value="What's up Doc?", size=(150, -1))
        self.bttn = wx.Button(self, label="Click Me!", id=ID_FOR_THE_BTN)

        self.stxt.PushEventHandler(MouseDownTracker(log))
        self.tctl.PushEventHandler(MouseDownTracker(log))
        self.bttn.PushEventHandler(MouseDownTracker(log))

        self.PushEventHandler(MouseDownTracker(log))

        # The pushed handlers should be popped, do so when the Panel is closed
        # Define the OnClose function for that, but since the Panel receives no
        # EVT_CLOSE, EVT_WINDOW_DESTROY is used instead
        self.Bind(wx.EVT_WINDOW_DESTROY, self.OnClose)  # toggle this line when needed, see below

        # Both MyPanel and MouseDownTracker will handle EVT_BUTTON
        self.Bind(wx.EVT_BUTTON, self.OnButton, self.bttn)

        sizer = wx.BoxSizer(wx.VERTICAL)
        sizer.Add((25, 25))

        row = wx.BoxSizer(wx.HORIZONTAL)
        row.Add((25, 1))
        row.Add(self.stxt, 0, wx.ALL, 5)
        row.Add(self.tctl, 0, wx.ALL, 5)
        row.Add(self.bttn, 0, wx.ALL, 5)
        sizer.Add(row)
        self.SetSizer(sizer)

    def OnButton(self, event):
        wx.MessageBox("Button clicked in Panel")
        self.log.write("Button clicked in Panel")

    def OnClose(self, event):
        self.stxt.PopEventHandler()
        self.tctl.PopEventHandler()
        self.bttn.PopEventHandler()

        self.PopEventHandler()


class MyFrame(wx.Frame):
    def __init__(self, parent, log, title="PushEventHandler Tester", size=(400, 350)):
        wx.Frame.__init__(self, parent=parent, title=title, size=size)

        self.panel = MyPanel(self, log)

        # The pushed handlers should be popped, do so when the Panel is closed
        self.Bind(wx.EVT_CLOSE, self.OnClose)

    def OnClose(self, event):
        self.panel.OnClose(event)
        event.Skip()


def run_with_standard_frame():
    app = wx.App(False)
    f = wx.Frame(None, title="PushEventHandler Tester", size=(400, 350))
    p = MyPanel(f, sys.stdout)
    f.Show()
    app.MainLoop()


def run_with_myframe():
    app = wx.App(False)
    f = MyFrame(None, sys.stdout)
    f.Show()
    app.MainLoop()


if __name__ == '__main__':
    # when running this, make sure line 60 is not commented
    # self.Bind(wx.EVT_WINDOW_DESTROY, self.OnClose)
    run_with_standard_frame()
    # when running this, comment out line 60
    # self.Bind(wx.EVT_WINDOW_DESTROY, self.OnClose)
    # run_with_myframe()
}}}


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

{{{
#!python
import sys
import wx

class MouseDownTracker(wx.EvtHandler):
    def __init__(self, log):
        wx.EvtHandler.__init__(self)
        self.log = log
        wx.EVT_LEFT_DOWN(self, self.OnMouseDown)

    def OnMouseDown(self, evt):
        pos = evt.GetPosition()
        window = self.GetNextHandler()
        self.log.write("Mouse down at %s on %s\n" % (pos, window.__class__.__name__))
        evt.Skip()


class MyPanel(wx.Panel):
    def __init__(self, parent, log):
        wx.Panel.__init__(self, parent)

        stxt = wx.StaticText(self, label="Hello")
        tctl = wx.TextCtrl(self, value="What's up Doc?", size=(150, -1))
        btn  = wx.Button(self, label="Click Me!")

        stxt.PushEventHandler(MouseDownTracker(sys.stdout))
        tctl.PushEventHandler(MouseDownTracker(sys.stdout))
        btn.PushEventHandler(MouseDownTracker(sys.stdout))

        sizer = wx.BoxSizer(wx.VERTICAL)
        sizer.Add((25, 25))

        row = wx.BoxSizer(wx.HORIZONTAL)
        row.Add((25,1))
        row.Add(stxt, 0, wx.ALL, 5)
        row.Add(tctl, 0, wx.ALL, 5)
        row.Add(btn, 0, wx.ALL, 5)
        sizer.Add(row)
        self.SetSizer(sizer)


app = wx.App(False)
f = wx.Frame(None, title="PushEventHandler Tester", size=(400, 350))
p = MyPanel(f, sys.stdout)
p.PushEventHandler(MouseDownTracker(sys.stdout))
f.Show()
app.MainLoop()
}}}