I am interested in comments on the following wxPython code. I have a small class that I wrote to handle the problem of notifyng multiple listeners of an event. I know that there are other ways to do this (namely, Robb Shecter and Robin Dunn's wxPython.lib.evtmgr module), but, I thought I'd offer this one as well.
It all revolves around the CDispatcher class - which follows.
-----8<-----8<-----8<-----8<-----8<-----8<-----8<-----
from wxPython.wx import *
import weakref
class CDispatcher(wxEvtHandler):
def __init__(self):
wxEvtHandler.__init__(self)
# A WeakValueDictionary to hold all event sources, types, listeners, and functions
self._table = weakref.WeakKeyDictionary()
def add_listener(self, event_source, event_type, listener, func):
# Connect a listener function to a particular object/event
# Register event source object, if not registered.
table = self._table
if not table.has_key(event_source): table[event_source] = {}
# Register event type, if not registered.
source_dict = table[event_source]
if not source_dict.has_key(event_type):
source_dict[event_type] = weakref.WeakKeyDictionary()
# Register listener and function
source_dict[event_type][listener] = func
def dispatch(self, event):
# Dispatch events to all listening functions, as appropriate.
event_source = event.GetEventObject() # Get the event source object
event_type = event.GetEventType() # Get the event type
try:
functions = self._table[event_source][event_type].values()
for f in functions: f(event) # Call all listener functions
except:
pass # Ignore errors. Is there a more appropriate behavior?
-----8<-----8<-----8<-----8<-----8<-----8<-----8<-----
This class allows a single event to be dispatched to multiple listeners. The conept is very simple. First a global instance of a CDispatcher class is created. There can be several CDispatcher instances, it is only necessary that a listener be added to the same dispatcher to which the original event is redirected. A listener is added to the dispatcher using its add_listener method. This method takes four parameters: the event source, the event type that will be generated by the source, the listener object, and the function that will be called when the event occurs. The add_listener function will add this information to a table that is stored in the _table variable in the CDispatcher instance. (More on that in a moment.) Events are redirected to the dispatcher by connecting to them and calling the dispatcher's 'dispatch' method. This method gets the event's source object and event type from the event, and uses that information to look up all related listener functions in the dispatcher's table. Each registered listener function is then called with the original event object (should this be a clone of the event object?).
The dispatcher's table is made up of three levels of dictionaries. Keys in the outer-most dictionary are the objects from which events originate (the event_source objects.) Values associated with event_source object keys in the outer-most dictionary are second-level dictionaries that have the event_type id as dictionary keys. Values associated with event_type id keys are third-level dictionaries that have as keys the listener objects. Values of keys in the inner-most dictionary (third level) are the functions to call when an event from the event_source of type event_type is received. One additional item to notice is that the inner-most and outer-most dictionaries are actually WeakKeyDictionary objects. This is nice because when either the event_source or listener goes away, the table is automatically cleaned up.
Here is an example:
-----8<-----8<-----8<-----8<-----8<-----8<-----8<-----
from wxPython.wx import *
from dispatcher import *
# Create a dispatcher
dispatcher = CDispatcher()
class CTextX(wxTextCtrl):
def __init__(self, parent, id, pos):
wxTextCtrl.__init__(self, parent, id, pos=pos)
self.id = id
def OnClick(self, event):
# Display this text control's id
self.SetValue('TextCtrl %d' % self.GetId())
def OnMotion(self, event):
# Display x position of mouse motion event
self.SetValue( str( event.GetX() ) )
class CTextY(wxTextCtrl):
def __init__(self, parent, id, pos):
wxTextCtrl.__init__(self, parent, id, pos=pos)
self.id = id
def OnClick(self, event):
# Display this text control's id
self.SetValue('TextCtrl %d' % self.GetId())
def OnMotion(self, event):
# Display y position of mouse motion event
self.SetValue( str( event.GetY() ) )
class CFrame(wxFrame):
def __init__(self):
wxFrame.__init__(self, None, -1, "Test", wxDefaultPosition, (200, 120))
# Widget IDs
BUTTON1_ID = 1001
TEXTX_ID = 1002
TEXTY_ID = 1003
# Widgets
self.panel = wxPanel( self, -1 )
self.button1 = wxButton( self.panel, BUTTON1_ID, "Test", (5, 5) )
self.textx = CTextX( self.panel, TEXTX_ID, pos=(5, 30) )
self.texty = CTextY( self.panel, TEXTY_ID, pos=(5, 55) )
# Redirect events to the dispatcher
EVT_BUTTON( self, BUTTON1_ID, dispatcher.dispatch )
EVT_MOTION( self.panel, dispatcher.dispatch )
# Add multiple listeners for button click event
dispatcher.add_listener( self.button1, wxEVT_COMMAND_BUTTON_CLICKED,
self.textx, self.textx.OnClick )
dispatcher.add_listener( self.button1, wxEVT_COMMAND_BUTTON_CLICKED,
self.texty, self.texty.OnClick )
# Add multiple listeners for panel mouse motion event
dispatcher.add_listener( self.panel, wxEVT_MOTION,
self, self.OnMotion )
dispatcher.add_listener( self.panel, wxEVT_MOTION,
self.textx, self.textx.OnMotion )
dispatcher.add_listener( self.panel, wxEVT_MOTION,
self.texty, self.texty.OnMotion )
def OnMotion(self, event):
print event.GetX(), event.GetY()
class CApp(wxApp):
def OnInit(self):
self.frame = CFrame()
self.SetTopWindow(self.frame)
self.frame.Raise()
self.frame.Show(True)
return True
if __name__ == '__main__':
app = CApp(0)
app.MainLoop()
-----8<-----8<-----8<-----8<-----8<-----8<-----8<-----
In the example there is a wxFrame/wxPanel with a single wxButton and two subclasses of wxTextCtrl objects. The wxTextCtrl subclasses are called CTextX and CTextY. Each implements the OnMotion event differently. CTextX gets and displays the mouse x coordinate, and CTextY gets and displays the mouse y coordinate. The frame redirects the button click and mouse motion events to the dispatcher object by connecting the events with the dispatcher.dispatch method (See the EVT_BUTTON and EVT_MOTION calls in the CFrame.__init__ method.) Both the CTextX and CTextY objects listen for the button click event (that is, the event of type wxEVT_COMMAND_BUTTON_CLICKED). When found, the dispatcher dispatches the event to each wxTextCtrl's OnClick method, which displays the control's id in the wxTextCtrl display. The CTextX and CTextY objects, as well as the wxFrame, listen for the mouse motion event (that is, the event of type wxEVT_MOTION). The wxFrame prints the mouse coordinates to the console. The two wxTextCtrl objects display either the x or y mouse coordinate in the wxTextCtrl display.
Any comments would be appreciated.
Mark
···
_________________________________________________________________
The new MSN 8: smart spam protection and 2 months FREE* http://join.msn.com/?page=features/junkmail