CopperSpice API  1.7.4
Custom Models

New models can be created by inheriting from QAbstractItemModel or one of its children. The QAbstractItemModel class provides an interface which supports storing and retrieving data. It also provides support for drag and drop operations. This is usually the best class to inherit from if the underlying data structure can only be represented by a hierarchical tree structure.

There are existing child classes of QAbstractItemModel and it may be more reasonable to inherit from one of them instead. The QAbstractListModel should be used if your data is structured as a list. If your data structure is a two dimensional table the using the QAbstractTableModel class would be a better choice.

The requirements for inheriting from QAbstractItemModel classes is described in more detail in the section Inheriting from a Model.

Inheriting from a Model

When inheriting from an abstract class, the new model class may need to provide implementations for some of the virtual methods. The exact number of methods which need to implemented depends on which views the model is required to support.

Models that inherit from QAbstractListModel and QAbstractTableModel can take advantage of the default implementations of methods provided by those abstract classes. Models which store data in a tree-like structures must provide implementations for more of the virtual methods in QAbstractItemModel.

The methods that need to be implemented in a child model class can be divided into three groups.

Item data handling
Every model needs to implement the methods required for views and delegates to query the dimensions (how many rows or columns) of the model and retrieve data.
Navigation and index creation
Hierarchical models need to provide methods which views can call to navigate the tree-like structures they expose and obtain model indexes for a given item.
Drag and drop support and MIME type handling
Any method which controls the way internal and external drag and drop operations are processed. These methods allow items to be described in terms of MIME types which other components and applications can understand.

Model Headers and Data

The data() method is responsible for returning the data which corresponds to the given model index. The following is an example for a custom model which inherits from QStringListModel. We only return a valid QVariant if the model index is valid, the row number is within the range, and the requested role is supported.

QVariant MyStringListModel::data(const QModelIndex &index, int role) const
{
if (! index.isValid()) {
return QVariant();
}
if (index.row() >= m_stringList.size()) {
return QVariant();
}
if (role == Qt::DisplayRole) {
return m_stringList.at(index.row());
} else {
return QVariant();
}
}

Views like QTreeView and QTableView can display headers both vertically and horizontally. If this model is displayed in a view which supports headers it is customary to show the row and column numbers.

QVariant MyStringListModel::headerData(int section, Qt::Orientation orientation, int role) const
{
if (role != Qt::DisplayRole) {
return QVariant();
}
if (orientation == Qt::Horizontal) {
return QString("Column %1").formatArg(section);
} else {
return QString("Row %1").formatArg(section);
}
}

Inserting and Removing Rows

Although both rows and columns can be added to a view, this does not make sense for a string list view which only has one column. The following code implements insertRows() and removeRows() for our custom MyStringListModel class.

The insertRows() method will insert a given number of empty strings into the model before the specified position. The number of strings inserted is equivalent to the number of rows specified by the caller. The parent index can be used to determine where in the model the rows should be added.

The code first calls beginInsertRows() to inform other components that the number of rows is about to change. It passes the row numbers of the first and last new rows to be inserted and the model index for their parent item. After changing the data member for the string list endInsertRows() is called to complete the process.

bool MyStringListModel::insertRows(int position, int rows, const QModelIndex &parent)
{
beginInsertRows(QModelIndex(), position, position + rows-1);
for (int row = 0; row < rows; ++row) {
m_stringList.insert(position, QString());
}
endInsertRows();
return true;
}

The method to remove rows is very similar. The rows to be removed from the model are specified by the position and the number of rows to delete. We ignore the parent index to simplify our implementation and just remove the corresponding items from the string list.

bool MyStringListModel::removeRows(int position, int rows, const QModelIndex &parent)
{
beginRemoveRows(QModelIndex(), position, position+rows-1);
for (int row = 0; row < rows; ++row) {
m_stringList.removeAt(position);
}
endRemoveRows();
return true;
}

Read Only Model

The custom model in this example implements a read only model. The data in the model will be stored in a QStringList and does not need to implement every method. Only rowCount() and data() need to be reimplemented since we are inheriting from QAbstractListModel. This is a better approach than inheriting from QAbstractItemModel which would require implementing five methods.

The rowCount() method returns the number of rows in the current model and data() returns the data corresponding to a given model index. The view calls the data() method to retrieve the data in the current cell. The row information is retrieved from the model index parameter for a role of Qt::DisplayRole.

class MyStringListModel : public QAbstractListModel
{
CS_OBJECT(MyStringListModel)
public:
MyStringListModel(const QStringList &strings, QObject *parent = nullptr)
: QAbstractListModel(parent), m_stringList(strings)
{ }
int rowCount(const QModelIndex &parent = QModelIndex()) const;
QVariant data(const QModelIndex &index, int role) const;
private:
QStringList m_stringList;
};
int MyStringListModel::rowCount(const QModelIndex &) const
{
return m_stringList.size();
}
QVariant MyStringListModel::data(const QModelIndex &index, int role) const
{
if (role == Qt::DisplayRole) {
return m_stringList.at(index.row());
}
return QVariant();
}

Editing Data in a Model

The read only model is not usually sufficient for what a user will require in an application. Editing the data will require making a few changes to the data() method. In addition the flags() and setData() methods will need to be implemented.

The code shown below shows the changes in data() to allow editing. A valid QVariant will be returned if the model index is valid, the item is of the correct enum Type, and the role is supported. The only two roles this custom model supports is Qt::ItemDataRole::DiaplayRole and Qt::ItemDataRole::EditRole.

QVariant MyStringListModel::data(const QModelIndex &index, int role) const
{
if (! index.isValid()) {
return QVariant();
}
if (index.row() >= m_stringList.size()) {
return QVariant();
}
if (role == Qt::DisplayRole || role == Qt::EditRole) {
return m_stringList.at(index.row());
} else {
return QVariant();
}
}

The purpose of the setData() method is simply to update values stored in the model. The process of creating the editor, changing cursors, and any other visual effects are the responsibility of the Delegate.

After the data is modified the model must inform the view. 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.

bool MyStringListModel::setData(const QModelIndex &index, const QVariant &;value, int role)
{
if (index.isValid() && role == Qt::EditRole) {
m_stringList.replace(index.row(), value.toString());
emit dataChanged(index, index);
return true;
}
return false;
}

The Delegate class will check whether an item can be modified before creating an editor. The model must let the delegate know if the current item can be edited. This is accomplished by returning the appropriate flags. In our example we enable all items and make them both selectable and editable.

Qt::ItemFlags MyStringListModel::flags(const QModelIndex &index) const
{
if (! index.isValid()) {
return Qt::ItemIsEnabled;
}
return QAbstractItemModel::flags(index) | Qt::ItemIsEditable;
}