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 it toolbarLayout
- 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 under toolbarLayout (a blue helper line will be displayed) and rename it tasksLayout
- 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.
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 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); };
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.