Tag Archives: Slots

Selective slot activation in Qt, using signal-slot mechanism

Sometimes you are bound to use a unified signal-slot for passing various informations between couple of objects interacting with each other. While the information needs to be of different type, sometimes you are bound to same unified mechanism for a variety of objects, because having one signal-slot for each object type being passed around is simply not viable.

Example:
In a hierarchical model-view-controller architecture, controllers on diverse layers can communicate with eachother by events. One controller can handle a event, bubble it up to it’s parent controller, or send it down to it’s child controllers. Implementing one handling method for each possible event passed around is not viable, since this means all controllers need to implement that method. Also, adding new events to the system is hard, because you have to rewrite all controllers to handle that new event type, or to pass it around.
Since one event handler per event type is not viable, another approach would be to have a generic event handler mechanism, which handles all the event types, and have different event types subclass a parent Event, and in the event handler, according to event sent as parameter, to respond to given events, if their type is appropiate.

With Qt this could be implemented by adding to each object a event(Event*) signal and fireEvent(Event*) slot, and all interested parties register to event(Event*) signal, and get notified when fireEvent() is called. One downside of this approach is that all registered slots get called, even if they are not interested in the given event. Even worse,the slots who are not interested in event still have to check if the event is an event they are interested in, just to realize the event is not of their interest.

So, we need to somehow fire events, but to notify only those events who are interested in a given event type. And of course, to rely on Qt’s cool signal-slot mechanism.

First, we declare a base Event class, instance of which gets passed around. Later, for new events, we just extend this base class, and pass instance of the new class to signal-slot mechanism.

Then, we need to decide how we gonna select what event we want to react on. For example, we can filter events by class name. So, we need a mechanism which will allow us to return a type of event either from an instance of object, or statically from class. I have created a template to help with this (and also allow easy change later if I want to replace it with something else):

template <class T>
        class EventHelper
        {
           public:
              static QString type()
              {
                return T::staticMetaObject.className();
              }

              static QString type(QObject *e)
              { 
                return e->metaObject()->className();
              }
        };

We can use this either by calling it with a class parameter like:

QString type = EventHelper<AppEvent>::type()

or with an instance of Event or another class extending Event, like:

QString type = EventHelper<Event>::type(instance of event here)

In order to route events, we create a EventRouter object. We will use this object to register various objects interested in handling events, and we will fire events using this EventRouter class. When firing events, EventRouter will take care of signaling only slots who are interested in the given event type.

For registering objects interested in given event type:

eventRouter.registerTarget(QString& type, Target* t)

where Target is a class which has slot for handling event, like

void handleEvent(Event* e)

When signaling event, EventRouter should take care of signaling only the interested parties. But how can this be accomplished, while still using the Qt’s event / slot mechanism?
One approach would be to use an intermediate object, as a relay object. For each event type we register Targets for, EventRouter holds a helper object, to which actually connects the signal and slot of the EventRouter and Target. So, the sole purpose of helper object is to relay the event to it’s Target’s:

class EventRouterHelper : public QObject
{
    Q_OBJECT

    public:
        EventRouterHelper(QObject* parent = NULL);

    signals:
        void event(Event* e);

    public slots:
        void fireEvent(Event* e) { emit event(e);}
};

All Target’s handleEvent slots get registered to EventRouteHelper’s event() signal. This allows using Qt’s mechanism to signal event to all Targets.

So EventRouter’s remaining job is to wire up everything and according to the type of signaled event, to retrieve the correct EventRouterHelper object and use it to fire the event to intersted Targets:

class EventRouter : public QObject
{
    Q_OBJECT

    public:
        EventRouter();
        ~EventRouter();

        void registerTarget(const QString& clazz, Target *target );
        void unregisterTarget(Target *p);

    public slots:
        void fireEvent(Event* e);

    protected:
        QMap<QString, EventRouterHelper*> mRoutes;
        QMap<Target*, EventRouterHelper*> mRouteObjects;
};

When registering Target, we check if we have a helper object for this event type. If we don’t have yet, then create one, otherwise hook up the signal/slots as required:

void EventRouter::registerTarget(const QString &clazz, Target *target)
{
    EventRouterHelper *o = mRoutes[clazz];

    // if we don't have helper object, create one
    if (o == NULL)
    {
        // create an EventRouterHelper which we use to hook onto
        // the signals and events
        o = new EventRouterHelper();
        mRoutes[clazz] = o;
    }

    // now connect up all signals and slots between target and event helper
    connect(o, SIGNAL(event(Event*)),
            target, SLOT(handleEvent(Event*)));
    mRouteObjects[target] = o;
}

Firing event is easy, just get the helper object for the given class, then fire the event. Because all Target slots are registerted on helper object’s signal, all Targets get notified automatically:

void EventRouter::fireEvent(Event *e)
{
    // get class of event
    QString clazz = EventHelper<Event>::type(e);

    // get helper object
    EventRouterHelper *r = mRoutes[clazz];

    if(r)
    {
        r->fireEvent(e);
    }
}

Source of sample application can be downloaded from here