一、基本概念
1. 模型索引
为了确保数据的表示与数据的获取相分离,Qt引入了模型索引的该你那。
每一块可以通过模型获取的数据都使用一个模型索引来表示,视图和委托都使用这些索引来请求数据并显示。这样,只有模型需要知道怎样获取数据,被模型管理的数据类型可以广泛定义。
模型索引包含一个指针,指向创建它们的模型,当使用多个模型时可以避免混淆。
模型索引由QModelIndex
类提供,它是对一块数据的临时引用,可以用来检索或者修改模型中的数据。因为模型随时可能对内部的结构进行重新组织,这样模型索引可能失效,所以,不需要页不应该存储模型索引。 如果需要对一块数据进行长时间的引用,必须使用QPersistentModelIndex
创建模型索引。
如果要获得一个数据项的模型索引,必须指定模型的3个属性:
- 行号
- 列号
- 父项
2. 行和列
在最基本的形式中,一个模型可以通过把它看作一个简单的表格来访问,这时每个数据项可以用行号和列号来定位。
但这并不意味着在底层的数据块是存储在数组结构中的,使用行号和列号是一种约定,以确保各组件间可以相互通信。
行号和列号都是从0开始的。列表模型和表格模型的索引数据项都是以根项(Root item)为父项,这些数据都可以称作顶层数据项(Top level item),在获取这些数据项的索引时,父项的模型索引可以用QModelIndex()
表示。
例如获取某个项模型索引:
QModelIndex index = model->index(0, 1, QModelIndex());
3. 父项
类似与表格的接口对于在使用表格表格或者列表时是非常理想的,但是,像树视图一样的结构需要模型提供一个灵活的接口,因为每一个数据项都可能成为其他数据项表格的父项,一个树视图中的顶层数据项也可能包含其他的数据项列表。
当为模型项请求一个索引时,必须提供该数据项父项的一些信息。顶层数据项可以使用QModelIndex()
作为父项索引,但是在树模型中,如果一个数据项不是顶层数据项,那么就要指定它的父项索引。
4. 项角色
模型中的数据项可以作为各种角色在其他组件中使用,允许为不同的情况提供不同类型的数据。
例如,Qt::DisplayRole
用来访问可以作为文本显在视图中的字符串。通常情况下,数据项包含了一些不同角色的数据,这些标准的角色由枚举变量Qt::ItemDataRole
来定义,常用的角色如下表所列。
要查看全部的角色类型,可以在帮助中索引Qt::ItemDataRole
关键字。
通过为每个角色提供适当的项目数据,模型可以为视图和委托提供提示,告诉它们数据应该怎样展示给用户。角色指出了从模型中引用哪种类型的数据,视图可以使用不同的方式来显示不同的角色。不同类型的视图也可以自由解析或者忽略这些角色信息。
常量 | 描述 |
---|---|
Qt::DisplayRole | 数据被渲染为文本(数据为QString 类型) |
Qt::DecorationRole | 数据被渲染为图标等装饰(数据为QColor 、QIcon 或者QPixmap 类型) |
Qt::EditRole | 数据可以在编辑器中进行编辑(数据为QString 类型) |
Qt::ToolTipRole | 数据显示在数据项的工具栏提示中(数据为QString 类型) |
Qt::StatusTipRole | 数据显示在状态栏中(数据为QString 类型) |
Qt::WhatsThisRole | 数据显示在数据项的“What’s This?”模式下(数据为QString 类型) |
Qt::SizeHintRole | 数据项的大小提示,将会应用到视图(数据为QSize 类型) |
可以通过向模型指定相关数据项对应的模型索引以及特定的角色来获取需要类型的数据。
代码示例:
#include <QApplication>
#include <QTreeVuew>
#include <QDebug>
#include <QStandardItemModel>
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
//创建标准项模型
QStandardItemModel model;
//获取模型的根项(Root Item),根项是不可见的
QStandardItem * parentItem = model.invisibleRootItem();
//创建标准项item0,并设置显示文本,,图标和工具提示。
QStandardItem* item0 = new QStandardItem;
item0->setText("A");
QPixmap pixmap0(50, 50);
pixmap0.fill("red");
item0->setIcon(QIcon(pixmap0));
item0->setToolTip("indexA");
//将创建的标准项作为根项的子项
parentItem->appendRow(item0);
//将创建的标准项作为新的父项
parentItem = item0;
//创建新的标准项,它将作为item0的子项
QStandardItem *item1 = new QStandardItem;
item1->setText("B");
QPixmap pixmap1(50, 50);
pixmap.fill("blue");
item1->setIcon(QIcon(pixmap1));
item1->setToolTip("indexB");
parentItem->appendRow(item1);
//创建新的标准项,这里使用了另一种方法来设置文本、图标和工具提示
QStandardItem *item2 = new QStandardItem;
QPixmap pixmap2(50, 50);
pixmap2.fill("green");
item2->setData("C", Qt::EditRole);
item2->setData("indexC", Qt::ToolTipRole);
item2->setData(QIcon(pixmap2), Qt::DecorationRole);
parentItem->appendRow(item2);
//在树视图中显示模型
QTreeView view;
view.setModel(&model);
view.show();
//获取item0的索引并输出item0的子项数目,然后输出了item1的显示文本和工具提示
QModelIndex indexA = model.index(0, 0, QModelIndex());
qDebug() << "indexA row count:" << model.rowCount(indexA);
QModelIndex indexB = model.index(0, 0, indexA);
qDebug() << "indexB text:" << model.data(indexB, Qt::EditRole).toString();
qDebug() << "indexB toolTip:" << model.data(indexB, Qt::ToolTipRole).toString();
return app.exec();
}
二、代理示例
1. QFileSystemModel
#include <QApplication>
#include <QFileSystemModel>
#include <QListView>
#include <QTreeView>
int
main (int argc, char *argv[])
{
QApplication app (argc, argv);
QFileSystemModel model;
model.setRootPath (QDir::currentPath());
QTreeView treeView;
treeView.setModel (&model);
treeView.setRootIndex (model.index (QDir::currentPath()));
QListView listView;
listView.setModel (&model);
listView.setRootIndex (model.index (QDir::currentPath()));
treeView.show();
listView.show();
return app.exec();
}
2. QStandardModel
#include <QApplication>
#include <QDebug>
#include <QFileDialog>
#include <QStandardItemModel>
#include <QTreeView>
#include <QIcon>
int
main (int argc, char *argv[])
{
QApplication app (argc, argv);
QStandardItemModel model;
QStandardItem * parentItem = model.invisibleRootItem();
{
QStandardItem *item0 = new QStandardItem;
item0->setText("A");
QPixmap pixmap0(50, 50);
pixmap0.fill("red");
item0->setIcon(QIcon(pixmap0));
item0->setToolTip("indexA");
parentItem->appendRow(item0);
parentItem = item0;
}
{
QStandardItem *item1 = new QStandardItem;
item1->setText("B");
QPixmap pixmap1(50, 50);
pixmap1.fill("blue");
item1->setIcon(QIcon(pixmap1));
item1->setToolTip("indexB");
parentItem->appendRow(item1);
}
{
QStandardItem* item2 = new QStandardItem;
QPixmap pixmap2(50, 50);
pixmap2.fill("green");
item2->setData("C",Qt::EditRole);
item2->setData("indexC",Qt::ToolTipRole);
item2->setData(QIcon(pixmap2),Qt::DecorationRole);
parentItem->appendRow(item2);
}
QTreeView view;
view.setModel(&model);
view.show();
return app.exec();
}
三、自定义StringModel
1. 只读模型
可读模型只需要实现rowCount()
和data()
。
此处还选择性实现了headerData()
,这个模型是非层次结构的。如果是层次结构的模型,还需要实现index()
和parent()
。
StringListModel.h
#pragma once
#include <QAbstractListModel>
#include <QStringList>
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;
};
2. 可编辑模型
可编辑模型需要实现flags()和
setData()`。
同时修改data()
的实现,将第三个if
的判断条件改为:
if(role == Qt::DisplayRole || role == Qt::EditRole
3. 插入和删除行
插入行和删除行需要实现insertRows)_
和removeRows()
。
StringListModel.h
#pragma once
#include <QAbstractListModel>
#include <QStringList>
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;
Qt::ItemFlags flags(const QModelIndex &index) const override;
bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override;
bool insertRows(int position, int rows, const QModelIndex &index = QModelIndex()) override;
bool removeRows(int position, int rows, const QModelIndex &index = QModelIndex()) override;
private:
QStringList stringList;
};
StringListModel.cpp
#include "StringListModel.h"
// 对于视图中的项目,我们想要显示字符串列表中的字符串,
// data()函数就是用来返回对应索引参数的数据项的。
QVariant
StringListModel::data(const QModelIndex &index, int role) const
{
if (!index.isValid())
return {};
if (index.row() > stringList.size())
return {};
if (role == Qt::DisplayRole || role == Qt::EditRole)
return stringList.at(index.row());
else
return {};
}
QVariant
StringListModel::headerData(int section, Qt::Orientation orientation, int role) const
{
if (role != Qt::DisplayRole)
return {};
if (orientation == Qt::Horizontal)
return QString("Column %1").arg(section);
else
return QString("Row %1").arg(section);
}
Qt::ItemFlags
StringListModel::flags(const QModelIndex &index) const
{
if (!index.isValid())
return Qt::ItemIsEnabled;
return QAbstractListModel::flags(index) | Qt::ItemIsEditable;
}
int
StringListModel::rowCount(const QModelIndex &parent) const
{
return stringList.count();
// 因为这个模型是非层次结构的,可以忽略的模型索引对应的父项,
// 所以这里只需要简单地返回字符串列表的字符串个数即可。
// 默认的,继承自QAbstractListModel的模型只包含一列,
// 所以不需要实现columnCount()函数。
}
// 1. 要保证修改的索引有效,项目是正确的类型,而且角色是被支持的。
// 2. 当数据被设置后,模型必须让视图知道有数据已经改变了,这通过发射dataChanged()信号来实现。
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;
}
// 这里只有一个顶层字符串列表,所以只需要向列表中添加空的字符串
bool
StringListModel::insertRows(int position, int rows, const QModelIndex &index)
{
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 &index)
{
beginRemoveRows(QModelIndex(), position, position + rows - 1);
for(int row =0;row < rows ;++row) {
stringList.remove(position);
}
endRemoveRows();
return true;
}
更复杂的模型可以参考Qt自带的Simple Tree Model示例程序。
参考资料:Qt Creator快速入门第2版 (霍亚飞 著)