Book Image

QT5 Blueprints

By : Symeon Huang
Book Image

QT5 Blueprints

By: Symeon Huang

Overview of this book

Table of Contents (17 chapters)
Qt 5 Blueprints
Credits
About the Author
About the Reviewers
www.PacktPub.com
Preface
Index

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.