Signals and Slots
Learn how to make connexions between objects.
Introduction
Signals and slots is the language construct introduced by Qt for communication between objects. This is a short and concise for the Observer pattern. It provides the possibility for objects (called observers) to be recipients for automatic notifications from objects upon change of state or any notification.
This mechanism is in the core of Event-driven program such as GUI applications. Where an application reacts to different events generated by the users. Events could be generated by mouse clicks, keyboard or other timed events.
The signals and slots mecanism is fundamental to Qt programming. It enables the application programmer to bind objects together without the objects knowing anything about each other. We have alread connected some signals and slots together.
Slots
Slots are almost idential to ordinary C++
member functions. They can be
virtual, public, protected or private. The can also be directly invoked as
any other C++
member functions. Also their parameters can be of any type. The
main difference is that slots
can be automatically connected to respond to
a signal. The classical connect mecansim look like:
connect(sender, SIGNAL(signal), receiver, SLOT(slot));
where sender
and receiver
are pointer to QOjbects and signal and slot are
methods without parameters.
for Example, in previous examples, we connected a spinBox
and a slider
to
synchronize their values using:
connect(spinbox, SIGNAL(valueChanged(int)), slider, SLOT(setValue(int)));
- What is intersing about signals is that a signal can also be connected to another signal!! making a chain reaction.
connect(lineEdit, SIGNAL(textChanged(const QString &),
this, SIGNAL(updateRecord(const QString &)));
- Another intersting aspect is that those connexions could be broken programmatically for a more intersting behavior.
disconnect(lcd, SIGNAL(overflow()), this, SLOT(handleMathError()));
Basic Example.
Volume control
Lets start with a basic example showing a slider
and LCDNumber
. Downdload
the starting project in volume_control.zip.
- Configure and run the project to produce a window like:
-
We want to establish a connexion that
display
the number of the slider each time itsvalue changes
. -
In order to do that we connect those objects using the mechanism of
signals/slots
.- First add the declaration in
window.h
void makeConnections();
We will create this method to setup all the connexions between the internal objects. We could do this in the consructor. However, we may find this approach more structured.
- Add the implementation of ths method in the
window.cpp
:void makeConnections() { //connect the slider to the LCD number connect(slider, SIGNAL(valueChanged(int)), number, SLOT(display(int))); }
o In this code we have two object (widgets) which are the
slider
and thenumber
. The line added will connect each change in value in the slider to un update of the lcdnumber displayed value.
- First add the declaration in
Custom SLOTs
In this second example, we will illustrate that we could create our custom slots to respond to a given signal. Our goal is create a dialog to roll a dice each time we click on a button.
-
First download and extract ther starter code in dice.zip .
-
Execute the programmer, we will see a diaglog like this one:
-
In order to create the connexion between the button and the dices, we need to create our
custom slot
to respond the click.- First, we will declare our private slots in the
window.hpp
private slots: void rollDice()
- Second, we will implement this slot in
window.cpp
void rollDice() { //Generate a random value for dice 1 int n = qrand() % 6; die1->setPixmap(diceFaces[n]); //Same for the second dice n = qrand() % 6; die2->setPixmap(diceFaces[n]); }
This method will alter change the images for the dices given the values
randomly generated
by Qt Random generator. - Now we only need to connect the click of the button with this slot, we will do it in a custom connexion.
void DiceRoller::makeConnections() { connect(rollButton, &QPushButton::clicked, this, DiceRoller::rollDice); }
In the last line, we used a new synatax to refere to the signals and slots using the
reference
mecanism. - First, we will declare our private slots in the
Special Signals (events)
Key Press
Another cool aspect of signals/slots
is that you could program them also in
the spirit of listener/event mechanism like in Java
. Fist, we will
illustrate this concept by writing an application to respond to a key press
from the keyboard.
We could write this concept using signals/slots
but we will do it by
overriding an inherited function:
void keyPressEvent( QKeyEvent *e)
This slot
will be trigger automatically each time we click an keyboard key.
The information of the key will be store in the class QKeyEvent.
Let’s show the mechanism by writing a simple application to display the keys entered by the user. The application will automatically close when we enter the escape key.
The starter code for is in pressEvent.zip
First we will write a custom widget that inherit from the QWidget
class:
class SpecialEvent : public QWidget
{
public:
SpecialEvent();
virtual ~SpecialEvent();
private:
void createWidgets();
void placeWidgets();
void makeConnexions();
private:
//members
QLabel *mainLabel;
QHBoxLayout *mainLayout;
protected:
void keyPressEvent(QKeyEvent *e);
};
The main point is this class, is the protected function that inform Qt that
we will override the keyPressEvent
for our widget.
Here is the implementation:
SpecialEvent::SpecialEvent():QWidget()
{
createWidgets();
placeWidgets();
makeConnexions();
}
SpecialEvent::~SpecialEvent()
{
delete mainLabel;
delete mainLayout;
}
void SpecialEvent::createWidgets()
{
// Create the label
mainLabel = new QLabel("K", this);
mainLabel->setFont(QFont("monospace",50));
//Create the layout
mainLayout = new QHBoxLayout();
}
void SpecialEvent::placeWidgets()
{
mainLayout->addWidget(mainLabel);
setLayout(mainLayout);
}
void SpecialEvent::makeConnexions()
{
}
void SpecialEvent::keyPressEvent(QKeyEvent *e)
{
if (e->key() == Qt::Key_Escape)
qApp->exit();
else
mainLabel->setText(e->text());
}
The interesting part is the keyPressEvent
where we:
- Compare the
e->key()
to static keyQt::Key_Escape
- If this key is entered we quit the application.
- Otherwise we will change the label of the label the text of the key.
mainLabel->setText(e->text())
Move event
Similar to the keyPressEvent
, we can also listen to any mouse mouvement
following the same syntax.
For example, suppose we want to change the title of the our keyPress application to show the current position of the window. We need to override the QMoveEvent using the following syntax:
void moveEvent(QMoveEvent *event)
Here is the implementation of this function, using the information stored in the
event
.
//getting the x position
auto x = e->pos().x();
// getting the y position
auto y = e->pos().y();
setWindowTitle(QString("Pos (%0, %1)").arg(x).arg(y));
Timer
Another example of pref dined events is a TimerEvent. This event is used to implement repetitive tasks that automatically triggered periodically. A classical example is a clock showing the current time:
Let’s change our simple application to display the current time and change it each second.
First we will add QLabel
to our private set of widgets to represent current time:
QLabel * timeLabel;
We need to create this label in the createWidgets
section:
//Getting the current time
QTime current = QTime::currentTime();
//Creating the label with the curren time
timeLabel = new QLabel(current.toString(), this);
//changint the font
timeLabel->setFont(QFont("times", 14));
Now, we will add it to our mainLayout in the placeWidgets
function:
//Add a spacer
mainLayout->addSpacer(new QSpacer(40, QSizePolicy::expanding));
mainLayout->addWidget(timeLabel);
Now for the essential part, in order to respond to a time event we need:
- Override the
timerEvent(QTimerEvent *)
function. - Register a timer with a given time with
startTime(int)
For the first part, here is the code for the overidden function:
void SpecialEvent::timerEvent(QTimerEvent *e)
{
//Here we simply change the test of the label
// by the current time
timeLabel->setText(QTime::currentTime().toString());
}
For the second part, we add the following call the contructor:
//Add a timer listener each 1000 miliscons (1 second)
startTimer(1000);
At the end you have an application three events. Here is a snapshot for this application: