QAbstractItemModel类继承自QObject, 该类是Qt所有模型类的基类,用于管理模型/视图结构中的数据。Qt的所有模型都需要子类化该类。注意,该类是抽象类,我们不应该创建该类的对象。
该类的构造函数,原型为__init__(parent: QObject = None)
模型索引是由 QModelIndex 类进行描述的,但该类只有一个默认构造函数,而使用默认构造函数创建的模型索引是无效模型索引,因此要创建一个有效的模型索引,需要使用工厂函数QAbstractItemModel::createIndex()来创建,在重新实现纯虚函数 index()和 parent()时,都有可能会调用该工厂函数来创建模型索引。
下面为相关的函数及其原型(本文使用的是C++代码的书写格式):
virtual QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex ()) const = 0; //纯虚函数
返回由行 row、列 column、父索引 parent 指定的数据项的模型索引,当子类重新实现此函数时,模型索引需调用 createIndex()函数来创建。
virtual QModelIndex parent(const QModelIndex &index) const = 0; //纯虚函数
返回索引 index 的父模型索引,若没有父模型索引,则返回无效的模型索引。重新实现该函数时需要小心调用 QModelIndex 中的成员函数(比如 QModelIndex::parent());因为自定义的模型索引只会调用自定义的实现,因此 QModelIndex::parent()会调用此处重新实现的该函数,从而导致无限递归。重新实现该函数时,通常也使用 createIndex()函数创建模型索引。
virtual int rowCount(const QModelIndex &parent = QModelIndex ()) const = 0; //纯虚函数
virtual int columnCount(const QModelIndex &parent =QModelIndex ()) const = 0; //纯虚函数
以上函数表示,返回父模型索引 parent 下的行/列数,在实现基于表格的模型时,若父模型索引有效,则以上函数都应返回 0。
virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const = 0; //纯虚函数
返回索引 index 所引用的项在给定角色 role 下存储的数据。注意:若没有需要返回的值,应返回无效的 QVariant,而不是返回 0。该函数用于向视图和委托提供项目数据,也就是说视图和委托是显示的该函数返回的值。
virtual bool setData(const QModelIndex &index, const QVariant &value, int role=Qt::EditRole); //虚拟的
设置索引 index 所引用数据项的值为 value,其角色为 role。若设置成功则返回 true,并且应发送 dataChanged()信号,否则返回 false。默认实现为返回 false,虽然此函数不是纯虚函数,但若模型是可编辑模型,则必须重新实现此函数,
QModelIndex createIndex(int row, int column, void *ptr = Q_NULLPTR); //受保护的
QModelIndex createIndex(int row, int column, quintptr id); //受保护的
bool hasIndex(int row, int column, const QModelIndex &parent =QModelIndex ()) const;
若根据 row、 column、 parent 返回的模型索引是有效索引,则返回 true,否则返回 false。
virtual bool hasChildren(const QModelIndex &parent = QModelIndex ())const; //虚拟的
若 parent 拥有任何子女,则返回 true,否则返回 false,注意,若设置标志为Qt::ItemNeverHasChildren,则使用此方法的行为是未定义的。在分层模型中,查找数据项的子项目数量是一项昂贵的操作,因此 rowCount()函数应在确有必要时进行调用,通过首先调用此函数判断数据项是否有子项,然后再决定是否调用 rowCount()函数是一种有效的方法。
要使模型能插入行/列和删除行/列,子类需要重新实现以下虚函数:
下面以 insertRows()虚函数为例,讲解其规则(其余函数,原理类同):
在将新行插入到任何基础数据结构之前,必须调用 beginInsertRows()函数(称其为 begin 函数),该函数会通知其他组件(比如视图或委托)行数将要发生变化,完成插入操作之后,还需要调用 endInsertRows()函数(称其为 end 函数)以通知其他组件,该模型的行数已经更改,若 insertRows()插入成功,则返回 true,否则返回 false。
更改模型结构的另一种方法:
通常使用 begin 和 end 函数就能够达到通知其他组件模型结构变化的目的,但对于结构比较复杂的模型,则这种方法可能会比较低效,比如若有一个有 300 百万行的模型,需要删除所有偶数行,这将有可能使用beginRemoveRows和endRemoveRows达到150万次之多,这显然是低效的。此时可使用以下步骤来更新模型结构
以上步骤可用于更新任何结构的模型。
virtual bool insertColumns(int column,int count,const QModelIndex &parent=QModelIndex ()); //虚拟的
virtual bool insertRows(int row, int count, const QModelIndex &parent = QModelIndex ()); //虚拟的
QAbstractItemModel 类对以上虚函数的默认实现什么也没有做,并返回 false,因此要想模型支持插入操作,需要重新实现以上虚函数。重新实现时需要调用相应的 begin 函数和 end 函数。
以上函数表示在指定的列 column/行 row 之前插入 count 行/列,插入的新行/列将是 parent 模型索引所指数据项的子项,若插入成功则返回 true,否则返回 false。
bool insertRow(int row, const QModelIndex &parent = QModelIndex ());
bool insertColumn(int column, const QModelIndex &parent = QModelIndex ());
以上函数分别调用虚函数 insertRows()和 insertColumns()
void beginInsertColumns(const QModelIndex &parent, int first, int last); //受保护的
void beginInsertRows(const QModelIndex &parent, int first, int last); //受保护的
void endInsertColumns(); //受保护的
void endInsertRows(); //受保护的

virtual bool removeColumns(int column,int count,const QModelIndex &parent=QModelIndex ()); //虚拟的
virtual bool removeRows(int row, int count, const QModelIndex &parent = QModelIndex ()); //虚拟的
bool removeRow(int row, const QModelIndex &parent = QModelIndex ());
bool removeColumn(int column, const QModelIndex &parent = QModelIndex ());
以上函数分别调用虚函数 removeRows()和 removeColumns()
void beginRemoveColumns(const QModelIndex &parent, int first, int last); //受保护的
void beginRemoveRows(const QModelIndex &parent, int first, int last); //受保护的
void endRemoveColumns(); //受保护的
void endRemoveRows(); //受保护的

virtual bool moveColumns(const QModelIndex &sourceParent, int sourceColumn, int count,const QModelIndex &destinationParent, int destinationChild); //虚拟的
virtual bool moveRows(const QModelIndex &sourceParent, int sourceRow, int count,const QModelIndex &destinationParent, int destinationChild); //虚拟的
bool moveRow(const QModelIndex &sourceParent, int sourceRow,const QModelIndex &destinationParent, int destinationChild);
bool moveColumn(const QModelIndex &sourceParent, int sourceColumn,const QModelIndex &destinationParent, int destinationChild);
以上函数分别调用虚函数 moveRows()和 moveColumns()
bool beginMoveColumns(const QModelIndex &sourceParent, int sourceFirst, int sourceLast,const QModelIndex &destinationParent, int destinationChild); //受保护的
bool beginMoveRows(const QModelIndex &sourceParent, int sourceFirst, int sourceLast,const QModelIndex &destinationParent, int destinationChild); //受保护的
void endMoveColumns(); //受保护的
void endMoveRows(); //受保护的


相关函数如下:
virtual QVariant headerData(int section, Qt::Orientation orientation,int role =Qt::DisplayRole) const; //虚拟的
virtual bool setHeaderData(int section, Qt::Orientation orientation, const QVariant &value, int role = Qt::EditRole); //虚拟的
以上函数用于获取和设置位于方向 orientation 和位置 section 的标头数据,其数据角色为 role(见下图)。重新实现 setHeaderData 函数时,必须明确地发送 headerDataChanged()信号。

相关函数如下:
使用 setItemDate()函数设置数据项(效果见图中 EEE)简明示例:
QFont f;
f.setPixelSize(22);
QMap mp; //创建 QMap
mp.insert(0,"EEE"); //设置 Qt::DisplayRole 角色的数据
mp.insert(1,QIcon("F:/1i.png")); //设置 Qt::DecorationRole 角色的数据
mp.insert(3,"222"); //设置角色 Qt::ToolTipRole 的数据
mp.insert(6,f); //设置角色 Qt::Font 的数据
//以下数据项由以上 4 个数据元素组成。
pmodel->setItemData(pmodel->index(0,1,QModelIndex()),mp);

自定义模型至少需要实现以下虚函数:
为了能添加自已的数据到模型中,通常还需要重新实现 setData()函数,而不重新实现setData()则无法向模型中添加数据。
数据:实际数据可使用 QList、数组、整型、或单独的一个类来保存,数据可存放在模型中,也可存放在文件等其他地方。
columnCout()、 rowCount()、 index()、 parent()这 4 个函数用于共同设计模型的结构,因为使用索引表示模型中的某个数据项的位置,因此设计模型索引的结构就是设计模型的结构
行数和列数的设计:比如对于 3 行 4 列的表格结构 columnCout()应返回 4,rowCount()应返回 3;对于列表结构,则因为列表只需要 1 列,所以 columncout()应总是返回 1, rowCount()返回该列表的行数;对于树形结构模型,则更复杂,需要根据当前父节点的情况进行判断,以返回该父节点拥有的列数和行数。
parent()函数(父模型索引)的设计:因为表格结构中的所有单元格都属于同一个父索引之下,所以可把所有单元格都视为顶级节点,因此他们的父索引可以以无效模型索引作为父索引,因此 parent()可以返回一个无效模型索引;对于列表结构的模型,同样只需返回一个无效模型索引即可;对树形结构模型,此步骤比较复杂,可以通过获取当前节点的父节点及其行号和列号,然后使用 createIndex()创建该父节点的索引。
index()函数的设计:该函数用于为模型中的每个数据项创建索引,创建索引需要使用 createIndex()函数,对于表格结构,只需向 createIndex()函数传递当前数据项所在的行号、列号及使用的数据的指针即可;对于列表结构,则列号始终为 0,其余同表格结构;对于树形结构,需要向该函数传递当前数据项位于父索引中的行号、列号及使用的数据的指针。
data()函数的返回值决定了视图上应显示的数据,也就是说在界面上用户看到的数据是由该函数返回的, 若返回不当的值,则数据无法正常显示在视图上,下面以使用内置的标准视图类为例来讲解怎样设计此函数。 data()函数会被视图类调用多次, 视图每次都会向 data 传递一个不同的 role(角色)参数值,然后视图根据 data 返回的值,设置该 role 的数据, 因此在设计 data 函数的返回值时,需要根据 role 的不同值返回不同的数据,以使视图正确的显示。
小手一抖,点个赞再走哦~~~