Mastering Qt 5 - Second Edition

4.7 (9 reviews total)
By Guillaume Lazar , Robin Penea
  • Instant online access to over 7,500+ books and videos
  • Constantly updated with 100+ new titles each month
  • Breadth and depth in over 1,000+ technologies
  1. Get Your Qt Feet Wet

About this book

Qt 5.11 is an app development framework that provides a great user experience and develops full capability applications with Qt Widgets, QML, and even Qt 3D. Whether you're building GUI prototypes or fully-fledged cross-platform GUI applications with a native look and feel, Mastering Qt 5 is your fastest, easiest, and most powerful solution. This book addresses various challenges and teaches you to successfully develop cross-platform applications using the Qt framework, with the help of well-organized projects.

Working through this book, you will gain a better understanding of the Qt framework, as well as the tools required to resolve serious issues, such as linking, debugging, and multithreading. You'll start off your journey by discovering the new Qt 5.11 features, soon followed by exploring different platforms and learning to tame them. In addition to this, you'll interact with a gamepad using Qt Gamepad. Each chapter is a logical step for you to complete in order to master Qt.

By the end of this book, you'll have created an application that has been tested and is ready to be shipped.

Publication date:
August 2018
Publisher
Packt
Pages
534
ISBN
9781788995399

 

Chapter 1. Get Your Qt Feet Wet

If you know C++ but have never touched Qt, or if you have already made some intermediate Qt applications, this chapter will ensure that your Qt foundations are solid before studying advanced concepts in the following chapters.

We will teach you how to create a simple todo application using Qt Creator. This application will display a list of tasks that you can create/update/delete. We will cover the Qt Creator and Qt Designer interfaces, an introduction to the signal/slot mechanism, the creation of a custom widget with custom signals/slots, and its integration into your application.

You will implement a todo app using new C++14 semantics: lambdas, auto variables, and for loops. Each of these concepts will be explained in depth and will be used throughout this book.

By the end of this chapter, you will be able to create a desktop application with a flexible UI using Qt widgets and new C++ semantics.

In this chapter, we will cover the following topics:

  • Qt project basic structure
  • MainWindow structure
  • Qt Designer interface
  • Signals and slots
  • Custom QWidget
  • C++14 lambda, auto, and for each
 

Qt project basic structure


First, start Qt Creator.

By default, Qt Creator is configured to use and generate lowercase filenames (such as mainwindow.cpp). As this book is using the Pascal case (that is, MainWindow.cpp), you should disable it. Uncheck the Tools | Options... | C++ | File Naming | Lower case file names option.

You can now create a new Qt project via File | New File or Project | Application | Qt Widgets Application | Choose.

The wizard will then guide you through four steps:

  1. Location: Choose a project name and location
  2. Kits: Target platforms that your project aims at (Desktop, Android, and so on)
  3. Details: Input base class information and a name for the generated class
  4. Summary: Allows you to configure your new project as a subproject and automatically add it to a version-control system

Even if all the default values can be kept, please at least set a useful project name, such as "todo" or "TodoApp." We won't blame you if you want to call it "Untitled" or "Hello world."

Once done, Qt Creator will generate several files, which you can see in the Projects hierarchy view:

The .pro file is Qt's configuration project file. As Qt adds specific file formats and C++ keywords, an intermediate build step is performed, parsing all the files to generate the final files. This process is done by qmake, an executable from the Qt SDK. It will also generate the final Makefiles for your project.

A basic .pro file generally contains:

  • Qt modules used (such as core, gui)
  • A target name (such as todo, todo.exe)
  • A project template (such as app, lib)
  • Sources, headers, and forms

There are some great features that come with Qt and C++14. This book will showcase them in all its projects. For the GCC and CLANG compilers, you must add CONFIG += c++14 to the .pro file to enable C++14 on a Qt project, as shown in the following code:

QT       += core gui 
CONFIG   += c++14 
 
greaterThan(QT_MAJOR_VERSION, 4): QT += widgets 
 
TARGET = todo 
TEMPLATE = app 
 
SOURCES += main.cpp \ 
           MainWindow.cpp 
 
HEADERS  += MainWindow.h \ 
 
FORMS    += MainWindow.ui \ 

The MainWindow.h and MainWindow.cpp files are the header/source for the MainWindow class. These files contain the default GUI generated by the wizard.

The MainWindow.ui file is your UI design file written in XML format. It can be edited more easily with Qt Designer. This tool is a What You See Is What You Get (WYSIWYG) editor that helps you to add and adjust your graphical components, known as widgets.

Here is the main.cpp file, with its well-known function:

#include "MainWindow.h" 
#include <QApplication> 
 
int main(int argc, char *argv[]) 
{ 
    QApplication a(argc, argv); 
    MainWindow w; 
    w.show(); 
 
    return a.exec(); 
} 

Usually, the main.cpp file contains the program entry point. It will, by default, perform three actions:

  • InstantiateQApplication
  • Instantiate and show your main window
  • Execute the blocking main event loop

This is the bottom-left toolbar for Qt Creator:

Use it to build and start your todo application in debug mode:

  1. Check that the project is in Debug build mode
  2. Use the hammer button to build your project
  3. Start debugging using the green Play button with the little blue bug

 

 

 

 

 

 

You will discover a wonderful and beautifully empty window. We will rectify this after explaining how MainWindow is constructed:

Note

  • Press Ctrl + B (for Windows/Linux) or Command + B (for Mac) to build your project
  • Press F5 (for Windows/Linux) or Command + R (for Mac) to run your application in debug mode
 

MainWindow structure


This generated class is a perfect yet simple example of Qt framework usage; we will dissect it together. As mentioned previously, the MainWindow.ui file describes your UI design and the MainWindow.h/MainWindow.cpp files define the C++ object where you can manipulate the UI with code.

It's important to take a look at the MainWindow.h header file. Our MainWindow object inherits from Qt's QMainWindow class:

#include <QMainWindow> 
 
namespace Ui { 
class MainWindow; 
} 
 
class MainWindow : public QMainWindow 
{ 
    Q_OBJECT 
 
public: 
    explicit MainWindow(QWidget *parent = 0); 
    ~MainWindow(); 
private: 
    Ui::MainWindow *ui; 
}; 

As our class inherits from the QMainWindow class, we will have to add the corresponding #include at the top of the header file. The second part is the forward declaration of Ui::MainWindow, as we only declare a pointer.

Q_OBJECT can look a little strange to a non-Qt developer. This macro allows the class to define its own signals/slots through Qt's meta-object system. These features will be covered later in this chapter in the section Signals and slots.

This class defines a public constructor and destructor. The latter is pretty common but the constructor takes a parent parameter. This parameter is a QWidget pointer that is null by default.

QWidget is a UI component. It can be a label, a textbox, a button, and so on. If you define a parent-child relationship between your window, layout, and other UI widgets, the memory management of your application will be easier. Indeed, in this case, deleting the parent is enough because its destructor will take care of also deleting its child recursively.

Our MainWindow class extends QMainWindow from the Qt framework. We have a ui member variable in the private fields. Its type is a pointer of Ui::MainWindow, which is defined in the ui_MainWindow.h file generated by Qt. It's the C++ transcription of the MainWindow.ui UI design file. The ui member variable will allow you to interact with your C++ UI components (QLabel, QPushButton, and so on), as shown in the following figure:

Note

If your class only uses pointers or references for a class type, you can avoid including the header by using forward declaration. That will drastically reduce compilation time and avoid circular dependencies.

Now that the header part is done, we can talk about the MainWindow.cpp source file.

In the following code snippet, the first include is our class header. The second one is required by the generated Ui::MainWindow class. This include is required as we only use a forward declaration in the header:

#include "MainWindow.h" 
#include "ui_MainWindow.h" 
 
MainWindow::MainWindow(QWidget *parent) : 
    QMainWindow(parent), 
    ui(new Ui::MainWindow) 
{ 
    ui->setupUi(this); 

In many cases, Qt generates good code using the initializer list. The parent argument is used to call the QMainWindow superclass constructor. Our ui private member variable is also initialized.

Now that ui is initialized, we must call the setupUi function to initialize all the widgets used by the MainWindow.ui design file:

As the pointer is initialized in the constructor, it must be cleaned in the destructor:

MainWindow::~MainWindow() 
{ 
    delete ui; 
} 
 

Qt Designer interface


Qt Designer is a major tool for developing Qt applications. This WYSIWYG editor will help you to easily design your GUI. If you switch between Edit mode and Design mode for the MainWindow.ui file, you will see the real XML content and the designer:

The designer displays several parts:

  • Form Editor (1): A visual representation of the form (empty for now)
  • Widget Box (2): Contains all the major widgets that can be used with your form
  • Object Inspector (3): Displays your form as a hierarchical tree
  • Property Editor (4): Enumerates the properties of the selected widget
  • Action Editor/Signal & Slots Editor (5): Handles toolbar actions and connections between your objects

It's time to embellish this empty window! Let's drag and drop a Label widget from the Display Widgets section on the form. You can change the name and the text properties directly from the Properties editor.

As we are making a todo application, we suggest these properties:

  • objectName: statusLabel
  • text: Status: 0 todo / 0 done

This label will later display the count of todo tasks and the count of tasks already done. Save, build, and start your application. You should now see your new label in the window.

You can now add a push button with those properties:

  • objectName: addTaskButton
  • text: Add task

You should get a result close to the following:

Note

You can edit the text property of a widget directly on your form by double-clicking on it!

The design of the MainWindow.ui file is ready, we can now study the signals and slots.

 

Signals and slots


The Qt framework offers a flexible message-exchange mechanism that is composed of three concepts:

  • signal is a message sent by an object
  • slot is a function that will be called when this signal is triggered
  • The connect function specifies which signal is linked to which slot

Qt already provides signals and slots for its classes, which you can use in your application. For example, QPushButton has signal clicked(), which will be triggered when the user clicks on the button. Another example: the QApplication class has a slot quit() function, which can be called when you want to terminate your application.

Here is why you will love Qt signals and slots:

  • A slot remains an ordinary function, so you can call it yourself
  • A single signal can be linked to different slots
  • A single slot can be called by different linked signals
  • A connection can be made between a signal and a slot from different objects, and even between objects living inside different threads

Keep in mind that to be able to connect a signal to a slot, their methods' signatures must match. The count, order, and type of arguments must be identical. Note that signals and slots never return values.

This is the syntax of a Qt connection:

connect(sender, &Sender::signalName,  
    receiver, &Receiver::slotName); 

The first test that we can do to use this wonderful mechanism is to connect an existing signal with an existing slot. We will add this connect call to the MainWindow constructor:

MainWindow::MainWindow(QWidget *parent) : 
    QMainWindow(parent), 
    ui(new Ui::MainWindow) 
{ 
    ui->setupUi(this); 
    connect(ui->addTaskButton, &QPushButton::clicked, 
    QApplication::instance(), &QApplication::quit); 
} 

Let's analyze how a connection is done:

  • sender: Object that will send the signal. In our example, the QPushButton named addTaskButton is added from the UI designer.
  • &Sender::signalName: Pointer to the member signal function. Here, we want do something when the clicked signal is triggered.
  • receiver: Object that will receive and handle the signal. In our case, it is the QApplication object created in main.cpp.
  • &Receiver::slotName: Pointer to one of the receiver's member slot functions. In this example, we use the built-in quit() slot from QApplication, which will exit the application.

You can now compile and run this short example. You will terminate the application if you click on addTaskButton of your MainWindow.

Note

You can connect a signal to another signal. The second signal will be emitted when the first one is triggered.

Now that you know how to connect a signal to an existing slot, let's see how to declare and implement a custom addTask() slot in our MainWindow class. This slot will be called when the user clicks on ui->addTaskButton.

The following is the updated MainWindow.h:

class MainWindow : public QMainWindow 
{ 
    Q_OBJECT 
 
public: 
    explicit MainWindow(QWidget *parent = 0); 
    ~MainWindow(); 
 
public slots: 
    void addTask(); 
 
private: 
    Ui::MainWindow *ui; 
}; 

Qt uses a specific slots keyword to identify slots. Since a slot is a function, you can always adjust the visibility (public, protected, or private) depending on your needs.

We will now add this slot implementation in the MainWindow.cpp file:

void MainWindow::addTask() 
{ 
    qDebug() << "User clicked on the button!"; 
} 

Qt provides an efficient way of displaying the debug information with the QDebug class. An easy way to obtain a QDebug object is to call the qDebug() function. Then you can use the << stream operator to send your debug information.

Update the top of the file like the following:

#include <QDebug> 
 
MainWindow::MainWindow(QWidget *parent) : 
    QMainWindow(parent), 
    ui(new Ui::MainWindow) 
{ 
    ui->setupUi(this); 
    connect(ui->addTaskButton, &QPushButton::clicked, 
    this, &MainWindow::addTask); 
} 

Since we now use qDebug() in our slot, we must include <QDebug>. The updated connect now calls our custom slot instead of quitting the application.

Build and run the application. If you click on the button, you will see your debug message inside the Application Output Qt Creator tab.

 

Custom QWidget


We now have to create the Task class that will hold our data (task name and completed status). This class will have its form file separated from MainWindow. Qt Creator provides an automatic tool to generate a base class and the associated form.

Click on File | New File or Project | Qt | Qt Designer Form Class. There are several form templates; you will recognize Main Window, which Qt Creator created for us when we started the todo app project. Select Widget and name the class Task, then click on Next. Here is a summary of what Qt Creator will do:

  1. Create a Task.h file and a Task.cpp file
  2. Create the associated Task.ui and do the plumbing to connect to Task.h
  3. Add these three freshly-created files to todo.pro so they can be compiled

Finish and voilà, the Task class is ready to be filled. We will jump into Task.ui first. Start by dragging and dropping checkbox (objectName =checkbox) and Push Button (objectName =removeButton):

This layout looks great, let's ship it to the customers! Unless you have a pixel-perfect eye, your items are not very well aligned. You need to indicate how your widgets should be laid out and how they should react when the window geometry changes (for example, when the user resizes the window). For this, Qt has several default layout classes:

  • Vertical Layout: Widgets are vertically stacked
  • Horizontal Layout: Widgets are horizontally stacked
  • Grid Layout: Widgets are arranged in a grid that can be subdivided into smaller cells
  • Form Layout: Widgets are arranged like a web form, a label, and an input

A basic layout will try to constrain all its widgets to occupy equal surfaces. It will either change the widgets' shape or add extra margins, depending on each widget's constraints. Check Box will not be stretched but Push Button will.

In our Task object, we want this to be horizontally-stacked. In the Form Editor tab, right-click on the window and select Lay out | Lay out Horizontally. Each time you add a new widget in this layout, it will be arranged horizontally.

Now add a Push Button (objectName = editButton) just after the checkbox object.

The Form Editor window offers a realistic preview of how your UI will render. If you stretch the window now, you can observe how each widget will react to this event. When resizing horizontally, you can note that the push buttons are stretched. It looks bad. We need something to "hint" to the layout that these buttons should not be stretched. Enter the Spacer widget. Take Horizontal Spacer in the widget box and drop it after the checkbox object:

A spacer is a special widget that tries to push (horizontally or vertically) adjacent widgets to force them to take up as little space as possible. The editButton and removeButton objects will take up only the space of their text and will be pushed to the edge of the window when resized.

You can add sub layouts of any type in a form (vertical, horizontal, grid, form) and create a complex-looking application with a combination of widgets, spacers, and layouts. These tools are targeted at designing a good-looking desktop application that can react properly to different window geometries.

The Designer part is finished, so we can switch to the Task source code. Since we created a Qt Designer Form class, Task is closely linked to its UI. We will use this as a leverage to store our model in a single place. When we create a Task object, it has to have a name:

#ifndef TASK_H 
#define TASK_H 
 
#include <QWidget> 
#include <QString> 
 
namespace Ui { 
class Task; 
} 
 
class Task : public QWidget 
{ 
    Q_OBJECT 
 
public: 
    explicit Task(const QString& name, QWidget *parent = 0); 
    ~Task(); 
 
    void setName(const QString& name); 
    QString name() const; 
    bool isCompleted() const; 
     
private: 
    Ui::Task *ui; 
}; 
 
#endif // TASK_H 

The constructor specifies a name and, as you can see, there are no private fields storing any state of the object. All of this will be done in the form part. We also added some getters and setters that will interact with the form. It's better to have a model completely separated from the UI, but our example is simple enough to merge them. Moreover, the Task implementation details are hidden from the outside world and can still be refactored later on. Here is the content of the Task.cpp file:

#include "Task.h" 
#include "ui_Task.h" 
 
Task::Task(const QString& name, QWidget *parent) : 
        QWidget(parent), 
        ui(new Ui::Task) 
{ 
    ui->setupUi(this); 
    setName(name); 
} 
 
Task::~Task() 
{ 
    delete ui; 
} 
 
void Task::setName(const QString& name) 
{ 
    ui->checkbox->setText(name); 
} 
 
QString Task::name() const 
{ 
    return ui->checkbox->text(); 
} 
 
bool Task::isCompleted() const 
{ 
   return ui->checkbox->isChecked(); 
} 

The implementation is straightforward; we store the information in ui->checkbox and both the name() and the isCompleted() getters take their data from ui->checkbox.

 

Adding a task


We will now rearrange the layout of MainWindow to be able to display our todo tasks. At this moment, there is no widget where we can display our tasks. Open the MainWindow.ui file. We will use Qt designer to create the UI:

  1. Drag and drop Horizontal layout inside the central widget and rename it toolbarLayout 
  2. Right-click on the central widget and select Lay out vertically
  3. Drag and drop the label, spacer, and button inside toolbarLayout 
  4. Drag and drop Vertical layout under toolbarLayout  (a blue helper line will be displayed) and rename it tasksLayout 
  5. Add a vertical spacer under tasksLayout  (again, check the blue helper line):

Voilà! Your MainWindow form is finished. Later in the chapter you will learn how to dynamically create and add some Task widgets to the empty tasksLayout.

To sum up, we have:

  • A vertical layout for centralWidget that contains the toolbarLayout item and the tasksLayout item.
  • A vertical spacer pushing these layouts to the top, forcing them to take up the smallest possible space.
  • Gotten rid of menuBar, mainToolBar, and statusBar. Qt Creator created them automatically, we simply don't need them for our purposes. You can guess their uses from their names.

Don't forget to rename the MainWindow title to Todo by selecting MainWindow in the Object Inspector window and editing the Qwidget | windowTitle property. Your app deserves to be named properly.

Note

Press Shift + F4 in Designer mode to switch between the form editor and the source.

Now that the MainWindow UI is ready to welcome tasks, let's switch to the code part. The application has to keep track of new tasks. Add the following in the MainWindow.h file:

#include <QVector> 
 
#include "Task.h" 
 
class MainWindow : public QMainWindow 
{ 
    // MAINWINDOW_H 
 
public slots: 
    void addTask(); 
 
private: 
    Ui::MainWindow *ui; 
    QVector<Task*> mTasks; 
};

The QVector is the Qt container class providing a dynamic array, which is an equivalent of std::vector. Generally speaking, the rule says that STL containers are more customizable, but they may miss some features compared to Qt containers. If you use C++11 smart pointers, you should favor std containers, but we will get into that later.

In the Qt documentation of QVector, you may stumble upon the following statement: For most purposes,QListis the right class to use. There is a debate about this in the Qt community:

  • Do you often need to insert objects larger than a pointer at the beginning or in the middle of your array? Use a QList class.
  • Need contiguous memory allocation? Less CPU and memory overhead? Use a QVector class.

The already-added addTask() slot will now becalledeach time we want to add a new Taskobject to the mTasksfunction.

Let's fill our QVector tasks each time addTaskButton is clicked. First, we connect the clicked() signal in the MainWindow.cpp file:

MainWindow::MainWindow(QWidget *parent) : 
    QMainWindow(parent), 
    ui(new Ui::MainWindow), 
    mTasks() 
{ 
    ui->setupUi(this); 
    connect(ui->addTaskButton, &QPushButton::clicked,  
    this, &MainWindow::addTask); 
}; 

Note

As a best practice, try to always initialize member variables in the initializer list and respect the order of variable declarations. Your code will run faster and you will avoid unnecessary variable copies. For more information, take a look at the standard C++ documentation at https://isocpp.org/wiki/faq/ctors#init-lists.

The body of the addTask() function should look like this:

void MainWindow::addTask() 
{ 
        qDebug() << "Adding new task"; 
        Task* task = new Task("Untitled task"); 
        mTasks.append(task); 
        ui->tasksLayout->addWidget(task); 
} 

We created a new task and added it to our mTask vector. Because the Task object is a QWidget, we also added it directly to tasksLayout. An important thing to note here is that we never managed our new task's memory. Where is the delete task instruction? This is a key feature of the Qt Framework we started to mention earlier in the chapter; the QObject class parenting automatically handles object destruction.

In the preceding code snippet, the ui->tasksLayout->addWidget(task) call has an interesting side-effect: the ownership of the task is transferred to the layout's widget. The QObject* parent defined in the Task constructor is now centralWidget of the MainWindow. The Task destructor will be called when MainWindow releases its own memory by recursively iterating through its children and calling their destructor.

This feature has interesting consequences. First, if you use the QObject parenting model in your application, you will have much less memory to manage. Second, it can collide with some new C++11 semantics, specifically the smart pointers. We will get into the details about this in later chapters.

 

Using a QDialog


We deserve something better than an untitled task. The user needs to define its name when created. The easiest path would be to display a dialog where the user can input the task name. Fortunately, Qt offers us a very configurable dialog that fits perfectly in addTask():

#include <QInputDialog> 
... 
void MainWindow::addTask() 
{ 
    bool ok; 
    QString name = QInputDialog::getText(this,  
        tr("Add task"), 
        tr("Task name"), 
        QLineEdit::Normal, 
        tr("Untitled task"),               &ok); 
    if (ok && !name.isEmpty()) { 
        qDebug() << "Adding new task"; 
        Task* task = new Task(name); 
        mTasks.append(task); 
        ui->tasksLayout->addWidget(task); 
    } 
}

The QinputDialog::getText function is a static blocking function that displays the dialog. When the user validates/cancels the dialog, the code continues. If we run the application and try to add a new task, we will see this:

The QInputDialog::getText signature looks like this:

QString QinputDialog::getText( 
  QWidget* parent,  
      const QString& title,  
      const QString& label,  
      QLineEdit::EchoMode mode = QLineEdit::Normal,  
      const QString& text = QString(),  
      bool* ok = 0, ...)

Let's break it down:

  • parent: This is the parent widget (MainWindow) to which QinputDialog is attached. This is another instance of the QObject class's parenting model.
  • title: This is the title displayed in the window title. In our example, we use tr("Add task"), which is how Qt handles i18n in your code. Later, we will see how to provide multiple translations for a given string.
  • label: This is the label displayed right above the input text field.
  • mode: This is how the input field is rendered (password mode will hide the text).
  • ok: This is a pointer to a variable that is set to true if the user presses OK and false if the user presses Cancel.
  • QString: The returned QString is what the user has typed.

There are a few more optional parameters we can safely ignore for our example.

 

Distributing code responsibility


Great, the user can now specify a task name when created. What if they make an error when typing the name? The next logical step is to be able to rename the task after we create it. We'll take a slightly different approach. We want our Task to be as autonomous as possible. If we attach it to another component (rather than MainWindow), this renaming feature has to keep working. Thus, this responsibility has to be given to the Task class:

// In Task.h 
public slots: 
    void rename(); 
 
// In Task.cpp 
#include <QInputDialog> 
 
Task::Task(const QString& name, QWidget *parent) : 
       QWidget(parent), 
       ui(new Ui::Task) 
{ 
   ui->setupUi(this); 
   setName(name); 
   connect(ui->editButton, &QPushButton::clicked, this, &Task::rename); 
} 
... 
void Task::rename() 
{ 
    bool ok; 
    QString value = QInputDialog::getText(this, tr("Edit task"), 
                                          tr("Task name"), 
                                          QLineEdit::Normal, 
                                          this->name(), &ok); 
    if (ok && !value.isEmpty()) { 
        setName(value); 
    } 
} 

We add a public rename() slot to connect it to a signal. The body of rename() reuses what we had previously covered with QInputDialog. The only difference is the QInputDialog default value, which is the current task name. When setName(value) is called, the UI is instantly refreshed with the new value; there's nothing to synchronize or update, the Qt main loop will do its job.

The nice thing is that Task::rename() is completely autonomous. Nothing has been modified in MainWindow, so we have effectively zero coupling between our Task and the parent QWidget.

 

Emitting a custom signal using lambdas


The remove task is straightforward to implement, but we'll study some new concepts along the way. The Task has to notify its owner and parent (the MainWindow) that the removeTaskButton QPushButton has been clicked. We'll implement this by defining a custom removed signal in the Task.h files:

class Task : public QWidget 
{ 
    ... 
public slots: 
    void rename(); 
signals: 
    void removed(Task* task); 
   ... 
}; 

Like we did for the slots, we have to add the Qt keyword signals in our header. Since signal is used only to notify another class, the public keyword is not needed (it even raises a compilation error). signal is simply a notification sent to the receiver (the connected slot); it implies that there is no function body for the removed(Task* task) function.

We added the task parameter to allow the receiver to know which task asked to be removed. The next step is to emit the removed signal upon the removeButton click. This is done in the Task.cpp file:

Task::Task(const QString& name, QWidget *parent) : 
        QWidget(parent), 
        ui(new Ui::Task) 
{ 
    ui->setupUi(this); 
    ... 
    connect(ui->removeButton, &QPushButton::clicked, [this] { 
        emit removed(this); 
    }); 
} 

This code excerpt shows a very interesting feature of C++11: lambdas. In our example, lambda is the following part:

[this] { 
        emit removed(this); 
    }); 

Here, we connected the clicked signal to an anonymous inline function, a lambda. Qt allows signal-relaying by connecting a signal to another signal if their signatures match. It's not the case here: the clicked signal has no parameter and the removed signal needs a Task*. A lambda avoids the declaration of a verbose slot in Task. Qt 5 accepts a lambda instead of a slot in a connect, and both syntaxes can be used.

Our lambda executes a single line of code: emit removed(this). Emit is a Qt macro that will trigger the connected slot with what we passed as the parameter. As we said earlier, removed(Task* this) has no function body, its purpose is to notify the registered slot of an event.

Lambdas are a great addition to C++. They offer a very practical way of defining short functions in your code. Technically, a lambda is the construction of a closure capable of capturing variables in its scope. The full syntax goes like this:

[ capture-list ] ( params ) -> ret { body }

Let's study each part of this statement:

  • capture-list: Defines what variables will be visible inside the lambda scope.
  • params: This is the function parameter's type list that can be passed to the lambda scope. There are no parameters in our case. We might have written [this] () { ... }, but C++11 lets us skip the parentheses altogether.
  • ret: This is the return type of the lambda function. Just like params, this parameter can be omitted if the return type is void.
  • body: This is obviously your code body where you have access to your capture-list and params, and which must return a variable with a ret type.

In our example, we captured the this pointer to be able to:

  • Have a reference on the removed() function, which is part of the Task class. If we did not capture this, the compiler would have shouted error: 'this' was not captured for this lambda function emit removed(this);.
  • Pass this to the removed signal: the caller needs to know which task triggered removed.

capture-list relies on standard C++ semantics: capture variables by copy or by reference. Let's say that we wanted to print a log of the name constructor parameter and we capture it by reference in our lambda:

connect(ui->removeButton, &QPushButton::clicked, [this, &name] { 
        qDebug() << "Trying to remove" << name; 
        this->emit removed(this); 
    }); 

This code will compile fine. Unfortunately, the runtime will crash with a dazzling segmentation fault when we try to remove a Task. What happened? As we said, our lambda is an anonymous function that will be executed when the clicked() signal has been emitted. We captured the name reference, but this reference may be invalid once we get out of the Task constructor (more precisely, from the caller scope). The qDebug() function will then try to display an unreachable code and crash.

You really need to be careful with what you capture and the context in which your lambda will be executed. In this example, the segmentation fault can be amended by capturing name by copy:

connect(ui->removeButton, &QPushButton::clicked, [this, name] { 
        qDebug() << "Trying to remove" << name; 
        this->emit removed(this); 
    }); 

Note

  • You can capture by copy or reference all variables that are reachable in the function where you define your lambda with the = and & syntax.
  • The this variable is a special case of the capture list. You cannot capture it by the [&this] reference and the compiler will warn you if you are in this situation: [=, this]. Don't do this. Kittens will die.

Our lambda is passed directly as a parameter to the connect function. In other words, the lambda is a variable. This has many consequences: we can call it, assign it, and return it. To illustrate a "fully formed" lambda, we can define one that returns a formatted version of the task name. The sole purpose of this snippet is to investigate the lambda function's machinery. Don't include the following code in your todo app, your colleagues might call you a "functional zealot":

connect(ui->removeButton, &QPushButton::clicked, [this, name] { 
    qDebug() << "Trying to remove" << 
        [] (const QString& taskName) -> QString { 
            return "-------- " + taskName.toUpper(); 
    }(name); 
    emit removed(this); 
}); 

Here we did a tricky thing. We called qDebug(). Inside this call, we defined a lambda that is immediately executed. Let's analyze it:

  • []: We performed no capture. lambda does not depend on the enclosing function.
  • (const Qstring& taskName): When this lambda is called, it will expect a QString to work on.
  • -> QString: The returned value of the lambda will be a QString.
  • return "------- " + taskName.toUpper(): The body of our lambda. We return a concatenation of a string and the uppercase version of the taskName parameter. As you can see, string-manipulation becomes a lot easier with Qt.
  • (name): Here comes the catch. Now that the lambda function is defined, we can call it by passing the name parameter. In a single expression, we define it then call it. The qDebug() function will simply print the result.

The real benefit of this lambda will emerge if we are able to assign it to a variable and call it multiple times. C++ is statically typed, so we must provide the type of our lambda variable. In the language specification, a lambda type cannot be explicitly defined. We will soon see how we can do it with C++11. For now, let's finish our remove feature.

The task now emits the removed() signal. This signal has to be consumed by MainWindow:

// in MainWindow.h 
public slots: 
    void addTask(); 
    void removeTask(Task* task); 
 
// In MainWindow.cpp 
void MainWindow::addTask() 
{ 
    ... 
    if (ok && !name.isEmpty()) { 
        qDebug() << "Adding new task"; 
        Task* task = new Task(name); 
        connect(task, &Task::removed,  
       this, &MainWindow::removeTask); 
    ... 
    } 
} 
 
void MainWindow::removeTask(Task* task) 
{ 
    mTasks.removeOne(task); 
    ui->tasksLayout->removeWidget(task); 
    delete task; 
} 

MainWindow::removeTask() must match the signal signature. The connection is made when the task is created. The interesting part comes in the implementation of MainWindow::removeTask().

The task is first removed from the mTasks vector. It is then removed from tasksLayout. The last step is to delete Task. The destructor will unregister itself from centralWidget of MainWindow. In this case, we don't rely on the Qt hierarchical parent-children system for the QObject life cycle because we want to delete Task before the destruction of MainWindow.

 

Simplifying with the auto type and a range-based for loop


The final step to a complete CRUD of our tasks is to implement the completed task feature. We'll implement the following:

  • Click on the checkbox to mark the task as completed
  • Strike the task name
  • Update the status label in MainWindow

The checkbox click-handling follows the same pattern as removed:

// In Task.h 
signals: 
    void removed(Task* task); 
    void statusChanged(Task* task); 
private slots: 
    void checked(bool checked); 
 
// in Task.cpp 
Task::Task(const QString& name, QWidget *parent) : 
        QWidget(parent), 
        ui(new Ui::Task) 
{ 
    ... 
 
    connect(ui->checkbox, &QCheckBox::toggled,  
    this, &Task::checked); 
} 
 
... 
 
void Task::checked(bool checked) 
{ 
    QFont font(ui->checkbox->font()); 
    font.setStrikeOut(checked); 
    ui->checkbox->setFont(font); 
    emit statusChanged(this); 
} 

We define a checked(bool checked) slot that will be connected to the QCheckBox::toggled signal. In slot checked(), we strike out the checkbox text according to the bool checked value. This is done by using the QFont class. We create a copied font from checkbox->font(), modify it, and assign it back to ui->checkbox. Event if the original font was in bold or with a special size, its appearance would still be guaranteed to stay the same.

Note

Play around with the font object in Qt Designer. Select checkbox in the Task.ui file and go to Properties Editor | QWidget | font.

The last instruction notifies MainWindow that the Task status has changed. The signal name is statusChanged, rather than checkboxChecked, in order to hide the implementation details of the task. Now add the following code in the MainWindow.h file:

// In MainWindow.h 
public: 
    void updateStatus(); 
public slots: 
    void addTask(); 
    void removeTask(Task* task); 
    void taskStatusChanged(Task* task); 
 
// In MainWindow.cpp 
MainWindow::MainWindow(QWidget *parent) : 
    QMainWindow(parent), 
    ui(new Ui::MainWindow), 
    mTasks() 
{ 
    ... 
    updateStatus(); 
    } 
} 
 
void MainWindow::addTask() 
{ 
   ... 
   if (ok && !name.isEmpty()) { 
       ... 
       connect(task, &Task::removed, this, 
               &MainWindow::removeTask); 
       connect(task, &Task::statusChanged, this, 
               &MainWindow::taskStatusChanged); 
       mTasks.append(task); 
       ui->tasksLayout->addWidget(task); 
       updateStatus(); 
   } 
} 
 
void MainWindow::removeTask(Task* task) 
{ 
   ... 
   delete task; 
   updateStatus(); 
} 
 
void MainWindow::taskStatusChanged(Task* /*task*/) 
{ 
    updateStatus(); 
} 
 
void MainWindow::updateStatus() 
{ 
    int completedCount = 0; 
    for(auto t : mTasks)  { 
        if (t->isCompleted()) { 
            completedCount++; 
        } 
    } 
    int todoCount = mTasks.size() - completedCount; 
 
    ui->statusLabel->setText(

 

 

        QString("Status: %1 todo / %2 completed") 
                             .arg(todoCount) 
                             .arg(completedCount)); 
} 

We defined a slot called taskStatusChanged, which is connected once a task is created. The single instruction of this slot is to call updateStatus(). This function iterates through the tasks and updates statusLabel. The updateStatus() function is called upon task creation and deletion.

In updateStatus(), we meet more new C++11 semantics:

for(auto t : mTasks)  { 
    ...  
} 

The for keyword lets us loop over a range-based container. Because QVector is an iterable container, we can use it here. The range declaration (auto t) is the type and variable name that will be assigned at each iteration. The range expression (mTasks) is simply the container on which the process will be done. Qt provides a custom implementation of the for (namely, foreach) loop targeted at prior versions of C++; you don't need it anymore.

The auto keyword is another great new semantic. The compiler deduces the variable type automatically based on the initializer. It relieves a lot of pain for cryptic iterators such as this:

// without the 'auto' keyword
std::vector<Task*>::const_iterator iterator = mTasks.toStdVector().begin();

// with the 'auto' keyword, how many neurones did you save? 
auto autoIter = mTasks.toStdVector().begin(); 

Since C++14, auto can even be used for function return types. It's a fabulous tool, but use it sparingly. If you put auto, the type should be obvious from the signature name/variable name.

Note

The auto keyword can be combined with const and references. You can write a for loop such as this: for (const auto & t : mTasks) { ... }.

Remember our half-bread lambda? With all the covered features, we can write:

auto prettyName = [] (const QString& taskName) -> QString { 
    return "-------- " + taskName.toUpper(); 
}; 
connect(ui->removeButton, &QPushButton::clicked,  
    [this, name, prettyName] { 
        qDebug() << "Trying to remove" << prettyName(name); 
        this->emit removed(this); 
}); 

Now that's something beautiful. Combining auto with lambda makes very readable code and opens up a world of possibilities.

The last item to study is the QString API. We used it in updateStatus():

ui->statusLabel->setText( 
        QString("Status: %1 todo / %2 completed") 
                             .arg(todoCount) 
                             .arg(completedCount)); 

The people behind Qt put a lot of work into making string-manipulation bearable in C++. This is a perfect example, where we replace the classic C sprintf with a more modern and robust API. Arguments are position-based only, no need to specify the type (less error-prone), and the arg(...) function accepts all kinds of types.

Note

Take some time to skim through the QString documentation at http://doc.qt.io/qt-5/qstring.html. It shows how much you can do with this class and you'll see yourself using fewer and fewer examples of std string or even cstring.

 

Summary


In this chapter, we created a desktop Qt application from scratch. Qt is well known for its signal/slot mechanism and you must be confident using this paradigm. We also introduced some important C++14 features that will be used throughout this book.

It's now time to discover some qmake secrets and what really happens when you build your Qt project. In the next chapter, we will talk about how to create and organize an application with some platform-dependent code that must run on Windows, macOS, and Linux.

About the Authors

  • Guillaume Lazar

    Guillaume Lazar is a software engineer living in France, near Paris. He has worked in different companies, from start-ups to multinationals, for the last 10 years. He took the opportunity to observe and learn many team organizations and technologies. In 2014, he founded his own software development company at the age of 27. The current hierarchical organization that applies to most companies seems obsolete to him. With his own company, he wants to try a different approach. Although he defines himself as a Qt framework lover, he likes to mix different technologies and platforms. He also spends time on game development, machine learning, and electronics, because "things" become "alive".

    Browse publications by this author
  • Robin Penea

    Robin Penea has been working in the software industry for a more than a decade. He worked in start-ups and large companies with many technologies that ranged from embedded software to web development. Armed with this experience, he wrote the Mastering Qt 5 book to spread what he loves the most about the programming craft: proper design and quality code. The teaching bug has bitten him, and he continues to share what he learned online using videos. When he is not tinkering with some new technology, he is either on a wall, rock-climbing, or playing music on his piano. You can reach him via Twitter @synapticrob.

    Browse publications by this author

Latest Reviews

(9 reviews total)
Tout s'est parfaitement déroulé : choix des livres, commande et réception. Le contenu des livres est conforme à ce que j'en attendais.
Ohne Kenntnisse der Firefox Developer Tools hätte ich den Kauf nie abschließen können. Der Button für den Einkaufswagen bzw. die Kasse wurde von anderen Elementen überdeckt.
They do all they have to.

Recommended For You

Qt5 C++ GUI Programming Cookbook - Second Edition

Use Qt 5 to design and build functional, appealing, and user-friendly graphical user interfaces (GUIs) for your applications.

By Lee Zhi Eng
Hands-On High Performance Programming with Qt 5

Build efficient and fast Qt applications, target performance problems, and discover solutions to refine your code

By Marek Krajewski
Getting Started with Qt 5

Begin writing graphical user interface(GUI) applications for building human machine interfaces with a clear understanding of key concepts of the Qt framework

By Benjamin Baka
Hands-On Mobile and Embedded Development with Qt 5

Explore Qt framework and APIs for building cross-platform applications for mobile devices, embedded systems, and IoT

By Lorn Potter