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:
- Drag and drop
Horizontal layout
inside the central widget and rename ittoolbarLayout
- Right-click on the central widget and select
Lay out vertically
- Drag and drop the label, spacer, and button inside
toolbarLayout
- Drag and drop
Vertical layout
undertoolbarLayout
(a blue helper line will be displayed) and rename ittasksLayout
- 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 thetoolbarLayout
item and thetasksLayout
item. - A vertical spacer pushing these layouts to the top, forcing them to take up the smallest possible space.
- Gotten 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.
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,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 addTask()
slot will now becalledeach 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); };
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.