Book Image

Mastering Qt 5 - Second Edition

By : Guillaume Lazar, Robin Penea
Book Image

Mastering Qt 5 - Second Edition

By: Guillaume Lazar, Robin Penea

Overview of 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.
Table of Contents (16 chapters)

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.