CopperSpice API  1.7.4
Signals and Slots

A key aspect of a well designed GUI program involves responding to actions taken by the user of the software.

If a user clicks a close button they probably want to close the corresponding window. Since this is a common occurrence a GUI library should have a convenient mechanism to associate actions taken by the user to some process the program should take. In the case of the mouse click action the program should respond by closing the desired window.

Some programs or libraries accomplish this by using callbacks, which is a pointer to a function. With callbacks, when something happens an event handler will use the callback pointer to invoke the callback function. Callbacks can have fundamental limitations and are not the best solution for a C++ GUI program.

A more effective approach than callbacks is the mechanism provided by Signals and Slots. In this paradigm a signal is emitted when a particular event occurs. A slot is usually a method and will be called in response to the signal. The signal / slot mechanism in CopperSpice is type safe and works across threads. A signal is emitted when a particular action occurs, such as the mouse click. A slot is the response which is usually a call to a method or function.

  • Invoking a callback is not usually type safe
  • In a multithreaded application callbacks often cause race conditions
  • Callbacks do not support lambda expressions which can capture local data
  • Syntax for a callback can be complicated to read or understand

Overview

The signal can be designed to emit any number of parameters of any copyable data type. The slot can ignore any or all of these arguments. The only constraint is that if the slot needs to receive some of the arguments the order must match those emitted by the signal. A class which emits a signal does not need to know which slots receive the signal. All classes which inherit from QObject or one of its subclasses may contain signals and slots.

Using the connect() method, an application can connect a one given signal to multiple slots. It can also connect multiple signals to a single slot. Signals and slot are often connected and disconnected multiple times in response to the user input.

The disconnect() method which breaks the signal / slot relationship. There are multiple overloads and several which can be used to disconnect all existing connections which match certain criteria.



Example

The following example shows how to declare a class which contains a signal and a slot. The CS_SIGNAL() and CS_SLOT() macros are used to declare and register the method names and parameters with the CopperSpice meta object system.

The CS_SLOT() macro is only required if your application is going to look up the slot at run time, using a string. If this is not required, simply declare the slot as you would declare any other C++ method. When the slot macro is not used then a method pointer, function pointer, or lambda expression must be used when setting up in the signal/slot connection.

Classes which contain the CS_SIGNAL or CS_SLOT macros must contain the CS_OBJECT(<class_name>) macro at the top of the class declaration. Your class must also inherit either directly from QObject or from a class which inherits from QObject.

Note
This example uses the older styling for making a connection. Refer to Preferred Connect() Syntax for a modern C++ approach.
class Counter : public QObject
{
CS_OBJECT(Counter)
public:
Counter() {
m_value = 0;
}
int value() const {
return m_value;
}
CS_SIGNAL_1(Public, void valueChanged(int newValue)) // legacy macro syntax
CS_SIGNAL_2(valueChanged, newValue)
CS_SLOT_1(Public, void setValue(int value)) // legacy macro syntax
CS_SLOT_2(setValue)
private:
int m_value;
};

When the value changes in Counter the valueChanged() signal will be emitted and any slot methods connected to this signal will be invoked. In this example there are two Counter objects and when the value is changed in object A the slot method will change the value in object B to match the value in Object A.

The following code will connect the signal in object A to the slot in object B.

Counter objA;
Counter objB;
// legacy syntax using macros ( shown for backwards compatibility )
QObject::connect(&objA, SIGNAL(valueChanged(int)), &objB, SLOT(setValue(int)));
objA.setValue(12);

A slot is nothing more than a normal method. The following is an implementation of the Counter::setValue() slot.

void Counter::setValue(int value) {
if (value != m_value) {
m_value = value;
emit valueChanged(value); // triggers the signal
}
}

Signals

When a signal is emitted any connected slots are normally invoked immediately.

There is a parameter in the connect() method which can be used to specify the connection type. The default is Qt::AutoConnection which usually means the same as Qt::DirectConnection and the slot will be invoked with no delay. If there are multiple slots connected to one signal the slots will be invoked in the order they were connected.

Another option is to specify Qt::QueuedConnection. This is used when the sender and receiver belong to different threads.

Slots

A slot can be a normal class method, global function, lambda expression, or function object.

Preferred Syntax

In the following example the Counter class declation has changed slightly depending on how the slot is called.

Macros
This is the older macro syntax. The connection is slower and static type checking will not occur. This syntax can be replaced by a method pointer.
If your code uses the SLOT() macro then you will need to uncomment CS_SLOT_1() and CS_SLOT_2().
Method Pointer
The macros for the second and fourth parameters are replaced with method pointers. Notice there are no parentheses or parameters listed with the method pointers.
Lambda Expression
The SIGNAL() macro is replaced with a method pointer and the SLOT() macro is replaced with a lambda expression.


class Counter : public QObject
{
CS_OBJECT(Counter)
public:
Counter() {
m_value = 0;
}
int value() const {
return m_value;
}
CS_SIGNAL_1(Public, void valueChanged(int newValue))
CS_SIGNAL_2(valueChanged, newValue)
// CS_SLOT_1(Public, void setValue(int value)) // only required when using the SLOT() macro
// CS_SLOT_2(setValue)
void setValue(int value); // only required to declare the slot for the method pointer
private:
int m_value;
};
Counter objA;
Counter objB;
// *** older syntax shown for backward compatibility, not recommended
QObject::connect(&objA, SIGNAL(valueChanged(int)), &objB, SLOT(setValue(int)));
// method pointer
QObject::connect(&objA, &Counter::valueChanged, &objB, &Counter::setValue);
// lambda expression
QObject::connect(&objA, &Counter::valueChanged, &objB, [&objB] (int newValue) { objB.setValue(newValue); } );

Meta Object Information

Any class which inherits from QObject will contain a meta object. The staticMetaObject() contains the class name and all signal and slot method names. This information can be useful when writing a plugin.

Refer to Meta Object System for more information.