Overview
I'm currently looking at sjbrown's tutorial. I was also looking at another one linked from the pygame site on sprites, but decided focusing on the MVC early on would be a benefit. This tutorial feels a bit incomplete to me - it describes the concepts and gives the code for a final demo, but beyond that it only has some pusedocode and brief mentions of ways to make it more advanced. I have gone through the demo, but I'm not 100% sure what the next steps are to extend it for my own use. So I'm going to back up a step, and try to build it up a little at a time, and extend bits as I go to get to my desired functionality.
I'm not going to try to explain the ideas behind MVC, other than to point out that it means Model View Controller. Like the tutorial, I am going to use a mediator - the Event Manager. This manager will pass events between listeners (some of which do not listen passively, but also shout about events of their own). So here's what we know we'll be needing:
- EventManager
- Events
- Listeners
Unlike the tutorial, I'll also be breaking the program into multiple files. I also have different preferences in naming conventions, so those will differ too. Sorry if that adds to confusion - but I need to be able to read my code. The first file (myevents.py) will contain the basic classes in the above list. I'm tempted to just post the entire file at once, but I'll refrain, and will instead go over it in the order I wrote it. Remember that this is based off the tutorial, but it's not just a direct copy with extra explaination, although most of the base classes won't differ from the tutorial.
Event
First up is the Event class. It's pretty simple so I'll explain it in one chunk:
class Event: """ A superclass for events to be handled by EventManager """ def __init__(self): """Set the name of the Event. Subclasses may have other attributes.""" self.name = "Generic" def __repr__(self): """Custom representation for the event.""" return self.name + " Event"
This simple class is not intended to be used directly. It's simply a common base for all the events - this allows some common functionality (not much, since it's so short) and for all the events to pass the isinstance test. I've been told that python should avoid checking types though, so while I'll stick with the class structure, I'll try to avoid using those tests unless I can't come up with something better. All the init function does is set the name. This isn't very spectacular since any events that descend from this class will want a more specific name. I added in a __repr__(self) function. This handy method will give me custom output when I do silly things like print event.
EventManager
Now for the EventManager. There are a couple of pieces to this (although it's not many more lines than Event, there's more going on), so I'll break it up a bit, posting just the methods in the EventManager.
def __init__(self): """Set up a dictionary to hold references to listeners""" from weakref import WeakKeyDictionary self.listeners = WeakKeyDictionary()The constructor sets up a dictionary of listeners. It uses a Weak Key Dictionary so that the Event Manager won't interfer with garbage collection. The listeners attribute will store references to listener objects. There's no need for my Listener class (which I'll get to below), I just thought I'd try to be consistent.
def register(self, listener): """Register a listener. It must implement the function notify(event)""" self.listeners[ listener ] = 1 def unregister(self, listener): """Unregister a listener""" if listener in self.listeners: del self.listeners[ listener ]Next we add functions to register and unregister listeners. Although I provide the method, I won't be using the unregister method until I've surpassed the tutorial (it doesn't use it either). If a listener is garbage collected without unregistering itself, weak keys will save us from errors too (at least, that's how I gather it works - I'm not going to worry about that), so there's really no explicit need to manually unregister listeners. All we do in register is store a reference to the listening object. We'll be needing that reference shortly.
def post(self, event): """Inform all listeners about an event.""" for listener in self.listeners: listener.notify(event)The last method in our Event Manager class is post. In many ways, this is the meat of the class. It sends the provided event to all its listeners via the notify method. This is the only requirement of listeners - that it implement this method.
Listener
Now we'll address Listeners. I'm going to skip the code here, as it's posted in full below. I'll provide a generic Listener class, which is similar in nature to the Event class - it is meant to be used as bare minimum interface for listeners. As such, all it needs to do is implement an empty notify function. The Listener also has an __init__, which registers itself with the provided Event Manager. For a noisy listener (one that generates methods), it will need to also store a reference to the manager, so that when an event is generated it can post the event.
The last section of the file is a small test. In short, if the file is run as a program (as opposed to being imported into another program), it will create an event instance, a manager instance, and a Test listener class and instance. The manager posts the event, unregisters the test listener, and posts the event again.
Generic Event I have recieved a Generic Event
Full Code (myevents.py)
class Listener: """ A superclass for listeners. Note that a listener must save the reference to the manager if it needs to send events, as opposed to just recieve them. """ def __init__(self, manager): """Register with the manager automatically.""" manager.register(self) def notify(self, event): """Default implementation for recieving events.""" pass class Event: """ A superclass for events to be handled by EventManager """ def __init__(self): """Set the name of the Event. Subclasses may have other attributes.""" self.name = "Generic" def __repr__(self): """Custom representation for the event.""" return self.name + " Event" class EventManager: """ A manager for coordinating communication between Model, View and Controller """ def __init__(self): """Set up a dictionary to hold references to listeners""" from weakref import WeakKeyDictionary self.listeners = WeakKeyDictionary() def register(self, listener): """Register a listener. It must implement the function notify(event)""" self.listeners[ listener ] = 1 def unregister(self, listener): """Unregister a listener""" if listener in self.listeners: del self.listeners[ listener ] def post(self, event): """Inform all listeners about an event.""" for listener in self.listeners: listener.notify(event) """Test the contents of this file.""" if __name__ == "__main__": e = Event() print e em = EventManager() class Test(Listener): """Uses the default init.""" def notify(self, event): print "I have recieved a "+ str(event) test = Test(em) em.post(e) em.unregister(test) em.post(e)
Labels: MVC, pygame, python, series 1, tutorial
Leave one.