原文:http://book.2cto.com/201209/4214.html
Model/View框架中,所有模型类具有共同的抽象基类QAbstractItemModel,所有视图类具有共同的抽象基类QAbstractItemView,所有委托类具有共同的抽象基类QabstractItem Delegate。这些类之间的协作关系如图13-4所示。
Model/View框架涉及的类如图13-5所示。初看起来这个框架非常复杂,但是由于所有的模型类、视图类、委托类都遵循上述协作关系,我们其实只需关注QAbstractItemModel的各个子类的区别、QAbstractItemView的各个子类的区别以及QAbstractItemDelegate的各个子类的区别。
(1)模型类。作为所有模型类的抽象基类,QAbstractItemModel定义了模型访问接口。这个接口是一组纯虚函数,负责访问数据集中的数据项。该类将数据集看作一棵树,但是该类并没有定义任何数据成员来存放数据集。类似地,除了具有灰色背景的类Qstandard ItemModel外,其他模型类也都如此。由于QAbstractItemModel是一个抽象基类,我们并不能直接定义该类的对象。使用模型类的一个简单做法是了解QAbstractItemModel各个派生类的功能,寻找一个与应用程序数据集特征最接近的派生类,定义该类的对象或者派生该类的子类。如果QAbstractItemModel的所有派生类都无法满足我们的要求(比如性能方面的),我们才需要直接派生QAbstractItemModel的子类,但这需要实现模型访问接口中的几个纯虚函数,编程工作量较大。
QAbstractListModel负责处理具有列表结构的数据集。它已经实现了部分模型访问接口,我们只需派生该类的子类,实现模型访问接口中的其他虚函数,因而比直接派生QAbstractItemModel的子类要简单。如果应用程序只是企图显示、编辑一些字符串,可以使用QAbstractListModel的子类QStringListModel。程序员所需做的只是将这些字符串写入一个QStringList对象中,然后将这个对象传递给一个QStringListModel对象。后者实现了整个模型访问接口,因而程序员不需要重载它的任何成员函数。
QAbstractTableModel负责处理具有表格结构的数据集。它已经实现了部分模型访问接口,我们只需派生该类的子类,实现模型访问接口中的其他虚函数。它的派生类QsqlQueryModel,QsqlTableModel,QSqlRelationalTableModel和关系型数据库相关,限于篇幅,本书不予讨论。
QStandardItemModel负责处理具有树状层次结构的数据集。当然,由于列表、表格也可以被看作特殊的树,该类实际上也可以处理具有列表、表格结构的数据集。和前面几个模型类不同,该类在其内部定义了一个数据结构,用来存放树状结构的数据集。它使用类QStandardItem表示一个树节点,QStandardItem定义了一些数据成员,用来存放数据集的数据。另外,它还定义了一些指针,每个指针指向另外一个QStandardItem对象,形成当前节点的子节点。通过这种方式,QStandardItemModel可以存储具有任意多层的树状数据集。
QFileSystemModel专门负责处理本地文件系统。它实现了整个模型访问接口,其他视图类通过该类可以立即显示本地文件系统。
和前面的模型类都不同,QAbstractProxyModel并不直接处理数据集,它对其他模型中的数据项进行一些处理,再将处理结果呈现给框架中的其他对象。该类的子类QsortFilterProxy Model能够做排序、筛选处理。
(2)视图类。作为所有视图类的抽象基类,QAbstractItemView负责绘制总体外观并处理用户的交互命令。由于它是一个抽象类,我们不能直接定义该类的对象,而是应该在该类的派生类中,选择一个和数据集拓扑结构相似的类,定义其对象来显示数据集。QListView适合显示具有列表结构的数据集,QTableView适合显示表格结构的数据集,QTreeView以及QColumnView适合显示树状结构的数据集。QHeaderView仅被用来显示标头部分,与数据项无关。
以上几个视图类实现普通视图的功能,在它们的内部没有定义任何模型对象。使用这些视图类时,程序员必须自行构造模型对象。为了方便对Model/View框架的使用,Qt还提供了一组便利视图类。这些类在其内部定义了模型对象,并定义了一组简洁、易用的成员函数来操作模型对象中的数据项。其中,QListWidget存储并显示一个列表,QTableWidget存储并显示一个表格,QTreeWidget存储并显示一棵树。在图中这些类的背景被显示为灰色,表示它们包含模型对象。这些视图类分别使用类QlistWidgetItem,QTableWidgetItem以及QTreeWidgetItem表示模型对象中的数据项。
对于QListWidget表示的列表或者QTableWidget表示的表格,依据其行数、列数即可遍历整个数据集。对于QTreeWidget表示的树状数据集,遍历所有树节点并非易事。为此,Model/View框架提供了迭代器QTreeWidgetItemIterator,对QTreeWidget表示的树进行前序遍历。
3)委托类。抽象基类QAbstractItemDelegate及其子类负责视图中每个数据项的绘制与编辑。视图类的内部定义了一个指针,指向一个委托对象。当视图需要绘制每个数据项时,会将该数据项的屏幕位置等信息传递给委托对象进行绘制。
QAbstractItemDelegate的子类QItemDelegate总是以一种默认的风格绘制数据项,而它的另外一个子类QStyledItemDelegate使用应用程序当前的界面风格进行绘制,使得整个界面具有协调一致的风格,因而我们应该尽可能地使用后者。当用户需要编辑某个数据项时,委托对象会将目标数据项的类型传递给类QItemEditorFactory,后者负责创建一个能够编辑该类型的编辑器控件,对目标数据项进行编辑。编辑完毕后,委托对象调用模型访问接口,将编辑结果写回模型。
(4)索引。在视图类或者委托类看来,模型类中的数据集总是一棵树,该树可能具有多层树节点。由于模型的访问者对数据集的存放方式一无所知,为了表示要访问哪个节点,访问者会通知模型类目标节点的父节点,以及在这个父节点所包含的所有子节点中,目标节点所在的行号、列号。模型类收到这些信息后,在数据集中找到目标节点,创建一个QModelIndex对象,逻辑上指向这个节点,这个QModelIndex对象就被称为目标节点的索引。模型的访问者此后就可以使用这个索引来访问目标节点,提高了访问效率。另外,QModelIndex内部还定义了一个指针,指向它所属的模型对象。这样,给定一个索引,不再需要任何其他信息,就可以唯一确定一个模型对象中的一个数据项。
当数据集发生变化时,比如部分数据项被删除或者有新的数据项加入,QModelIndex表示的索引可能失效,也就是不再指向原先所指的数据项。而QPersistentModelIndex所表示的索引却总是指向某一个数据项,不会随着数据集的变化而失效,除非它所指的数据项被删除。
(5)选择。视图类允许用户选择一些数据项。在最复杂情况下,用户可以选择多个父节点所包含的多个子节点。以从上到下、从左到右的阅读顺序,每个父节点下被选的多个子节点可能是不连续的。我们将连续的被选子节点形成的集合称为“选择块”。一个QItemSelectionRange对象记录一个选择块的范围信息,它使用数据成员tl(top-left)表示选择块左上角数据项的索引,使用数据成员br(bottom-right)表示选择块右下脚数据项的索引。一个QItemSelection对象包含多个QItemSelectionRange对象,表示多个选择块的信息。
视图类使用QItemSelectionModel记录选择信息,该类的数据成员currentSelection所指的QItemSelection对象表示用户最近一次所做的选择,而数据成员ranges所指的QItemSelection对象表示最近一次之前所选的选择块。