13.6.1 派生QAbstractProxyModel的子类(1)
QAbstractProxyModel是一个抽象类,程序员不能直接定义该类的对象,而是应该派生该类的子类,实现对其他模型的代理。该类在其内部定义了一个指针,指向被代理的模型。我们将派生的子类称为"代理模型"(proxy model),而把被代理的模型称为"源模型"(source model)。成员函数setSourceModel()设置代理模型中的这个指针,指向一个源模型,而成员函数sourceModel()返回这个指针。
对源模型的代理本质上就是建立代理模型的索引与源模型的索引之间的映射关系。QAbstractProxyModel定义了2个成员函数表达这种映射关系。成员函数mapToSource()将代理模型的索引映射为源模型的索引,而成员函数mapFromSource()将源模型的索引映射为代理模型的索引。这两个函数是纯虚函数,QAbstractProxyModel派生类必须实现它们。
定义QAbstractProxyModel的派生类时,需要完成以下两项任务。
(1)重载纯虚函数mapToSource()以及mapFromSource(),定义代理模型索引与源模型索引之间的映射关系。
(2)实现QAbstractItemModel的最小接口。由于QAbstractProxyModel是QAbstractItemModel的子类,从前者派生出来的代理模型就必须实现所有模型都应该实现的最小接口,即index(),parent(),rowCount(),columnCount()以及data()。
在定义普通模型时,我们十分清楚采用什么样的数据结构来存放数据集,也十分清楚如何定义QModelIndex中的内部指针(或者内部ID)以令其指向某个数据项。然而,在定义代理模型时,由于这个模型中的数据集只是逻辑上存在的,或者说是虚拟的,在定义这个模型对应的索引以及上述成员函数时,情况会变得复杂、抽象。接下来我们以一个具体的例子来说明如何定义代理模型。
我们将要定义的代理模型RevertProxyModel能够对任意一个源模型做如下映射:给定源模型中的任意一个父节点,设该节点有N行子节点,则源模型中第i行子节点被映射为代理模型中该父节点的第N-1-i行子节点。也就是说,代理模型颠倒源模型中子节点的行顺序。例如,设源模型为13.2.3节3.中的一棵满二叉树,如图13 21左侧所示。经代理模型RevertProxyModel映射后的满二叉树如该图右侧所示,每个父节点所拥有的子节点的顺序都被颠倒。
|
| 图13 21 源模型以及代理模型中的满二叉树 |
如图13 22所示,从视图对象的角度来看,代理模型和普通模型没有什么区别。视图对象会调用接口函数index(),parent(),rowCount(),columnCount()以及data()来访问代理模型中的数据集。如13.2.2节所述,在这些函数中,index()函数最重要,它负责为模型中的某个数据项创建一个完整的索引。所谓"完整"是指视图对象仅使用这个索引,就能够访问模型中的目标数据项,不再需要任何其他额外信息。一旦视图对象获得某个目标数据项的索引,它就可以调用其他接口函数轻易地获得目标数据项的其他信息。因此,设计一个代理模型的首要任务是定义它所使用的索引。
代理模型以及源模型都使用类QModelIndex表示索引。给定一个QModelIndex对象,只有当我们知道它是哪个模型对应的索引时,才能够确定它所指的目标数据项。通常情况下,单独的一个QModelIndex对象不能唯一地确定一个目标数据项。
每个QModelIndex对象具有3个主要的数据成员:行号、列号、内部指针(或内部ID)。对于源模型,行号、列号表示目标数据项在其父节点所包含的所有子节点中的位置,而内部指针(或内部ID)指向目标数据项(物理地或逻辑地)。对于代理模型,如图13 22中的虚线框所示,虽然从视图对象的角度来看,代理模型的某个父节点PP(表示Proxy-model Parent)也含有若干个数据项,但是这些数据项只是源模型中对应数据项的映射,代理模型根本不会在物理上存放这些数据项,因而我们将它们称为"虚拟数据项"。源模型中的一个数据项和其对应的虚拟数据项很可能具有不同的行号、列号。例如,设图13 22左下角实线方框中的4行、4列是源模型中父节点SP(表示Source-model Parent)的数据项。经过RevertProxyModel的映射后,这些数据项会被映射为虚线方框中的虚拟数据项。数据项SC(表示Source-model Child)在源模型中的位置是第2行、第1列(行号和列号总是从0开始的)。映射后,对应的虚拟数据项PC(表示Proxy-model Child)位于第1行、第1列。因此,对于代理模型,QModelIndex中的行号、列号表示一个虚拟数据项在代理模型中的行号、列号,而不是源模型中的行号、列号。类似地,在本节中,只要所讨论的内容与模型相关,我们应该明了这些内容是针对代理模型的,还是针对源模型的。
|
| (点击查看大图)图13 22 例子RevertProxyModel中代理模型类对索引的映射过程 |