ClanLib

Signals and Slots

Signals and slots are used for communication between objects. In GUI programming we often want a change in one component to be notified to another component. More generally, we want objects of any kind to be able to communicate with one another. In ClanLib we started using signals in ClanGUI, but broadened it up into other areas, like network and input.

Older toolkits achieve this kind of communication using callbacks. A callback is a pointer to a function, so if you want a processing function to notify you about some event you pass a pointer to another function (the callback) to the processing function. The processing function then calls the callback when appropriate. Callbacks have two fundamental flaws. Firstly they are not type safe. We can never be certain that the processing function will call the callback with the correct arguments. Secondly the callback is strongly coupled to the processing function since the processing function must know which callback to call.

In ClanLib we have an alternative to the callback technique. We use signals and slots. A signal is emitted when a particular event occurs. ClanLib's components have many pre-defined signals. To handle the signals that you are interested in, you connect the signal to a slot.

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 or care whether anything is receiving the signals it emits. This is true information encapsulation. A signal can be connected to as many slots as you desire.

A list box, for example, emits both highlighted() and activated() signals. Most objects will probably only be interested in activated() but some may want to know about which item in the list box is currently highlighted. If the signal is interesting to two different objects you just connect the signal to slots in both objects.

When a signal is emitted, the slots connected to it are executed immediately, just like a normal function call. The emit will return when all slots have returned.

Slots can be used for receiving signals, but they are normal member functions. A slot does not know if it has any signals connected to it.

Slot objects maintains connection state to the signals to which they are connected. When a slot object is destroyed, the signals to which it was connected will be automatically informed, and subsequently disconnected. However, this also means that it's important to be aware that you have to store the slots somewhere (even though they slots themselves are never used actively).

A minimal C++ class declaration might read:

Adding a signal to this:

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 and slots: this class can tell the outside world that its state has changed by emitting a signal, sig_value_changed.

The template-type of the signal class is called CL_Signal_v1. The _v1 means void, 1 parameter. If we wanted a type with 2 parameters, we would create a signal of type CL_Signal_v2:

Currently void is the only return type supported, so all signals are called CL_Signal_v?.

Emitting signals

Here is a possible implementation of Foo::set_value():

The line sig_value_changed(v) emits the signal sig_value_changed from the object. As you can see, you emit a signal by calling the signal as a function(arguments). Examples on the _v2 and _v3 signals mentioned above:

Connecting to signals

Connecting to a signal is pretty straightforward as well:

Explanation:

Make sure you store the CL_Slot object somewhere, otherwise the slot is disconnected when it is destroyed (goes out of scope for instance). Put it in your class definition or similar; just don't make it a local variable.

Each signal has a connect() function, which returns a CL_Slot object you need to keep. It takes two parameters; the object of the receiving slot, and the definition of the receiving slot. Most often the first argument will be this, but you can actually connect it to another class than your current object:

Slot Container

CL_SlotContainer is a utility class which can store CL_Slots for you. Its syntax is as follows:

You see the syntax for connecting signals using a CL_SlotContainer is a little bit different than the CL_Slot method (example below).

Disconnecting slots

It is possible to disconnect a slot from its signal. This is automatically done when the slot is deleted/leaves scope, but you can also call the signal.disconnect() function to disable a slot.

Virtual slots

ClanLib supports "virtual" slots, meaning you can override previously connected slots. This works like the inheritage in C++, you can call earlier hooked up signals from the overridden slot.

A normal connect looks like this:

But to override any previously connected signals, you use the connect_virtual method:

Note that the connected function must have an extra parameter, the CL_SlotParent. This is a reference to the previously connected signal, and you can call it if you want, just like virtual methods in C++.

Userdata

When a signal is fired, the connected slots are called with the parameters defined in the signal. Sometimes you want to add extra information to each slot, and for that ClanLib provides userdata. It is used by adding an extra variable when connecting to the signal.

This is how you would connect to a normal signal, without using any userdata. The signal requires two params, and you supply a function with two params.

Here we used the exact same signal, but added a string parameters as a userdata. Each time the signal is now fired, the userdata string will contain the string "hello".

You can use any type for the userdata, be it string, ints, whatever.

In this example, userdata is used to connect several button clicks to the same function, but with an extra userdata integer describing which button was pressed.

Specific ClanLib signals

All signals in ClanLib is a little bit different than explained in this overview so far - they are all functions. The only effect this has on your code is that you put parenthesis behind the signal when connecting to it. Some examples:

As you see, the only different is the parenthesis - sig_clicked() instead of sig_clicked.

More examples

If you need to see some real world examples of signals and slots, have a look at the GUI overview.

Thanks to TrollTech and their QT toolkit for their Signal & Slot explanation; this overview was based on theirs.

Questions or comments, write to the ClanLib mailing list.