CopperSpice API  1.7.2
Signals and Slots

Signals and Slots are used for communication between objects. In GUI programming when a widget changes state, another widget usually needs to be notified. There needs to be a way to communicate between objects. For example, if a user clicks a close button they probably want the close() method to be called on the window.

Some libraries and user source code achieve this communication by using callbacks, which is a pointer to a function. With callbacks, when an something happens some event handler will use the callback pointer to invoke the callback function. Callbacks can have some fundamental limitations and although they can be valuable, there are usually better ways to achieve the desired result.

  • Invoking a callback may not be type safe
  • Callbacks may not work across threads

Overview

A more effective approach than using callbacks is to use the mechanism of Signals and Slots. A signal is emitted when a particular event occurs. A slot is a method which is called in response to some particular Signal which was triggered or emitted. The signal and slot mechanism is type safe and works across threads. The signature of a signal must be compatible with the signature of the slot. A slot may have a shorter signature than the signal it is connected to since the slot methods can ignore trailing arguments. Signals and slots can take any number of arguments of any copyable data type.

A class which emits a signal neither knows nor cares which slots receive the signal. All classes that inherit from QObject or one of its subclasses can contain signals and slots. Signals are emitted by objects when they change their state in a way that may be interesting to other objects.

You can add any number of signals and slots to your application to respond to events which matter to your user interface. Connect as many signals as you want to a single slot and disconnect as needed. There is a form of disconnect which will remove the signal / slot relationship for every previously connected pair.



Signal / Slot Example

The following example shows how to declare a class in your application which contains a signal and a slot. There are two macros for each Signal and Slot. They 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 needs to query the meta object system at run time for this class. If you are simply making a connection so the slot method is called in response to a triggered signal, it is not necessary to use this macro. Just declare the slot as you would any other C++ method. In this situation when calling connect() you must use the method pointer or lambda expression syntax.

Note
This example uses the older styling for making a connection. Refer to Preferred Connect() Syntax for a modern C++ approach.

Classes which contain signals, slots, or properties must use the CS_OBJECT(name) macro at the top of the class declaration. Your class must also inherit either directly from QObject or from a parent class which inherited from QObject.

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))
CS_SLOT_2(setValue)
private:
int m_value;
};

The idea is when the value changes in Counter the valueChanged() signal will be emitted and then any slot methods which are connected to this signal are invoked. In our example we have two Counter objects and when the value is changed in object A the slot method will set the value in object B equal the value from Object A. This is the code you would write to connect the signal in object A to the slot in object B.

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

A slot is nothing more that a normal method you write and then add to your application. The following is a simple 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 usually executed immediately. Execution of the code following the emit statement will occur once all slots have returned. The is different for a queued connection. In this case the code following the emit will continue and the slot methods will be executed later when events are processed. For more information about queued connections refer to Qt::ConnectionType.

If several slots are connected to one signal the slots will be executed one after the other in the order they were connected.

Slots

Since slots are normal functions or methods they can be called directly and not just in the signal slot system.

One interesting side effect about slot methods is how the access specifier like Public or Private is interpreted. Normally you can not call a Private method from outside the class. In the Signal/Slot delivery system the access specifier of the slot method is not considered. This means that a signal emitted from an instance of a class can invoke a private slot of an unrelated class.

You can also define slots to be virtual.

There is some overhead to calling a slot method however it is minimal.

Preferred Connect() Syntax

It is still possible to use the older SIGNAL and SLOT macros however it is more efficient to use method pointers for the Signal and either a method pointer or a lambda expression for the slot method.

The following example uses the Counter class declared earlier. The first call to connect() shows the older macros. In the second example the macros for the second and fourth parameters are replaced with method pointers. The syntax of &Counter refers to the class and not an instance of Counter. Notice there are no parentheses or parameters listed with the method pointers.

In the third example the SIGNAL macro is replaced with a method pointer and the SLOT macro is replaced with a lambda expression. This format has interesting advantages. You can call the slot method or directly include the code in the body of the 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)
void setValue(int value); // slot declaration
private:
int m_value;
};
Counter objA;
Counter objB;
// older syntax using macros ( shown for backward compatibility )
QObject::connect(&objA, SIGNAL(valueChanged(int)), &objB, SLOT(setValue(int)));
// method pointers
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 special meta object. The staticMetaObject() contains the class name and the names of all the signal and slot members. Refer to Meta Object System for more information.

Signals And Slots With Default Arguments

The signatures of signals and slots may contain arguments which can have default values. Consider the QObject::destroyed() signal.

void destroyed(QObject * = nullptr);

When a QObject is deleted it emits the QObject::destroyed() signal. Your application may need to take some action when this this occurs, to avoid using an object which has been freed. The following slot declaration would appear in your code.

void mySlot(QObject * obj = nullptr);

To connect the destroyed signal to the slot you can use QObject::connect() and the SIGNAL() and SLOT() macros. The SIGNAL macro can not have fewer arguments than the SLOT macro and the data types of these passed should match. All of the following forms are valid.

The next section explains how to call connect() more efficiently leveraging modern C++.

connect(sender, SIGNAL(destroyed(QObject*)), this, SLOT(mySlot(QObject*)));
connect(sender, SIGNAL(destroyed(QObject*)), this, SLOT(mySlot()));
connect(sender, SIGNAL(destroyed()), this, SLOT(mySlot()));

Advanced Signals and Slots Usage

For cases where you may require information on the sender of the signal, CopperSpice provides the QObject::sender() method which returns a pointer to the object which sent the signal.

The QSignalMapper class is provided for situations where many signals are connected to the same slot and the slot needs to handle each signal differently. Suppose you have three push buttons that determine which file you will open: "Tax File", "Accounts File", or "Report File".

In order to open the correct file, use QSignalMapper::setMapping() to map all the clicked() signals to a QSignalMapper object. Then you connect the file's QPushButton::clicked() signal to the QSignalMapper::map() slot.

signalMapper = new QSignalMapper(this);
signalMapper->setMapping(taxFileButton, QString("taxFile.txt"));
signalMapper->setMapping(accountFileButton, QString("accountsFile.txt"));
signalMapper->setMapping(reportFileButton, QString("reportFile.txt"));
connect(taxFileButton, SIGNAL(clicked()), signalMapper, SLOT(map()));
connect(accountFileButton, SIGNAL(clicked()), signalMapper, SLOT(map()));
connect(reportFileButton, SIGNAL(clicked()), signalMapper, SLOT(map()));

Then connect the mapped() signal to readFile() where a different file will be opened, depending on which push button is pressed.

connect(signalMapper, SIGNAL(mapped(QString)), this, SLOT(readFile(QString)));