Home | All Classes | Main Classes | Annotated | Grouped Classes | Functions

Creating New Models

Introduction

The separation of functionality between the components in the model/view architecture enables new models to be written that can take advantage of existing views. This approach lets us manage data from a variety of sources, and present it using a standard graphical user interface. The interface provided by the QAbstractItemModel class supports models that arrange their data in hierarchical structures, allowing for the possibility that data will be inserted, removed, changed, or sorted in some way. It also provides support for drag and drop operations.

In this section, we will create a simple read-only model to explore the basic principles of the model/view architecture. This model will later be adapted so that items can be modified with an editor.

A read-only example model

The model implemented here is a simple, non-hierarchical, read-only data model. 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;
        QVariant data(const QModelIndex &index, int role) const;

    private:
        QStringList stringList;
    };

Apart from the constructor of the model, we need to implement only two functions: rowCount() returns the number of rows in the model, and data() returns data corresponding to model item indices.

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.

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
    {
        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 data

The data() function is responsible for returning the data that corresponds to the index argument. There are three types of indices: View, HorizontalHeader, and VerticalHeader. If our model is displayed in a view with headers, we want the headers to show the row and column numbers. For View items, we want to return the strings in our string list:

    QVariant StringListModel::data(const QModelIndex &index, int /* role */) const
    {
        if (!index.isValid())
            return QVariant();
        if (index.type() == QModelIndex::HorizontalHeader)
            return index.column();
        if (index.type() == QModelIndex::VerticalHeader)
            return index.row();
        return stringList.at(index.row());
    }

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 will 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 implementing two extra functions: isEditable() and setData(). These are added to the class declaration:

        bool isEditable(const QModelIndex &index) const;
        bool setData(const QModelIndex &index, int role, const QVariant &value);

Making the model editable

A delegate will check whether an item is editable before creating an editor. The model has to let the delegate know that its items are editable:

    bool StringListModel::isEditable(const QModelIndex &/*index*/) const
    {
        return true; // all items in the model are editable
    }

Note that we do not have to know how the delegate will perform 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, int role,
                                  const QVariant &value)
    {
        if (index.isValid() && index.type() == QModelIndex::View &&
            role == 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 specified is replaced by the data provided by the delegate. However, before we actally set the data in the model, we make sure that the index is valid, the item is of the correct type, and that the role is supported. We insist that the role must be the EditRole since this indicates to us that the item is being modified by an editing operation. The underlying data in this model is the same for all roles, so this detail just makes it easier to integrate the model with other standard components.

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

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 will reimplement the functions for inserting and removing rows. These are declared in the class definition:

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

We implement a function to allow rows to be inserted into the model:

    bool StringListModel::insertRows(int position, const QModelIndex &/*index*/,
                                     int rows)
    {
        for (int row = 0; row < rows; ++row) {
            stringList.insert(position, "");
        }

        emit rowsInserted(QModelIndex(), position, position+rows-1);
        return true;
    }

Since rows in this model correspond to strings in a list, this function inserts a number of empty strings into the list before the given position. The number of strings is equivalent to the number of rows specified. The parent index is normally used to determine which table of items in the model is being referred to. In this case, we only have a single top-level list of string, so we just insert empty strings into that list. We could check the parent index to see whether it corresponds to an item in the list, and return false to indicate failure.

The model emits the rowsInserted() signal to inform other components that the number of rows has changed, specifying the first and last rows that were inserted. Note that the signal is always emitted after the rows are inserted, allowing other components to immediately access the new rows.

The function to remove rows from the model is also simple to write:

    bool StringListModel::removeRows(int position, const QModelIndex &/*index*/,
                                     int rows)
    {
        emit rowsRemoved(QModelIndex(), position, position+rows-1);

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

        return true;
    }

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. The rowsRemoved() signal is always emitted before the rows are removed, specifying the first and last rows to be removed. This is to allow other components to access the data before it is removed.

Next steps

We can display the contents of this 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 introduction to the view classes.


Copyright © 2004 Trolltech. Trademarks
Qt 4.0.0-tp1