Understanding the mechanism of signals and slots
It is really important to keep your curiosity and to explore what on earth these properties do. However, please remember to revert the changes you made to the app, as we are about to enter the core part of Qt, that is, signals and slots.
Note
Signals and slots are used for communication between objects. The signals and slots mechanism is a central feature of Qt and probably the part that differs the most from the features provided by other frameworks.
Have you ever wondered why a window closes after the Close button is clicked on? Developers who are familiar with other toolkits would say that the Close button being clicked on is an event, and this event is bound with a callback function that is responsible for closing the window. Well, it's not quite the same in the world of Qt. Since Qt uses a mechanism called signals and slots, it makes the callback function weakly coupled to the event. Also, we usually use the terms signal and slot in Qt. A signal is emitted when a particular event occurs. A slot is a function that is called in response to a particular signal. The following simple and schematic diagram helps you understand the relation between signals, events, and slots:
Qt has tons of predefined signals and slots, which cover its general purposes. However, it's indeed commonplace to add your own slots to handle the target signals. You may also be interested in subclassing widgets and writing your own signals, which will be covered later. The mechanism of signals and slots was designed to be type-safe because of its requirement of the list of the same arguments. In fact, the slot may have a shorter arguments list than the signal since it can ignore the extras. You can have as many arguments as you want. This enables you to forget about the wildcard void*
type in C and other toolkits.
Since Qt 5, this mechanism is even safer because we can use a new syntax of signals and slots to deal with the connections. A conversion of a piece of code is demonstrated here. Let's see what a typical connect statement in old style is:
connect(sender, SIGNAL(textChanged(QString)), receiver, SLOT(updateText(QString)));
This can be rewritten in a new syntax style:
connect(sender, &Sender::textChanged, receiver, &Receiver::updateText);
In the traditional way of writing code, the verification of signals and slots only happens at runtime. In the new style, the compiler can detect the mismatches in the types of arguments and the existence of signals and slots at compile time.
Note
As long as it is possible, all connect
statements are written in the new syntax style in this book.
Now, let's get back to our application. I'll show you how to display some words in a plain text edit when the Hello button is clicked on. First of all, we need to create a slot since Qt has already predefined the clicked signal for the QPushButton
class. Edit mainwindow.h
and add a slot declaration:
#ifndef MAINWINDOW_H #define MAINWINDOW_H #include <QMainWindow> namespace Ui { class MainWindow; } class MainWindow : public QMainWindow { Q_OBJECT public: explicit MainWindow(QWidget *parent = 0); ~MainWindow(); private slots: void displayHello(); private: Ui::MainWindow *ui; }; #endif // MAINWINDOW_H
As you can see, it's the slots
keyword that distinguishes slots from ordinary functions. I declared it private to restrict access permission. You have to declare it a public
slot if you need to invoke it in an object from other classes. After this declaration, we have to implement it in the mainwindow.cpp
file. The implementation of the displayHello
slot is written as follows:
void MainWindow::displayHello() { ui->plainTextEdit->appendPlainText(QString("Hello")); }
It simply calls a member function of the plain text edit in order to add a Hello
QString to it. QString
is a core class that Qt has introduced. It provides a Unicode character string, which efficiently solves the internationalization issue. It's also convenient to convert a QString
class to std::string
and vice versa. Besides, just like the other QObject
classes, QString
uses an implicit sharing mechanism to reduce memory usage and avoid needless copying. If you don't want to get concerned about the scenes shown in the following code, just take QString
as an improved version of std::string
. Now, we need to connect this slot to the signal that the Hello push button will emit:
MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWindow) { ui->setupUi(this); connect(ui->helloButton, &QPushButton::clicked, this, &MainWindow::displayHello); }
What I did is add a connect
statement to the constructor of MainWindow
. In fact, we can connect signals and slots anywhere and at any time. However, the connection only exists after this line gets executed. So, it's a common practice to have lots of connect
statements in the construction functions instead of spreading them out. For a better understanding, run your application and see what happens when you click on the Hello button. Every time you click, a Hello text will be appended to the plain text edit. The following screenshot is what happened after we clicked on the Hello button three times:
Getting confused? Let me walk you through this. When you clicked on the Hello button, it emitted a clicked signal. Then, the code inside the displayHello
slot got executed, because we connected the clicked signal of the Hello button to the displayHello
slot of MainWindow
. What the displayHello
slot did is that it simply appended Hello
to the plain text edit.
It may take you some time to fully understand the mechanism of signals and slots. Just take your time. I'll show you another example of how to disconnect such a connection after we clicked on the Hola button. Similarly, add a declaration of the slot to the header file and define it in the source file. I pasted the content of the mainwindow.h
header file, as follows:
#ifndef MAINWINDOW_H #define MAINWINDOW_H #include <QMainWindow> namespace Ui { class MainWindow; } class MainWindow : public QMainWindow { Q_OBJECT public: explicit MainWindow(QWidget *parent = 0); ~MainWindow(); private slots: void displayHello(); void onHolaClicked(); private: Ui::MainWindow *ui; }; #endif // MAINWINDOW_H
It's only declaring a onHolaClicked
slot that differed from the original. Here's the content of the source file:
#include "mainwindow.h" #include "ui_mainwindow.h" MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWindow) { ui->setupUi(this); connect(ui->helloButton, &QPushButton::clicked, this, &MainWindow::displayHello); connect(ui->holaButton, &QPushButton::clicked, this, &MainWindow::onHolaClicked); } MainWindow::~MainWindow() { delete ui; } void MainWindow::displayHello() { ui->plainTextEdit->appendPlainText(QString("Hello")); } void MainWindow::onHolaClicked() { ui->plainTextEdit->appendPlainText(QString("Hola")); disconnect(ui->helloButton, &QPushButton::clicked, this, &MainWindow::displayHello); }
You'll find that the Hello button no longer works after you clicked on the Hola button. This is because in the onHolaClicked
slot, we just disconnected the binding between the clicked signal of helloButton
and the displayHello
slot of MainWindow
. Actually, disconnect
has some overloaded functions and can be used in a more destructive way. For example, you may want to disconnect all connections between a specific signal sender and a specific receiver:
disconnect(ui->helloButton, 0, this, 0);
If you want to disconnect all the slots associated with a signal, since a signal can be connected to as many slots as you wish, the code can be written like this:
disconnect(ui->helloButton, &QPushButton::clicked, 0, 0);
We can also disconnect all the signals in an object, whatever slots they might be connected to. The following code will disconnect all the signals in helloButton
, which of course includes the clicked signal:
disconnect(ui->helloButton, 0, 0, 0);
Just like a signal, a slot can be connected to as many signals as you want. However, there's no such function to disconnect a specific slot from all the signals.
Tip
Always remember the signals and slots that you have connected.
Apart from the new syntax for traditional connections of signals and slots, Qt 5 has offered a new way to simplify such a binding process with C++11 lambda expressions. As you may have noticed, it's kind of tedious to declare a slot in the header file, define it in the source code file, and then connect it to a signal. It's worthwhile if the slot has a lot of statements, otherwise it becomes time consuming and increases the complexity. Before we go any further, we need to turn on C++11 support on Qt. Edit the pro file (layout_demo.pro
in my example) and add the following line to it:
CONFIG += c++11
Note
Note that some old compilers don't support C++11. If this happens, upgrade your compiler.
Now, you need to navigate to Build | Run qmake to reconfigure the project properly. If everything is okay, we can go back to editing the mainwindow.cpp
file. This way, there is no need to declare a slot and define and connect it. Just add a connect
statement to the construction function of MainWindow
:
connect(ui->bonjourButton, &QPushButton::clicked, [this](){ ui->plainTextEdit->appendPlainText(QString("Bonjour")); });
It's very straightforward, isn't it? The third argument is a lambda expression, which was added to C++ since C++11.
Note
For more details about lambda expression, visit http://en.cppreference.com/w/cpp/language/lambda.
This pair of signal and slot connection is done if you don't do need to to disconnect such a connection. However, if you need, you have to save this connection, which is a QMetaObject::Connection
type. In order to disconnect this connection elsewhere, it would be better to declare it as a variable of MainWindow
. So the header file becomes as follows:
#ifndef MAINWINDOW_H #define MAINWINDOW_H #include <QMainWindow> namespace Ui { class MainWindow; } class MainWindow : public QMainWindow { Q_OBJECT public: explicit MainWindow(QWidget *parent = 0); ~MainWindow(); private slots: void displayHello(); void onHolaClicked(); private: Ui::MainWindow *ui; QMetaObject::Connection bonjourConnection; }; #endif // MAINWINDOW_H
Here, I declared bonjourConnection
as an object of QMetaObject::Connection
so that we can save the connection dealing with an unnamed slot. Similarly, the disconnection happens in onHolaClicked
, so there won't be any new Bonjour
text on screen after we click on the Hola button. Here is the content of mainwindow.cpp
:
#include "mainwindow.h" #include "ui_mainwindow.h" MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWindow) { ui->setupUi(this); connect(ui->helloButton, &QPushButton::clicked, this, &MainWindow::displayHello); connect(ui->holaButton, &QPushButton::clicked, this, &MainWindow::onHolaClicked); bonjourConnection = connect(ui->bonjourButton, &QPushButton::clicked, [this](){ ui->plainTextEdit->appendPlainText(QString("Bonjour")); }); } MainWindow::~MainWindow() { delete ui; } void MainWindow::displayHello() { ui->plainTextEdit->appendPlainText(QString("Hello")); } void MainWindow::onHolaClicked() { ui->plainTextEdit->appendPlainText(QString("Hola")); disconnect(ui->helloButton, &QPushButton::clicked, this, &MainWindow::displayHello); disconnect(bonjourConnection); }
Tip
Downloading the example code
You can download the example code files from your account at http://www.packtpub.com for all the Packt Publishing books you have purchased. If you purchased this book elsewhere, you can visit http://www.packtpub.com/support and register to have the files e-mailed directly to you.
This is indeed another new usage of disconnect
. It takes in a QMetaObject::Connection
object as the only argument. You'll thank this new overloaded function if you're going to use the lambda expression as a slot.