The pyglet.window, pyglet.media, pyglet.app and pyglet.text modules make use of a consistent event pattern, which provides several ways to attach event handlers to objects. You can also reuse this pattern in your own classes easily.
Throughout this documentation, an “event dispatcher” is an object that has events it needs to notify other objects about, and an “event handler” is some code that can be attached to a dispatcher.
An event handler is simply a function with a formal parameter list corresponding to the event type. For example, the Window.on_resize event has the parameters (width, height), so an event handler for this event could be:
def on_resize(width, height): pass
The Window class subclasses EventDispatcher, which enables it to have event handlers attached to it. The simplest way to attach an event handler is to set the corresponding attribute on the object:
window = pyglet.window.Window() def on_resize(width, height): pass window.on_resize = on_resize
While this technique is straight-forward, it requires you to write the name of the event three times for the one function, which can get tiresome. pyglet provides a shortcut using the event decorator:
window = window.Window() @window.event def on_resize(width, height): pass
This is not entirely equivalent to setting the event handler directly on the object. If the object already had an event handler, using @event will add the handler to the object, rather than replacing it. The next section describes this functionality in detail.
As shown in Subclassing Window, you can also attach event handlers by subclassing the event dispatcher and adding the event handler as a method:
class MyWindow(pyglet.window.Window): def on_resize(self, width, height): pass
It is often convenient to attach more than one event handler for an event. EventDispatcher allows you to stack event handlers upon one another, rather than replacing them outright. The event will propogate from the top of the stack to the bottom, but can be stopped by any handler along the way.
To push an event handler onto the stack, use the push_handlers method:
def on_key_press(symbol, modifiers): if symbol == key.SPACE fire_laser() window.push_handlers(on_key_press)
As a convenience, the @event decorator can be used as an alternative to push_handlers:
@window.event def on_key_press(symbol, modifiers): if symbol == key.SPACE fire_laser()
One use for pushing handlers instead of setting them is to handle different parameterisations of events in different functions. In the above example, if the spacebar is pressed, the laser will be fired. After the event handler returns control is passed to the next handler on the stack, which on a Window is a function that checks for the ESC key and sets the has_exit attribute if it is pressed. By pushing the event handler instead of setting it, the application keeps the default behaviour while adding additional functionality.
You can prevent the remaining event handlers in the stack from receiving the event by returning a true value. The following event handler, when pushed onto the window, will prevent the escape key from exiting the program:
def on_key_press(symbol, modifiers): if symbol == key.ESCAPE: return True window.push_handlers(on_key_press)
You can push more than one event handler at a time, which is especially useful when coupled with the pop_handlers function. In the following example, when the game starts some additional event handlers are pushed onto the stack. When the game ends (perhaps returning to some menu screen) the handlers are popped off in one go:
def start_game(): def on_key_press(symbol, modifiers): print 'Key pressed in game' return True def on_mouse_press(x, y, button, modifiers): print 'Mouse button pressed in game' return True window.push_handlers(on_key_press, on_mouse_press) def end_game(): window.pop_handlers()
Note that you do not specify which handlers to pop off the stack – the entire top “level” (consisting of all handlers specified in a single call to push_handlers) is popped.
You can apply the same pattern in an object-oriented fashion by grouping related event handlers in a single class. In the following example, a GameEventHandler class is defined. An instance of that class can be pushed on and popped off of a window:
class GameEventHandler(object): def on_key_press(self, symbol, modifiers): print 'Key pressed in game' return True def on_mouse_press(self, x, y, button, modifiers): print 'Mouse button pressed in game' return True game_handlers = GameEventHandler() def start_game() window.push_handlers(game_handlers) def stop_game() window.pop_handlers()
pyglet provides only the Window and Player event dispatchers, but exposes a public interface for creating and dispatching your own events.
The steps for creating an event dispatcher are:
In the following example, a hypothetical GUI widget provides several events:
class ClankingWidget(pyglet.event.EventDispatcher): def clank(self): self.dispatch_event('on_clank') def click(self, clicks): self.dispatch_event('on_clicked', clicks) def on_clank(self): print 'Default clank handler.' ClankingWidget.register_event_type('on_clank') ClankingWidget.register_event_type('on_clicked')
Event handlers can then be attached as described in the preceding sections:
widget = ClankingWidget() @widget.event def on_clank(): pass @widget.event def on_clicked(clicks): pass def override_on_clicked(clicks): pass widget.push_handlers(on_clicked=override_on_clicked)
The EventDispatcher takes care of propogating the event to all attached handlers or ignoring it if there are no handlers for that event.
There is zero instance overhead on objects that have no event handlers attached (the event stack is created only when required). This makes EventDispatcher suitable for use even on light-weight objects that may not always have handlers. For example, Player is an EventDispatcher even though potentially hundreds of these objects may be created and destroyed each second, and most will not need an event handler.
The Observer design pattern, also known as Publisher/Subscriber, is a simple way to decouple software components. It is used extensively in many large software projects; for example, Java’s AWT and Swing GUI toolkits and the Python logging module; and is fundamental to any Model-View-Controller architecture.
EventDispatcher can be used to easily add observerable components to your application. The following example recreates the ClockTimer example from Design Patterns (pages 300-301), though without needing the bulky Attach, Detach and Notify methods:
# The subject class ClockTimer(pyglet.event.EventDispatcher): def tick(self): self.dispatch_event('on_update') ClockTimer.register_event_type('on_update') # Abstract observer class class Observer(object): def __init__(self, subject): subject.push_handlers(self) # Concrete observer class DigitalClock(Observer): def on_update(self): pass # Concrete observer class AnalogClock(Observer): def on_update(self): pass timer = ClockTimer() digital_clock = DigitalClock(timer) analog_clock = AnalogClock(timer)
The two clock objects will be notified whenever the timer is “ticked”, though neither the timer nor the clocks needed prior knowledge of the other. During object construction any relationships between subjects and observers can be created.
pyglet uses a modified version of Epydoc to construct its API documentation. One of these modifications is the inclusion of an “Events” summary for event dispatchers. If you plan on releasing your code as a library for others to use, you may want to consider using the same tool to document code.
The patched version of Epydoc is included in the pyglet repository under trunk/tools/epydoc (it is not included in distributions). It has special notation for document event methods, and allows conditional execution when introspecting source code.
If the sys.is_epydoc attribute exists and is True, the module is currently being introspected for documentation. pyglet places event documentation only within this conditional, to prevent extraneous methods appearing on the class.
To document an event, create a method with the event’s signature and add a blank event field to the docstring:
import sys class MyDispatcher(object): if getattr(sys, 'is_epydoc'): def on_update(): '''The object was updated. :event: '''
Note that the event parameters should not include self. The function will appear in the “Events” table and not as a method.