13.6 代理模型(Proxy Models)
有些情况下,应用程序需要对模型中的数据集做一些处理之后,再交给视图对象显示。例如,一位人事部经理在使用一个电子邮件处理程序时,需要在过去几年接收的成千上万封求职信中,寻找一份曾经被拒、但是目前急需的人才的求职信。由于他面对的求职者太多,所以只是记得这封求职信的接收时间大约是2010年秋季。对发送者姓名、邮件主题也只有些模糊的记忆。这位用户想让这个邮件处理程序筛选出2010年9月到2010年12月期间的邮件,显示在两个视图中。在其中一个视图,用户依据发送者姓名进行排序,以快速过滤掉一些发送者(比如已在公司就职的员工)。在另外一个视图,用户可以输入其他筛选条件,筛选主题中含有某个单词的邮件。
筛选2010年9~12月期间邮件的这个操作由模型或者视图对象来完成都不合适。如果由模型来完成,这个筛选操作会影响所有与这个模型关联的视图对象,使得所有视图只能看到上述期间的邮件,无法看到其他时间段的邮件。如果由视图对象来完成,每个视图对象都需要执行这个操作,耗费时间,操作结果无法被其他视图对象共享。另外,在Model/View框架中,视图对象的主要职责是显示而非处理数据。
由于以上原因,Qt使用一个单独的代理模型(proxy model)负责对源模型(source model)中的数据项进行处理,再将处理结果呈现给视图对象。代理模型具有双重职责:首先,它重载了QAbstractItemModel定义的模型接口,因而其他视图对象可将其看作一个普通模型;另外,它在内部定义了一个指针,指向一个源模型,以对该模型中的数据项进行处理。代理模型并不直接处理源模型中的数据项(本节称它们为"源数据项"),而是将它们的索引映射为代理模型的索引,得到一些"虚拟数据项"。和这个代理模型关联的其他视图对象显示的正是这些虚拟数据项。所谓"虚拟",是指代理模型并不实际存储这些数据项,而是通过虚拟数据项索引与源数据项索引之间的映射关系,逻辑上拥有一些数据项。采用这种方式,可以避免重复存放源模型的数据集,节省空间,也会提高程序运行速度。
代理模型向外界呈现一个处理过的数据集,但是并不真地更改源模型中的数据集。其他视图对象仍然可以直接访问源模型中的原始数据集。另外,当被看作一个普通模型时,代理模型可以和多个视图对象关联,令这些视图对象共享代理模型中的虚拟数据集,而不用每个视图对象都要对数据处理一次。
Qt的Model/View框架定义了抽象基类QAbstractProxyModel来表示代理模型。直接派生该类的子类,我们可以对源数据项实现任何类型的处理。这种方式虽然足够灵活,但需要我们处理许多实现细节,13.6.1节详细阐述了这种方式。如果我们只需做排序或者筛选操作,可以使用Model/View框架定义的类QSortFilterProxyModel,该类能够将源模型某一列的数据作为关键字,对源模型的所有行进行排序。该类还允许程序员设定一个正则表达式,该类可以将这个表达式施加到源模型的某一列,对源模型的所有行进行筛选操作,13.6.2节给出了一个具体例子。