Model View Part 2

Avoid Pitfalls When Developing a Qt Model-View-Delegate Project

By Christopher Probst and ICS Development Team

In part one of this blog series we explained the concept of model-view design and explained the need for a more advanced process for handling dynamic data. In part two, we illustrate the power of Qt Quick’s model-view-delegate framework with an example application that downloads mp3 files from the web. We point out best practices and common pitfalls while writing a C++ model in Qt Quick. Our example is hosted in this GitLab repository, and it requires Qt 5.12 or later and a C++14 compiler.

We recommend cloning the repository, opening the project in Qt Creator, and following along as we discuss the code.

The project, an mp3 download manager, is intended to show a method for connecting backend business logic to visual QML through a model using MVD. The application, shown in Figure 1, consists of a ListView, where each delegate renders a visual representation of an Mp3Item from the Mp3ListModel C++ class (the model).

Below the main view frame, are rows of buttons for downloading mp3 files, removing mp3 items from the model, changing the local download folder, and adding mp3 URLs to the model. When the Download Checked button is clicked, all of the queued, selected mp3s are downloaded, and the download progress is shown on the right as a Label (percent) and a ProgressBar value.

Figure 1: Screenshot of the Download Manager
Figure 1: Screenshot of the Download Manager

QAbstractItemModel and QAbstractListModel

One important feature from the mp3 Download Manager example rests in the ListView model property binding. We expose an instance of the Mp3ListModel C++ class to the QQmlApplicationEngine in main.cpp by setting the context property.

Mp3ListModel mp3ListModel(&mp3List);
engine.rootContext()->setContextProperty("mp3ListModel", &mp3ListModel);

Now, we are able to bind this instance to the ListView model property in DownloadManager.qml.

ListView {
  anchors.fill: parent
  model: mp3ListModel //property binding to C++ model
  delegate: Mp3Item {
    width: parent.width
  }
}

Our C++ Mp3ListModel subclasses QAbstractListModel, a convenience class that implements part of QAbstractItemModel with the knowledge that the model data is a list rather than a tree structure. Because QAbstractListModel is an abstract base class, it requires its pure virtual methods to have implementations in any instantiated subclass.

The two methods that must be implemented are rowCount(), called to obtain the size of the model data, and data(), called to read an item’s value at a specific model index and role. By overriding roleNames(), the delegate is able to bind to model data via named data roles. To save time, Qt Creator provides a wizard that generates the skeleton of a subclassed QAbstractItemModel, shown in Figure 2. In the Project View side panel, access the context menu of the active project and select Add New...

Figure 2: Qt Creator Item Model Wizard
Figure 2: Qt Creator Item Model Wizard

Although this application is simple enough so as not to require the distinction, we separate the QAbstractItemModel from the logic that encapsulates the download process itself. This is important because if the application grew in complexity and scope, we would want to maintain a distinction between the model and the class that manages the downloads. The Mp3List class is really a wrapper for the core model data, a QList of pointers to Mp3Items, each with an instance of a Download object.

Item Selection

Knowing who should be responsible for handling the selection of an item in a ListView is a common issue encountered while working with model-view in QML. In our example, we expose this value to QML (for reading and writing) so that the state corresponds with the CheckBox Qt Quick Controls 2 Item. However, because the backend needs this selection value in order to determine if it should download the mp3 file when the Download Checked Items button is clicked, the boolean value must be available in C++ as well. The Mp3Item struct must exist to encapsulate both a Download object and the selection boolean value. The selection value shouldn’t exist in the Download class because it isn’t related to an actual download.

Business Logic

The Download class is really our core business logic. It provides a wrapper around the QNetworkReply pointer, for receiving files over http, and around the QFile pointer, for transferring the downloaded mp3 file from memory to disk. This class holds the important values associated with each Mp3Item, such as its progress toward completion. Periodically, QNetworkReply will emit the downloadProgress signal which we connect to a lambda function that emits another downloadProgress signal after storing the current and total bytes to the corresponding member variables.

This downloadProgress signal is connected to yet another lambda function when the download is initiated with a call to Mp3List::downloadCheckedItems(). This lambda emits the dataChanged signal of QAbstractItemModel, the notification for the QML engine to re-render the visual representation of the specific model index in its delegate. Lambda functions provide an extremely useful mechanism for exposing the sender data in its capture list, as shown below with this and i.

for (int i = 0; i < m_mp3Items.size(); ++i) {
    Mp3Item *item = m_mp3Items.at(i);

    if (item->selected && !item->download.finished() && !item->download.error()) {
        connect(&item->download, &Download::downloadProgress,
                [this, i]() { emit dataChanged(i); });

        item->download.startDownload();
    }
}

Summary

The mp3 Download Manager is a great example that showcases the ease and power of Qt Quick’s MVD framework. Expanding on this example, one can imagine a model class interacting with any external data source, such as a CAN bus, GPIO, or a database. Qt Quick’s model-view-delegate framework effortlessly handles cases where model data surpasses the use Qt properties. For a more in-depth review of the topics covered in this blog series, look out for our webinar coming this summer.

References