How to Use the Signal/Slot Communication Mechanism


Introduction

ROOT supports its own version of the signal/slot communication mechanism originally featured in Qt, a C++ GUI application framework by Troll Tech. The ROOT implementation uses the ROOT meta class system. In addition to all features provided by Qt the ROOT version supports connecting slots to a class (as opposed to connecting to a specific object). These slots will be called whenever the specified signal is emitted by any object of the class. Also a slot can have default arguments and be either a class method or a stand-alone function (compiled or interpreted).

This ROOT based implementation was made by Valeriy Onuchin.


Basic Concepts

Signals and slots are used for communication between objects.

Signals are emitted by objects when they change their state in a way that may be interesting to the outside world. This is all the object does to communicate. It does not know if anything is receiving the signal at the other end.

Slots can be used for receiving signals. A slot does not know if it has any signal(s) connected to it.

This is true information encapsulation, and ensures that the object can be used as a true software component.

Signals and slots can take any number of arguments of any type.

It is possible to connect as many signals as you want to a single slot, and a signal can be connected to as many slots as you desire.

It is possible to make a single connection from all objects of the same class.


A Small Example

A minimal C++ class declaration might read:

    class A {
    private:
       Int_t  fValue;
    public:
       A() { fValue = 0; }
       Int_t  GetValue() const { return fValue; }
       void   SetValue(Int_t);
    };

A small ROOT interpreted class might read:

    class A {
       RQ_OBJECT()
    private:
        Int_t  fValue;
    public:
        A() { fValue = 0; }
        Int_t  GetValue() const { return fValue; }
        void   SetValue(Int_t);      //*SIGNAL*
    };

This class has the same internal state, and public methods to access the state, but in addition it has support for component programming using signals. This class can tell the outside world that its state has changed by emitting a signal, SetValue(Int_t).

Here is a possible implementation of A::SetValue():

    void A::SetValue(Int_t v)
    {
       if (v != fValue) {
          fValue = v;
          Emit("SetValue(Int_t)", v);
       }
    }

The line Emit("SetValue(Int_t)", v) emits the signal SetValue(Int_t) with argument v from the object. As you can see, you emit a signal by using Emit(``full_method_name'',arguments).

Here is one of the ways to connect two of these objects together:

    A *a = new A();
    A *b = new A();
    a->Connect("SetValue(Int_t)", "A", b, "SetValue(Int_t)");
    b->SetValue(11);
    a->SetValue(79);
    b->GetValue();          // this would now be 79, why?

The statement a->Connect("SetValue(Int_t)", "A", b, "SetValue(Int_t)")
denotes that object a connects its "SetValue(Int_t)" signal to "A::SetValue(Int_t)" method of object b.

Calling a->SetValue(79) will make a emit a signal, which b will receive, i.e. b->SetValue(79) is invoked. It is executed immediately, just like a normal function call. b will in turn emit the same signal, which nobody receives, since no slot has been connected to it, so it disappears into hyperspace.

This example illustrates that objects can work together without knowing about each other, as long as there is someone around to set up a connection between them.


Features of the ROOT implementation


Signals

A signal is a normal class method. The first requirement is that it should call an Emit() method. The format of this method is the following:

    Emit(``full_method_name''[,arguments])

where ``full_method_name'' is the method name and prototype string of the signal method.
For example, for SetValue(Int_t value) the full method name will be "SetValue(Int_t)", where SetValue is the method name and Int_t the prototype string. Note that typedefs will be resolved to facilitate matching of slots to signals. So the slot "print(int)" can be connected to the above signal which has an Int_t as argument.

The second requirement is that the method declaration should have the string *SIGNAL* in its comment field. Like:

    void SetValue(Int_t x);  //*SIGNAL*

This provides an explicit interface specification for the user (this requirement is currently not enforced at run-time).

The third requirement, only necessary if you want to have class signals (i.e. for all objects of a class), is that you have to replace the standard ClassImp macro by ClassImpQ.

Signals are currently implemented for all ROOT GUI classes and the TTimer and TCanvas classes (to find quickly all defined signals do for example: grep '*SIGNAL*' $ROOTSYS/include/*.h).


Examples

A First Time Example (rqfirst.C)

This example shows:


Histogram Filling with Dynamic State Reported via Signals (rqsimple.C)

Based on hsimple this example demonstrates:


An Example on How to Use Canvas Event Signals (rqfiller.C)

This example shows:

With this demo you can fill histograms by hand:


Complex GUI Using Signals and Slots (guitest.C)

Based on $ROOTSYS/test/guitest.cxx this example demonstrates:


Rene Brun, Fons Rademakers and Valeriy Onuchin
Last update 29/10/2000 by FR