• Aucun résultat trouvé

Signals and Slots

Dans le document Qt 4 THE BOOK of THE BOOK of (Page 37-41)

Basics, Tools, and First Code

1.3 Signals and Slots

The programs discussed until now generate output only. But if we need to handle user input, we cannot manage without communication between objects.

Many GUI toolkits usecallback functionsorevent listenersto manage commu-nication between objects, but Qt uses thesignal/slot concept.7 Compared with callback functions, this mechanism has the advantage that Qt automatically dis-mantles a connection if either of the two communicating objects is deleted. This avoids crashes, and makes programming simpler.

1.3.1 The Simplest Case: A Slot Responds to a Signal

The easiest way to explain how signals and slots allow objects to communicate is with a simple example. Consider the following program, which displays a simple button with the text Quit. If the user clicks this button, the application ends.

// signalSlot/main.cpp

#include <QApplication>

#include <QPushButton>

int main(int argc, char *argv[]) {

QApplication a(argc, argv);

QPushButton button("Quit");

button.show();

QObject::connect(&button, SIGNAL(clicked()),

&a, SLOT(quit()));

return a.exec();

}

Compared with the “Hello, world!” program from Section 1.1 on page 25, only two things have changed. First, the QLabel object used there has been replaced by a

7 There are also events and event handler functions in Qt. The difference between signals and events is that a signal may be connected to as many slots as desired, including slots from different objects. In contrast, an event handler handles events determined for other objects.

It’s a kind of event interceptor. Chapter 7 provides more details of events.

QPushButton. This class is used to display a button and process mouse clicks on this button.

The second difference consists of the call to QObject::connect(). connect() is a static function of the QObject class that creates a connection between a signal originat-ing from one object and a slot in a destination object. The first two arguments specify the object sending the signal and the signal that we want to bind to the receiving slot. The last two arguments specify the object that is the recipient of the signal, and the receiving slot. The & characters are necessary because the function expects the addresses of the sending and receiving objects as arguments. Here this function is used to determine the action that is executed by the application when the user presses the button: The application terminates.

Slots are normal functions of a class that are specially marked so that they can react to signals. Signals on the other hand are “sent” by objects. A signal from an object can be connected to one or several slots of a single receiving object or of several different receiving objects. If an object sends out a signal, then all the slots are called that are connected to the signal. If there is no matching link, nothing happens.

The call to the QObject::connect() function in the example connects the clicked() signal of the QPushButton object with the quit() slot of the QApplication object.

The button sends out the clicked() signal whenever the user presses the button, thus causing the button’s clicked() function to be called. In response to this signal, the quit() function of the application is called. Calling this slot ends the event loop, and thus the entire application.

When linking signals and slots with the QObject::connect() function, you must use the macros SIGNAL() and SLOT(), as shown. For its second (signal) and fourth (slot) arguments, the connect() function expects to be passed string values that contain a prefix describing the type (signal or slot) and otherwise comply with an internal Qt convention, about which we need not be concerned. Using the two macros will ensure that the expected strings are generated correctly.

1.3.2 Signals Carrying Additional Information and How They Are Processed

The link between a signal and a slot can also be used to transmit additional infor-mation that controls the precise reaction of the slot. For example, see the applica-tion shown in Figure 1.6. This program consists of three control elements: a label, which displays a numeric value; a spin box, which can be used to change the value via the keyboard or mouse (and which also displays the value); and a slider, which shows the current value graphically and can be manipulated to change the value.

Figure 1.6:

All three elements should display the same changeable value.

The aim is for all three widgets to always display the same value. If the user changes the value via the slider, the value must also be adjusted in the spin box and in the label. The same applies to the slider and label if the user adjusts the value in the spin box. This is accomplished with the following code:

// signalSlot2/main.cpp

#include <QApplication>

#include <QVBoxLayout>

#include <QLabel>

#include <QSpinBox>

#include <QSlider>

int main(int argc, char *argv[]) {

QApplication a(argc, argv);

QWidget window;

QVBoxLayout* mainLayout = new QVBoxLayout(&window);

QLabel* label = new QLabel("0");

QSpinBox* spinBox = new QSpinBox;

QSlider* slider = new QSlider(Qt::Horizontal);

mainLayout->addWidget(label);

mainLayout->addWidget(spinBox);

mainLayout->addWidget(slider);

QObject::connect(spinBox, SIGNAL(valueChanged(int)), label, SLOT(setNum(int)));

QObject::connect(spinBox, SIGNAL(valueChanged(int)), slider, SLOT(setValue(int)));

QObject::connect(slider, SIGNAL(valueChanged(int)), label, SLOT(setNum(int)));

QObject::connect(slider, SIGNAL(valueChanged(int)), spinBox, SLOT(setValue(int)));

window.show();

return a.exec();

}

We will place the three widgets in turn (that is, from top to bottom) into a vertical layout. To do this the QSpinBox class contributes the spin box element, and the QSlider is correspondingly responsible for the slider.

To ensure synchronization of the widgets, we use four connect() calls (Figure 1.7):

if the value of the spin box changes, then the label and the slider must be updated;

if the status of the slider varies, the label and the spin box need to be brought up-to-date. (Note that because the spinBox, label, and slider variables are already pointer variables that reference the widgets, we don’t have to take their addresses using the & operator, as we did in the previous example.)

Figure 1.7:

A change made to the value by the user is reported by the QSpinBox and QSlider classes via the signal QSpinBox::valueChanged(int) or QSlider::valueChanged(int).

In each case, the integer argument indicated by the int keyword, which the signal transmits to the slot, specifies the new value of the spin box or the slider.

A new value for the label is set using the QLabel::setNum(int) slot, a function that is called with an integer value as an argument. The spin box and the slider are handled similarly using the slots QSpinBox::setValue(int) and QSlider::setValue(int).

The arrows in Figure 1.7 show that a signal can be connected to several slots and that a slot can react to several signals. For example, if the QSpinBox object sends out the signal valueChanged(int) with the value 5, both the setNum(int) slot of the QLabel object and the setValue(int) function of the QSlider object are called with the value 5.

Qt does not specify the order in which this happens. Either the label or the slider can be updated first, and the exact behavior may be unpredictable. Still, all three widgets will eventually display the value 5.

In this example the signals and slots use the same argument list because no type conversion takes place in signal/slot connections. Thus the setText() slot of the QLa-bel object, which takes a string as an argument and displays it, cannot be connected to the valueChanged(int) signal, since the int argument will not be converted to a string.

If a type conversion cannot be avoided, then you must make a derived class and implement a corresponding slot. This new slot performs the type conversion on the value sent by the signal and then calls the actual, desired slot. This is possible since slots are normal functions of a class. However, the reverse is not true: You cannot use any function you like as a slot, since slots must specifically marked so that they are detected as such by Qt. Chapter 2 explains in detail how to inherit from QObject and define your own signals and slots.

Even though signal/slot connections do not automatically adjust argument types, you may connect a signal to a slot that accepts fewer arguments than those sent by the signal; the slot simply ignores the extra arguments. In this way the val-ueChanged(int) signal of the QSlider could be connected, say, to the quit() slot of the QApplication object. While this would terminate the application as soon as the value of the slider is changed (admittedly not an especially useful behavior), it does show that quit() ignores the int sent by the signal. The types of the argu-ments used by the slot must match those of the signal arguargu-ments. For example, you can connect the signal signalFoo(int, double) to the slots slotFoo(), slotFoo(int), and slotFoo(int, double). However, you cannot link the signalFoo(int, double) signal with the slotFoo(double) slot using connect().

If you try to create an invalid signal/slot connection, neither the compiler nor the linker will complain. Only when the application is run will you see a warning that the signal and slot were not connected properly. For example, if the erroneous connect() call described in our previous paragraph is executed, the terminal window from which the program was called displays the following warning:

Object::connect: Incompatible sender/receiver arguments

SomeClass::signalFoo(int,double) --> SomeClass::slotFoo(double)

A slot that expects more arguments than the signal contains cannot process the signal. Thus we can’t connect the signalFoo(int, double) signal to the slotFoo(int, double, double) slot.

Dans le document Qt 4 THE BOOK of THE BOOK of (Page 37-41)