Home · All Classes · All Functions · Overviews

[Previous: Model Classes] [Contents] [Next: View Classes]

Creating New Models

Introduction

The separation of functionality between the model/view components allows models to be created that can take advantage of existing views. This approach lets us present data from a variety of sources using standard graphical user interface components, such as QListView, QTableView, and QTreeView.

The QAbstractItemModel class provides an interface that is flexible enough to support data sources that arrange information in hierarchical structures, allowing for the possibility that data will be inserted, removed, modified, or sorted in some way. It also provides support for drag and drop operations.

The QAbstractListModel and QAbstractTableModel classes provide support for interfaces to simpler non-hierarchical data structures, and are easier to use as a starting point for simple list and table models.

In this chapter, we create a simple read-only model to explore the basic principles of the model/view architecture. Later in this chapter, we will adapt this simple model so that items can be modified by the user.

For an example of a more complex model, see the Simple Tree Model example.

The requirements of QAbstractItemModel subclasses is described in more detail in the Model Subclassing Reference document.

Designing a Model

When creating a new model for an existing data structure, it is important to consider which type of model should be used to provide an interface onto the data. If the data structure can be represented as a list or table of items, you can subclass QAbstractListModel or QAbstractTableModel since these classes provide suitable default implementations for many functions.

However, if the underlying data structure can only be represented by a hierarchical tree structure, it is necessary to subclass QAbstractItemModel. This approach is taken in the Simple Tree Model example.

In this chapter, we will implement a simple model based on a list of strings, so the QAbstractListModel provides an ideal base class on which to build.

Whatever form the underlying data structure takes, it is usually a good idea to supplement the standard QAbstractItemModel API in specialized models with one that allows more natural access to the underlying data structure. This makes it easier to populate the model with data, yet still enables other general model/view components to interact with it using the standard API. The model described below provides a custom constructor for just this purpose.

A Read-Only Example Model

The model implemented here is a simple, non-hierarchical, read-only data model based on the standard QStringListModel class. It has a QStringList as its internal data source, and implements only what is needed to make a functioning model. To make the implementation easier, we subclass QAbstractListModel because it defines sensible default behavior for list models, and it exposes a simpler interface than the QAbstractItemModel class.

When implementing a model it is important to remember that QAbstractItemModel does not store any data itself, it merely presents an interface that the views use to access the data. For a minimal read-only model it is only necessary to implement a few functions as there are default implementations for most of the interface. The class declaration is as follows:

 class StringListModel : public QAbstractListModel
 {
     Q_OBJECT

 public:
     StringListModel(const QStringList &strings, QObject *parent = 0)
         : QAbstractListModel(parent), stringList(strings) {}

     int rowCount(const QModelIndex &parent = QModelIndex()) const;
     QVariant data(const QModelIndex &index, int role) const;
     QVariant headerData(int section, Qt::Orientation orientation,
                         int role = Qt::DisplayRole) const;

 private:
     QStringList stringList;
 };

Apart from the model's constructor, we only need to implement two functions: rowCount() returns the number of rows in the model and data() returns an item of data corresponding to a specified model index.

Well behaved models also implement headerData() to give tree and table views something to display in their headers.

Note that this is a non-hierarchical model, so we don't have to worry about the parent-child relationships. If our model was hierarchical, we would also have to implement the index() and parent() functions.

The list of strings is stored internally in the stringList private member variable.

Dimensions of The Model

We want the number of rows in the model to be the same as the number of strings in the string list. We implement the rowCount() function with this in mind:

 int StringListModel::rowCount(const QModelIndex &parent) const
 {
     return stringList.count();
 }

Since the model is non-hierarchical, we can safely ignore the model index corresponding to the parent item. By default, models derived from QAbstractListModel only contain one column, so we do not need to reimplement the columnCount() function.

Model Headers and Data

For items in the view, we want to return the strings in the string list. The data() function is responsible for returning the item of data that corresponds to the index argument:

 QVariant StringListModel::data(const QModelIndex &index, int role) const
 {
     if (!index.isValid())
         return QVariant();

     if (index.row() >= stringList.size())
         return QVariant();

     if (role == Qt::DisplayRole)
         return stringList.at(index.row());
     else
         return QVariant();
 }

We only return a valid QVariant if the model index supplied is valid, the row number is within the range of items in the string list, and the requested role is one that we support.

Some views, such as QTreeView and QTableView, are able to display headers along with the item data. If our model is displayed in a view with headers, we want the headers to show the row and column numbers. We can provide information about the headers by subclassing the headerData() function:

 QVariant StringListModel::headerData(int section, Qt::Orientation orientation,
                                      int role) const
 {
     if (role != Qt::DisplayRole)
         return QVariant();

     if (orientation == Qt::Horizontal)
         return QString("Column %1").arg(section);
     else
         return QString("Row %1").arg(section);
 }

Again, we return a valid QVariant only if the role is one that we support. The orientation of the header is also taken into account when deciding the exact data to return.

Not all views display headers with the item data, and those that do may be configured to hide them. Nonetheless, it is recommended that you implement the headerData() function to provide relevant information about the data provided by the model.

An item can have several roles, giving out different data depending on the role specified. The items in our model only have one role, DisplayRole, so we return the data for items irrespective of the role specified. However, we could reuse the data we provide for the DisplayRole in other roles, such as the ToolTipRole that views can use to display information about items in a tooltip.

An Editable Model

The read-only model shows how simple choices could be presented to the user but, for many applications, an editable list model is much more useful. We can modify the read-only model to make the items editable by changing the data() function we implemented for read-only, and by implementing two extra functions: flags() and setData(). The following function declarations are added to the class definition:

     Qt::ItemFlags flags(const QModelIndex &index) const;
     bool setData(const QModelIndex &index, const QVariant &value,
                  int role = Qt::EditRole);

Making the Model Editable

A delegate checks whether an item is editable before creating an editor. The model must let the delegate know that its items are editable. We do this by returning the correct flags for each item in the model; in this case, we enable all items and make them both selectable and editable:

 Qt::ItemFlags StringListModel::flags(const QModelIndex &index) const
 {
     if (!index.isValid())
         return Qt::ItemIsEnabled;

     return QAbstractItemModel::flags(index) | Qt::ItemIsEditable;
 }

Note that we do not have to know how the delegate performs the actual editing process. We only have to provide a way for the delegate to set the data in the model. This is achieved through the setData() function:

 bool StringListModel::setData(const QModelIndex &index,
                               const QVariant &value, int role)
 {
     if (index.isValid() && role == Qt::EditRole) {

         stringList.replace(index.row(), value.toString());
         emit dataChanged(index, index);
         return true;
     }
     return false;
 }

In this model, the item in the string list that corresponds to the model index is replaced by the value provided. However, before we can modify the string list, we must make sure that the index is valid, the item is of the correct type, and that the role is supported. By convention, we insist that the role is the EditRole since this is the role used by the standard item delegate. For boolean values, however, you can use Qt::CheckStateRole and set the Qt::ItemIsUserCheckable flag; a checkbox will then be used for editing the value. The underlying data in this model is the same for all roles, so this detail just makes it easier to integrate the model with standard components.

When the data has been set, the model must let the views know that some data has changed. This is done by emitting the dataChanged() signal. Since only one item of data has changed, the range of items specified in the signal is limited to just one model index.

Also the data() function needs to be changed to add the Qt::EditRole test:

 QVariant StringListModel::data(const QModelIndex &index, int role) const
 {
     if (!index.isValid())
         return QVariant();

     if (index.row() >= stringList.size())
         return QVariant();

     if (role == Qt::DisplayRole || role == Qt::EditRole)
         return stringList.at(index.row());
     else
         return QVariant();
 }

Inserting and Removing Rows

It is possible to change the number of rows and columns in a model. In the string list model it only makes sense to change the number of rows, so we only reimplement the functions for inserting and removing rows. These are declared in the class definition:

     bool insertRows(int position, int rows, const QModelIndex &index = QModelIndex());
     bool removeRows(int position, int rows, const QModelIndex &index = QModelIndex());

Since rows in this model correspond to strings in a list, the insertRows() function inserts a number of empty strings into the string list before the specified position. The number of strings inserted is equivalent to the number of rows specified.

The parent index is normally used to determine where in the model the rows should be added. In this case, we only have a single top-level list of strings, so we just insert empty strings into that list.

 bool StringListModel::insertRows(int position, int rows, const QModelIndex &parent)
 {
     beginInsertRows(QModelIndex(), position, position+rows-1);

     for (int row = 0; row < rows; ++row) {
         stringList.insert(position, "");
     }

     endInsertRows();
     return true;
 }

The model first calls the beginInsertRows() function to inform other components that the number of rows is about to change. The function specifies the row numbers of the first and last new rows to be inserted, and the model index for their parent item. After changing the string list, it calls endInsertRows() to complete the operation and inform other components that the dimensions of the model have changed, returning true to indicate success.

The function to remove rows from the model is also simple to write. The rows to be removed from the model are specified by the position and the number of rows given. We ignore the parent index to simplify our implementation, and just remove the corresponding items from the string list.

 bool StringListModel::removeRows(int position, int rows, const QModelIndex &parent)
 {
     beginRemoveRows(QModelIndex(), position, position+rows-1);

     for (int row = 0; row < rows; ++row) {
         stringList.removeAt(position);
     }

     endRemoveRows();
     return true;
 }

The beginRemoveRows() function is always called before any underlying data is removed, and specifies the first and last rows to be removed. This allows other components to access the data before it becomes unavailable. After the rows have been removed, the model emits endRemoveRows() to finish the operation and let other components know that the dimensions of the model have changed.

Next Steps

We can display the data provided by this model, or any other model, using the QListView class to present the model's items in the form of a vertical list. For the string list model, this view also provides a default editor so that the items can be manipulated. We examine the possibilities made available by the standard view classes in the chapter on View Classes.

The Model Subclassing Reference document discusses the requirements of QAbstractItemModel subclasses in more detail, and provides a guide to the virtual functions that must be implemented to enable various features in different types of models.

[Previous: Model Classes] [Contents] [Next: View Classes]


Copyright © 2010 Nokia Corporation and/or its subsidiary(-ies) Trademarks
Qt 4.6.3