This question addresses the very specific question of the EvtHandler
required to post en event using wxPython.
I'm using Python 2.7. In the example below I have two kinds of events:
StartMeausuringEvent
is triggered within a wx.Panel
derived object (DisplayPanel
), hence I use self.GetEventHandler()
, this works, even though the binding is in the parent object.NewResultEvent
is triggered from a threading.Thread
derived object (MeasurementThread
), and it has no event handler, hence I have been forced to send an event handler along, and I chose the event handler of the wx.Frame
derived object (MeasurementFrame
), as this is also the object that "cathes" the event eventually.WHY does the first one work, since the object ? And more generally, how tight has the connection between the event handler and the object that "catches" the event has to be?
import wx
import time
import threading
import numpy as np
import wx.lib.newevent
# Create two new event types
StartMeasuringEvent, EVT_START_MEASURING = wx.lib.newevent.NewCommandEvent()
NewResultEvent, EVT_NEW_RESULT = wx.lib.newevent.NewEvent()
class MeasurementFrame(wx.Frame):
def __init__(self, parent):
wx.Frame.__init__(self, parent, title="Lets measure!", size=(300, 300))
# Layout
self.view = DisplayPanel(self)
sizer = wx.BoxSizer(wx.HORIZONTAL)
sizer.Add(self.view, 1, wx.ALIGN_CENTER)
self.SetSizer(sizer)
self.SetMinSize((300, 300))
self.CreateStatusBar()
# Create a new measuring device object to embody a physical measuring device
self.device = MeasuringDevice(self.GetEventHandler(), amplification=10)
# Bind events to the proper handlers
self.Bind(EVT_START_MEASURING, self.OnStartMeasurement)
self.Bind(EVT_NEW_RESULT, self.OnNewResult)
def OnStartMeasurement(self, evt):
self.view.SetStatus("Measuring")
self.device.start_measurement()
def OnNewResult(self, evt):
self.view.SetStatus("New Result!")
print evt.result_data
class DisplayPanel(wx.Panel):
def __init__(self, parent):
wx.Panel.__init__(self, parent)
# Attributes
self._result_display = wx.StaticText(self, label="0")
self._result_display.SetFont(wx.Font(16, wx.MODERN, wx.NORMAL, wx.NORMAL))
self._status_display = wx.StaticText(self, label="Ready!")
self._status_display.SetFont(wx.Font(8, wx.MODERN, wx.NORMAL, wx.NORMAL))
# Layout
sizer = wx.BoxSizer(wx.VERTICAL)
button1 = wx.Button(self, wx.NewId(), "Increment Counter")
# button2 = wx.Button(self, wx.NewId(), "Decrease Counter")
sizer.AddMany([(button1, 0, wx.ALIGN_CENTER),
# (button2, 0, wx.ALIGN_CENTER),
((15, 15), 0),
(self._result_display, 0, wx.ALIGN_CENTER),
(self._status_display, 0, wx.ALIGN_LEFT)])
self.SetSizer(sizer)
# Event Handlers
button1.Bind(wx.EVT_BUTTON, self.OnButton)
def OnButton(self, evt):
""" Send an event ... but to where? """
wx.PostEvent(self.GetEventHandler(), StartMeasuringEvent(self.GetId()))
def SetStatus(self, status=""):
""" Set status text in the window"""
self._status_display.SetLabel(status)
class MeasuringDevice:
def __init__(self, event_handler, amplification=10):
self.amplification = amplification
self.event_handler = event_handler # The object to which all event are sent
def start_measurement(self, repetitions=1):
""" Start a thread that takes care of obtaining a measurement """
for n in range(repetitions):
worker = MeasurementThread(self.event_handler, self.amplification)
worker.start()
class MeasurementThread(threading.Thread):
def __init__(self, event_handler, amplification):
threading.Thread.__init__(self)
self.event_handler = event_handler
self.amplification = amplification
def run(self):
print("Beginning simulated measurement")
time.sleep(1) # My simulated calculation time
result = np.random.randn()*self.amplification
evt = NewResultEvent(result_data=result)
wx.PostEvent(self.event_handler, evt)
print("Simulated Measurement done!")
if __name__ == '__main__':
my_app = wx.App(False)
my_frame = MeasurementFrame(None)
my_frame.Show()
my_app.MainLoop()
The first one works because command events automatically propagate up the containment hierarchy (a.k.a the window parent/child connections) until there is a matching binding found or until it reaches a top-level parent window like a frame or dialog. See http://wiki.wxpython.org/self.Bind%20vs.%20self.button.Bind and also http://wxpython.org/OSCON2006/wxPython-intro-OSCON2006.pdf starting at slide 53 for more explanation.
There is a specific path that the event processor will search when looking for a matching event binding. In a nutshell: as mentioned above, event types that derive directly or indirectly from wx.CommandEvent
will continue searching up through the parents until a match is found, and for other types of events it will only look for bindings in the window that the event was sent to and will not propagate to parent windows. If a handler calls event.Skip()
then when the handler returns the event processor will continue looking for another matching handler (still limited to the same window for non-command events.) There is a lot more to it than that, (see slide 52 in the PDF linked above) but if you understand this much then it will be enough for almost every event binding/handling situation you'll likely encounter.
BTW, the wx.Window
class derives from wx.EvtHandler
so unless you've done something like PushEventHandler
then window.GetEventHandler()
will return the window itself. So unless you need to push new event handler instances (most Python programs don't, it is used more often in C++) then you can save some typing by just using window
instead of window.GetEventHandler()
.