If you know C++ but have never touched Qt, or if you have made some intermediate Qt applications, this chapter will ensure that your Qt foundations are safe before studying advanced concepts in the following chapters.
We will teach you 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 one of these concepts will be explained in depth and will be used throughout the book.
At 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
Qt Designer interface
UI fundamentals
Signals and slots
Custom
QWidget
C++14 lambda, auto, for each
The first thing to do is to start Qt Creator.
In Qt Creator, you can 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:
Location: You must choose a project name and a location.
Kits: Target platforms that your project aims at (Desktop, Android, and so on).
Details: Base class information and name for the generated class.
Summary: Allows you to configure your new project as a subproject and automatically add it to a version control system.
Even if all 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 that 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 files to generate 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 (
core
,gui
, and so on)Target name (
todo
,todo.exe
, and so on)Project template (
app
,lib
, and so on)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 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 headers/sources for the MainWindow
class. These files contain the default GUI generated by the wizard.
The MainWindow.ui
file is your UI design file in XML format. It can be edited more easily with Qt Designer. This tool is a WYSIWYG (What You See Is What You Get) editor that helps you to add and adjust your graphical components (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(); }
As usual, the main.cpp
file contains the program entry point. It will, by default, perform two actions:
Instantiate and show your main window
Instantiate a
QApplication
and 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:
Check that the project is in Debug build mode.
Use the hammer button to build your project.
Start debugging using the green Play button with a little blue bug.
You will discover a wonderful and beautifully empty window. We will rectify this after explaining how this MainWindow
is constructed:

An empty MainWindow screenshot
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 MainWindow.h
/ MainWindow.cpp
is the C++ object where you can manipulate the UI with code.
It is important to take a look at the header file MainWindow.h
. 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, on top of the header file, we add the corresponding include. The second part is the forward declaration of the Ui::MainWindow
, as we only declare a pointer.
The Q_OBJECT
can look a little strange to a non-Qt developer. This macro allows the class to define its own signals/slots and more globally Qt's meta-object system. These features will be covered later in this chapter.
This class defines a public constructor and destructor. The latter is pretty common. But the constructor takes a parameter parent. This parameter is a QWidget
pointer that is null
by default.
A 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, 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, which in turn will delete its children and so on.
Our MainWindow
class extends QMainWindow
from the Qt framework. We have a ui
member variable in the private fields. The 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 UI design file MainWindow.ui
. The ui
member variable will allow you to interact with your UI components (QLabel
, QPushButton
, and so on) from C++, as shown in the following figure:

Tip
C++ tip
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.
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 the include required by the generated class Ui::MainWindow
. 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 a good piece of code using the initializer list. The parent
argument is used to call the superclass constructor QMainWindow
. Our private member variable ui
is also initialized now.
Now that ui
is initialized, we must call the setupUi
function to initialize all widgets used by the MainWindow.ui
design file:
As we initialize a pointer in the constructor, it must be cleaned in the destructor:
MainWindow::~MainWindow() { delete ui; }
Qt Designer is a major tool for developing Qt applications. This WYSIWYG editor will help you 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: This is a visual representation of the form (empty for now)
Widget Box: This contains all widgets that can be used with your form
Object Inspector: This displays your form as a hierarchical tree
Property Editor: This enumerates the properties of the selected widget
Action Editor/Signal & Slots Editor: This handles 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 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. OK, 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 this:

The Qt framework brings a flexible message exchange mechanism through three concepts: signals, slots, and connections:
A
signal
is a message sent by an objectA
slot
is a function that will be called when this signal is triggeredThe
connect
function specifies whichsignal
is linked to whichslot
Qt already provides signals and slots for its classes, which you can use in your application. For example, QPushButton
has a signal clicked()
, which will be triggered when the user clicks on the button. 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
: This is the object that will send the signal. In our example, it is theQPushButton
namedaddTaskButton
added from the UI designer.&Sender::signalName
: This is the pointer to the member signal function. Here, we want do something when the clicked signal is triggered.receiver
: This is the object that will receive and handle the signal. In our case, it is theQApplication
object created inmain.cpp
.&Receiver::slotName
: This is a pointer to one of the receiver'smember
slot functions. In this example, we use the built-inquit()
slot fromQapplication
, which will exit the application.
You can now compile and run this short example. You will terminate the application if you click on the addTaskButton
of your MainWindow
.
Tip
Qt tip
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
.
This 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 slot
keyword to identify slots. Since a slot is a function, you can always adjust the visibility (public
, protected
or private
) depending on your needs.
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 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 this:
#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 out 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 Qt Creator Application Output
tab.
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:
Create a
Task.h
file and aTask.cpp
file.Create the associated
Task.ui
and do the plumbing to connect it toTask.h
.Add these three new files to
todo.pro
so they can be compiled.
Finish and, voilà , the Task
class is ready to be filled. We will jump into the Task.ui
first. Start by dragging and dropping a Check Box
(put checkbox
in the objectName
) and a Push Button
(objectName
= removeButton
):

My alignment looks great, let's ship this 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
: In this layout, widgets are vertically stackedHorizontal Layout
: In this layout, widgets are horizontally stackedGrid Layout
: In this layout, widgets are arranged in a grid that can be subdivided into smaller cellsForm Layout
: In this layout, widgets are arranged like a web form, a label, and an input
Every layout will try to constrain all widgets to occupy equal surfaces. It will either change the widgets' shape or add extra margins, depending on each widget's constraints. A Check Box
will not be stretched but a 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)
line 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 the 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 now take up only the space of their text and will be pushed to the edge of the window when it is 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 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 this will be done in the form part. We also added some getters and setters that will interact with the form. It is better to have a model completely separated from the UI, but our example is simple enough to merge them. Moreover, 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 our ui->checkbox
and the name()
and isCompleted()
getters take their data from the ui->checkbox
.
We will now rearrange the layout of MainWindow
to be able to display our todo tasks. Right now, there is no widget where we can display our tasks. Open the MainWindow.ui
file and edit it to get the following result:

If we detail the content, we have:
A vertical layout for
centralWidget
containing thetoolbarLayout
file and thetasksLayout
file.A vertical spacer pushing these layouts to the top, forcing them to take up the smallest possible space.
We got rid of
menuBar
,mainToolBar
, andstatusBar
. Qt Creator created them automatically, we simply don't need them for our purposes. You can guess their uses from their names.
Do not forget to rename the MainWindow
title to Todo
by selecting the MainWindow in the Object Inspector window and editing the Qwidget | windowTitle property. Your app deserves to be named properly.
Now that the MainWindow
UI is ready to welcome tasks, let's switch to the code part. The application has to keep track of the 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 the std::vector
. As a general rule, STL containers are more customizable but might 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 might stumble upon the following statement: "For most purposes, QList
is 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 slot addTask()
will now be called each time we want to add a new Task
object to the mTasks
function.
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); };
Tip
C++ tip
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. 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 Task is a QWidget
, we also added it directly to the tasksLayout
. An important thing to note here is that we never managed this new task's memory. Where is the delete task
instruction? This is a key feature of the Qt Framework we started to broach earlier in the chapter; the QObject
class parenting automatically handles object destruction.
In our case, the ui->tasksLayout->addWidget(task)
call has an interesting side-effect; the ownership of the task is transferred to tasksLayout
. The QObject*
parent defined in Task
constructor is now tasksLayout
, and the Task
destructor will be called when tasksLayout
releases its own memory by recursively iterating through its children and calling their destructor.
This will happen at this precise moment:
MainWindow::~MainWindow() { delete ui; }
When MainWindow
is released (remember, it's a stack variable allocated in the main.cpp
file), it will call delete ui
, which in turn will bring down the whole QObject
hierarchy. 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 that in later chapters.
We deserve something better than an untitled task. The user needs to define its name when it's 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'll 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 theQinputDialog
is attached. This is another instance of theQObject
class's parenting model.title
: This is the title displayed in the window title. In our example, we usetr("Add task")
, which is how Qt handles i18n in your code. We will see later on 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 to false if the user presses Cancel.QString
: The returnedQString
is what the user has typed.
There are a few more optional parameters we can safely ignore for our example.
Great, the user can now specify the task name when it's created. What if he makes an error when typing the name? The next logical step is to rename the task after we created 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 slot rename()
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
.
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 (MainWindow
) that the removeTaskButton
QPushButton
has been clicked. We'll implement this by defining a custom signal removed
in 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 a signal
is used only to notify another class, the public
keyword is not needed (it even raises a compilation error). A 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, the lambda
is the following part:
[this] { emit removed(this); });
What we did here is to connect 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 immediately trigger the connected slot
with what we passed in a 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
: This defines what variables will be visible inside thelambda
scope.params
: This is the function parameters type list that can be passed to thelambda
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 thelambda
function. Just likeparams
, this parameter can be omitted if the return type isvoid
.body
: This is obviously your code body where you have access to yourcapture-list
, andparams
, and which must return a variable with a typeret
.
In our example, we captured the this
pointer to be able to:
Have a reference on the
removed()
function, which is a part of theTask
class. If we did not capturethis
, the compiler would have shoutederror: 'this' was not captured for this lambda function emit removed(this);
.Pass
this
to theremoved
signal; the caller needs to know which task triggeredremoved
.
The capture-list
relies on standard C++ semantics: capture variables by copy or by reference. Let us say that we wanted to print a log of the constructor parameter name
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 -
and is -
invalid once we get out of Task
constructor (more precisely, from the caller scope). The qDebug()
function will then try to display an unreachable code and crash.
You really want 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 the name
by copy:
connect(ui->removeButton, &QPushButton::clicked, [this, name] { qDebug() << "Trying to remove" << name; this->emit removed(this); });
Tip
C++ Tip
You can capture by copy or reference all variables that are reachable in the function where you define your lambda with the syntax [
=
] and [&
].The
this
variable is a special case of the capture list. You cannot capture it by reference[&this]
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 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 something like a "functional zealot":
connect(ui->removeButton, &QPushButton::clicked, [this, name] { qDebug() << "Trying to remove" << [] (const QString& taskName) -> QString { return "-------- " + taskName.toUpper(); }(name); this->emit removed(this); });
Here we did a tricky thing. We called qDebug()
; inside this call we defined a lambda
which is immediately executed. Let's analyze it:
[]
: We performed no capture. Thelambda
does not depend on the enclosing function.(const Qstring& taskName)
: When this lambda is called, it will expect aQString
to work on.-> QString
: The returned value of the lambda will be aQString
.return "------- " + taskName.toUpper()
: the body of ourlambda
. We return a concatenation of a string and the uppercase version of the parametertaskName
. As you can see, string manipulation becomes a lot easier with Qt.(name)
: Here comes the catch. Now that thelambda
function is defined, we can call it passing thename
parameter. In a single instruction, we define it and call it. TheQDebug()
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'll see soon how we can do it with C++11. For now, let's finish our remove feature.
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); task->setParent(0); delete task; }
The 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
. Here, tasksLayout
releases its ownership of task
(that is, tasksLayout
ceases to be the task
class's parent).
So far so good. The next two lines are interesting. The ownership transfer does not completely release the task
class ownership. If we commented these lines, here is how removeTask()
will look:
void MainWindow::removeTask(Task* task) { mTasks.removeOne(task); ui->tasksLayout->removeWidget(task); // task->setParent(0); // delete task; }
If you add a log message in Task
destructor and execute the program, this log message will be displayed. Nonetheless, the Qt documentation tells us in Qlayout::removeWidget
part: The ownership of a widget remains the same as when it was added.
Instead, what really happens is that the task
class's parent becomes centralWidget
, the tasksLayout
class's parent. We want Qt to forget everything about task
, that's why we call task->setParent(0)
. We can then safely delete it and call it a day.
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 slot checked(bool checked)
that will be connected to the checkbox::toggled
signal. In our slot checked()
, we strike out the checkbox
text according to the bool checked
value. This is done using the QFont
class. We create a copy font from the checkbox->font()
, modify it, and assign it back to ui->checkbox
. If the original font
was in bold, with a special size, its appearance would be guaranteed to stay the same.
Tip
Play around with the font object in Qt Designer. Select the 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
, to hide the implementation details of the task. 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 taskStatusChanged
, which is connected when a task is created. The single instruction of this slot
is to call updateStatus()
. This function iterates through the tasks and updates the 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:
std::vector::const_iterator iterator = mTasks.toStdVector() .stdTasks.begin(); // how many neurones did you save? auto autoIter = stdTasks.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.
Tip
The auto
keyword can be combined with const
and references. You can write a for loop like 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.
Tip
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
.
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 with this paradigm. We also introduced some important C++14 features that will be used all through this book.
It's now time to discover some qmake
secrets and what is really done when you build your Qt project. In the next chapter, we will also talk about how to create and organize an application with some platform-dependent code that must run on Windows, Mac OS, and Linux.