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)

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.

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.

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.

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.