13.2.3 派生新模型类(1)
选择合适的基类:QAbstractItemModel最灵活、复杂。QAbstractListModel and QAbstractTableModel简单,已经实现了一些功能,取决于数据本身的结构。
1.最小模型访问接口
类QAbstractItemModel定义了模型访问接口,模型类的使用者(比如视图类、委托类)使用这个接口访问数据集。类QAbstractItemModel是一个抽象类,模型类的使用者无法直接使用这个类,它们必须使用这个类的派生类来访问数据集。本节介绍如何派生新的模型类。为简单起见,我们令模型类的使用者为视图类。
类QAbstractItemModel定义的这个接口包含多个函数,其中5个函数被定义为该类的纯虚函数,意味着任何派生类必须实现这5个函数,视图类才能和这个派生类协同工作,访问其中的数据集。这5个函数是视图类和模型类之间的最小接口。下面我们分别介绍这5个函数的功能。派生类的实现者应该理解并精确实现每个函数的功能。
当视图类企图访问某个数据项时,会调用下面的函数请求模型类创建一个索引,指向目标数据项:
- QModelIndex index ( int row, int column, const QModelIndex & parent =
- QModelIndex() ) const = 0
其中parent是目标数据项父节点的索引。这个父节点可能含有多行、多列子节点,参数row、column表示目标数据项所在的行号、列号。如果parent为无效索引,则表示目标数据项的父节点为类QAbstractItemModel内部定义的那个不可见根节点。
当视图类企图访问一个数据项的父节点时,会调用下面的函数请求模型类创建一个索引,指向该父节点:
- QModelIndex parent ( const QModelIndex & index ) const = 0
其中index是子节点的索引,该函数的返回值应该是父节点的索引。
当视图类企图访问一个数据项的子节点时,会调用下面的两个函数,向模型类咨询这个父节点含有多少行、多少列子节点:
- int rowCount ( const QModelIndex & parent = QModelIndex() ) const = 0
- int columnCount ( const QModelIndex & parent = QModelIndex() ) const = 0
类似地,当parent为无效索引时,这两个函数返回不可见根节点含有多少行、多少列子节点。
派生类一旦实现了以上4个函数,视图类可以获得数据集中任何一个数据项的索引。具体地说,如果数据集是一个列表,视图类调用rowCount()(令参数parent为无效索引),获得列表中数据项的个数。由于列表的列数被约定为1,所以无需调用columnCount()。视图类再调用index()即可获得每个数据项的索引。如果数据集是一个表格,视图类调用rowCount()以及columnCount()(令参数parent为无效索引),获得表格含有多少行、多少列数据项,再调用index()获得每个数据项的索引。
如果数据集是一棵树,情况会复杂些。虽然一般情况下一棵树只具有一个根,每个父节点只含有1列子节点,但是我们此处考虑最复杂的情况:一棵树可以具有多行、多列的根节点,每个父节点也可以含有多行、多列的子节点。视图类调用rowCount()以及columnCount()(令参数parent为无效索引),获得该树含有多少行、多少列根节点,再调用index()函数(令参数parent为无效索引),获得每个根节点的索引。将其中每个根节点作为父节点,调用rowCount()以及columnCount(),获得每个根节点含有多少行、多少列子节点,再调用index()函数,获得每个子节点的索引。依此类推,可以逐级获得更深层次节点的索引,直到获得所有节点的索引。给定一个节点,如果视图类想获得其父节点的索引,可以直接调用函数parent()。
视图类一旦获得某个数据项的索引,它就可以调用下面的函数读取某个角色对应的数据子项:
- QVariant data ( const QModelIndex & index,
- int role =Qt::DisplayRole ) const = 0
其中index是目标数据项的索引,而role是表示某个角色的枚举常量。正是通过这个函数,派生类将其管理的数据呈现给视图类。
2.实现最小接口的例子--满二叉树
如果数据集是一个列表或者表格,虽然我们可以按照前一节所述,直接从QAbstractItemModel派生新模型类并重载5个纯虚函数,但是,从Qt提供的类QabstractList Model 或者 QAbstractTableModel派生新模型类的效率更高,因为这两个类已经实现了QAbstractItemModel的部分虚函数。13.7节将介绍如何从这两个类派生新的模型类,以处理具有列表或者表格结构的数据集。
本节介绍如何从QAbstractItemModel派生新模型类,以处理具有树状层次结构的数据集。虽然Qt提供的类QStandardItemModel能够存放、访问这样的数据集,但是本节没有使用这个类。通过演示如何从QAbstractItemModel派生新模型类,我们可以深入理解一个模型类应该如何与Model/View框架中的其他类协同工作,这有利于我们理解后续小节将要讨论的其他模型类。
我们以一个满二叉树的例子,来介绍如何从QAbstractItemModel派生新模型类。所谓满二叉树(full binary tree),如图13 10所示,以通俗的语言来描述,是指除了最底层的节点(被称为叶子节点)之外,每个高层节点都具有2个子节点。图中二叉树的每个节点存放着一个整数,每个父节点中的数是它的两个子节点中数的和。
有多种数据结构可被用来存放一棵满二叉树,本例采用如图13-10所示的数组来存放。我们按照从上到下、从左到右的顺序,把各节点中的整数存放在数组中。给定一个数组元素,对其下标做简单的运算,即可求出其父节点、两个子节点的位置。具体地说,设数组下标始于0,如果一个数组元素的下标为i,其父节点的下标为 (i-1)/2(此处"/"表示整数除法,即只保留商),其左子节点的下标为 2 * i +1,右子节点的下标为 2 * i + 2。例如,图中节点"11"、"5"、"6"的数组下标分别为5、11、12,满足上述公式。