简述
在构建一个模型类之前,需要先考虑清楚这个模型面对的是一个怎样的数据源,只有清楚数据源的特性,才能知道要利用哪个基类,实现哪些接口。比如,如果数据结构可以被表示为列表或者表格,我们可以通过子类化QAbstractListModel或者QAbstractTableModel来实现。如果底层数据结构是层级结构,我们就需要考虑子类化QAbstractItemModel。
这篇文章介绍的例子是实现一个简单的字符串列表模型,这个模型数据源是字符串列表。考虑到是列表类型,QAbstractListModel是个理想的选择。
一个只读模型
下面的模型基于QAbstractListModel实现。只提供了刚好让这个模型能运作的接口。它用一个QStringList作为内部数据源。
请牢记, QAbstractItemModel只是为视图提供了获取数据的接口,它本身并不会存储数据。
该只读模型的声明如下:
class StringListModel : public QAbstractListModel
{
Q_OBJECT
public:
StringListModel(const QStringList &strings, QObject *parent = nullptr)
: QAbstractListModel(parent), stringList(strings) {}
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
QVariant data(const QModelIndex &index, int role) const override;
QVariant headerData(int section, Qt::Orientation orientation,
int role = Qt::DisplayRole) const override;
private:
QStringList stringList;
};
除了模型的构造函数外,我们只需要实现两个函数:rowCount()和data(),一个用来获取数据条目数量,一个用来获取条目数据。
有些时候也需要实现headerData()来提供头部信息。
下面是每个方法的具体实现:
int StringListModel::rowCount(const QModelIndex &parent) const
{
return stringList.count();
}
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();
}
QVariant StringListModel::headerData(int section, Qt::Orientation orientation, int role) const
{
if (role != Qt::DisplayRole)
return QVariant();
if (orientation == Qt::Horizontal)
return QStringLiteral("Column %1").arg(section);
else
return QStringLiteral("Row %1").arg(section);
}
提示: 上面所实现的接口都是虚函数。视图会自动调用这些接口来展示数据。
让模型可编辑
在Qt之模型-视图编程(代理类)中我们了解到,编辑功能是由代理提供。代理在为项目创造一个编辑器之前会先判断该项目是否可编辑,判断方法是flags(),所以我们要重写该函数使得所有项目都为可编辑状态。重写方法如下:
Qt::ItemFlags StringListModel::flags(const QModelIndex &index) const
{
if (!index.isValid())
return Qt::ItemIsEnabled;
return QAbstractItemModel::flags(index) | Qt::ItemIsEditable;
}
请记住, 我们不需要知道代理执行编辑的具体过程是怎样的,只需要向代理提供一个setData()接口用于将数据设置到数据源中。实现如下:
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, {role});
return true;
}
return false;
}
data()也要做出相应的改变
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();
}
插入和移除行
插入和移除行也是可能的,注意: 这些函数需要自己调用才会执行。
实现如下:
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;
}
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;
}
使用
现在来使用一下上面创建的模型,效果如下:
![](https://i-blog.csdnimg.cn/blog_migrate/954a2288974f201763af0a5b295ea8cf.gif)
源码
QStringList stringList;
stringList << "床前明月光"
<< "疑是地上霜"
<< "举头望明月"
<< "低头思故乡";
StringListModel *stringListModel = new StringListModel(stringList);
QListView *listView = new QListView;
listView->setModel(stringListModel);
QGridLayout *gridLayout = new QGridLayout;
gridLayout->addWidget(listView);
this->setLayout(gridLayout);
引用
[1] Qt助手