The previous section gave you a rough overview about how to use
the OcempGUI event system with your own objects. As you might
have seen, using only the notify()
method and lengthy if-else conditions might not always be the
best idea. Also, overriding the
notify()
method whenever the
functionality of an object should change is not the best,
especially, if it should be runtime dependant.
You might consider using the BaseObject
from the ocempgui.object
module
instead. It offers ready to use signal slots and methods to bind
callbacks at runtime, improving its and your flexibility without
big effort.
The BaseObject
is event driven, which
means, that it acts and reacts upon events it receives and
that it can raise events. Hereby you have to distinguish
between Signals and
Events. Signals are
certain identifiers, a BaseObject
can
listen to, while Events are a combination
of a Signal and additional data.
What does that mean in practice? As you already know from
the section called “Building event driven systems”, objects will register
themselves at an event manager with a specific signal, they
listen to. Events in turn carry a specific signal id and
additional data. A BaseObject
will
register itself at an event manager with its signals. If
events are passed to the event manager, it will distribute
them to the object, if needed. The object in turn will react
according to its programming instructions.
To allow your object to react according to the application
needs on certain events easily, the
BaseObject
supports the connection of
callbacks to signals you can define for
it. A callback is a method or function,
which should be invoked, when the object receives a certain
event.
The first thing you should do is to let your existing object or
the newly created one inherit from the
BaseObject
class. Afterwards you can
unleash its full power by adding just a minimal set of code.
from ocempgui.object import BaseObject class OwnObject (BaseObject): def __init__ (self): BaseObject.__init__ (self) ...
Example 16.
Inheriting from the BaseObject
class
That is not all of course. You also have to set up the signals the object has to listen to and create callbacks. Let us create a small ping-pong example, where two objects react upon a 'ping' and 'pong' signal.
The BaseObject
has a
_signals attribute, which basically is a
dictionary with the signal ids it listens to as keys and list
for the callbacks as values. To allow your object to listen to
the 'ping' or 'pong' signal, you have to add those to this
dictionary.
from ocempgui.object import BaseObject class OwnObject (BaseObject): def __init__ (self): BaseObject.__init__ (self) self._signals["ping"] = [] self._signals["pong"] = [] ...
Example 17. Adding a signal to the object
The list as value is mandatory to allow callbacks to be connected to those signals. If you are going to supply other types as value, keep in mind, that it is unlikely that connecting or disconnecting callbacks will work as supposed.
Now we just need to make the notify()
aware of those signal types and let it invoke the appropriate
callbacks, which will be connected to those signals.
class OwnObject (BaseObject): ... def notify (self, event): if event.signal == "ping": self.run_signal_handlers ("ping") elif event.signal == "pong": self.run_signal_handlers ("pong")
Example 18. Setting up the notify() method
As you see, the run_signal_handlers()
takes care of invoking the connected callbacks. Now you have
anything set up to allow the object to listen to specific
events, to connect callbacks to it and let it invoke them,
when it receives the specific signal.
To connect methods or functions as callbacks to a specific
signal, the connect_signal()
method of
the BaseObject
can be used. It allows
additional data to be passed to the callback by specifiying the
data right after the signal and callback. If the callbacks is
not needed anymore, it can be disconnected using the
disconnect_signal()
method.
class OwnObject (BaseObject): ... my_obj = OwnObject () ev_callback1 = my_obj.connect_signal ("ping", ping_callback, data) ev_callback2 = my_obj.connect_signal ("pong", pong_callback, data1, data2) ... my_obj.disconnect_signal (ev_callback1) my_obj.disconnect_signal (ev_callback2)
Example 19. Connecting and disconnecting callbacks.
Now it just needs to be connected to an event manager. In
contrast to the earlier section, you do not need to register
any signal of your BaseObject
inheritor
manually. When you connect it to an event manager, it will
automatically do that for you.
class OwnObject (BaseObject): ... manager = EventManager my_obj = OwnObject () # Any signal of the object will be registered automatically. my_obj.manager = manager
Example 20. Connecting the object to an event manager.
The last important thing to know about the
BaseObject
is its ability to emit
events. If the object is connected to an event manager, you
can let it send events through the manager with the object its
emit()
method. The syntax is the same
as if you would emit events on the
EventManager
directly.
Now let us look at the example we just went through (the
following one is slightly modified only). You can find the
example as python script under
examples/baseobject.py
# BaseObject usage example. from ocempgui.object import BaseObject from ocempgui.events import EventManager # Callbacks, which should be invoked for the object. def ping_callback (obj, additional_data): print "The object is: %s" % obj.name print "Passed data is: %s" % additional_data def pong_callback (): print "Another callback with no arguments." # Object implementation, which can listen to specific events. class OwnObject (BaseObject): def __init__ (self, name): BaseObject.__init__ (self) self.name = name # The object should be able to listen to 'ping' and 'pong' # events. self._signals["ping"] = [] self._signals["pong"] = [] def notify (self, event): # This simple notify method will not be used in this # example. Instead, the signals are invoked directly. if event.signal == "ping": self.run_signal_handlers ("ping") elif event.signal == "pong": self.run_signal_handlers ("pong") manager = EventManager () # Create an object and connect callbacks to its both events. my_obj = OwnObject ("First object") ev1 = my_obj.connect_signal ("ping", ping_callback, my_obj, "data") ev2 = my_obj.connect_signal ("pong", pong_callback) # Connect it to the event manager my_obj.manager = manager # Invoke the connected signals handlers for a specific event. manager.emit ("ping", None) manager.emit ("pong", None) # After disconnecting a callback, it will not be invoked anymore. my_obj.disconnect_signal (ev1) my_obj.disconnect_signal (ev2) manager.emit ("ping", None) manager.emit ("pong", None)
Example 21. Ping-Pong with a BaseObject
The ocempgui.object
module includes
another event capable object class, the
ActionListener
, which inherits from the
BaseObject
class, but allows you to
create and delete signals and listening queues as you need
them without the necessity to subclass.
The ActionListener
creates the signal
id and a callback queue, when you connect a callback to it and
registers itself for this signal automatically at its event
manager. This can be extremeley useful, if a more flexible
event capable object type is needed, which does not need to do
any sanity checks on the event data. Instead it will send the
event data to the callback as well, which then can work with it.
Once more let us create a ping-ping example using the
ActionListener
class instead of a
BaseObject
now.
You can find the example as python script under
examples/actionlistener.py
# ActionListener usage example. import sys from ocempgui.events import EventManager from ocempgui.object import ActionListener count = 0 def emit_pong (event, manager): print "emit_pong received: [%s] - emitting pong..." % event manager.emit ("pong", "pong_event") def emit_ping (event, manager): global count if count > 10: sys.exit () count += 1 print "emit_ping received: [%s] - emitting ping..." % event manager.emit ("ping", "ping_event") # Create an event manager and two ping-pong listeners. manager = EventManager () listener1 = ActionListener () listener1.connect_signal ("ping", emit_pong, manager) listener1.manager = manager listener2 = ActionListener () listener2.connect_signal ("pong", emit_ping, manager) listener2.manager = manager # start ping-pong actions print "Starting Ping-Pong" manager.emit ("ping", "ping_event")
Example 22. Ping-Pong with the ActionListener
Looking at the first interesting line, line eight,
def emit_pong (event, manager): print "emit_pong received: [%s] - emitting pong..." % event manager.emit ("pong", "pong_event")
EventManager
object, on which it emits a pong event with additional data then.
Line twelve and following does the same, but breaks, if it was invoked more than ten times.
The next lines of interest are line twenty-three to twenty-nine,
listener1 = ActionListener () listener1.connect_signal ("ping", emit_pong, manager) listener1.manager = manager
ActionLister
objects will
be created and signal slots and callbacks for the 'ping' and
'pong' events will be set up. In line twenty-five and
twenty-nine the objects will be registered at the event
manager created earlier in the code.
Although this class is very mighty, you should not use it as base for own event capable classes. When the feature set and code amount of your own classes grow, it easily can happen, that you oversee events or that you do not understand which signals it should deal with anymore.
It is however a good and valuable class type to work as proxy or to delegate events to different functions or methods in a context sensitive manner.