有时,我们的系统需要显示大量数据,比如从数据库中读取数据,以自己的方式显示在自己的应用程序的界面中。早期的 Qt 要实现这个功能,需要定义一个组件,在这个组件中保存一个数据对象,比如一个列表。我们对这个列表进行查找、插入等的操作,或者把修改的地方写回,然后刷新组件进行显示。这个思路很简单,也很清晰,但是对于大型程序,这种设计就显得苍白无力。比如,在一个大型系统中,你的数据可能很大,全部存入一个组件的数据对象中,效率会很低,并且这样的设计也很难在不同组件之间共享数据。如果你要几个组件共享一个数据对象,要么你就要用存取函数公开这个数据对象,要么你就必须把这个数据对象放进不同的组件分别进行维护。

    Smalltalk 语言发明了一种崭新的实现,用来解决这个问题,这就是著名的 MVC 模型。对这个模型无需多言。MVC 是 Model-View-Controller 的简写,即模型-视图-控制器。在 MVC 中,模型负责获取需要显示的数据,并且存储这些数据的修改。每种数据类型都有它自己对应的模型,但是这些模型提供一个相同的 API,用于隐藏内部实现。视图用于将模型数据显示给用户。对于数量很大的数据,或许只显示一小部分,这样就能很好的提高性能。控制器是模型和视图之间的媒介,将用户的动作解析成对数据的操作,比如查找数据或者修改数据,然后转发给模型执行,最后再将模型中需要被显示的数据直接转发给视图进行显示。MVC 的核心思想是分层,不同的层应用不同的功能。

    Qt 4 开始,引入了类似的 model/view 架构来处理数据和面向最终用户的显示之间的关系。当 MVC 的 V 和 C 结合在一起,我们就得到了 model/view 架构。这种架构依然将数据和界面分离,但是框架更为简单。同样,这种架构也允许使用不同界面显示同一数据,也能够在不改变数据的情况下添加新的显示界面。为了处理用户输入,我们还引入了委托(delegate)。引入委托的好处是,我们能够自定义数据项的渲染和编辑。

    Model View 概览
    Model View 概览

    如上图所示,模型与数据源进行交互,为框架中其它组件提供接口。这种交互的本质在于数据源的类型以及模型的实现方式。视图从模型获取模型索引,这种索引就是数据项的引用。通过将这个模型索引反向传给模型,视图又可以从数据源获取数据。在标准视图中,委托渲染数据项;在需要编辑数据时,委托使用直接模型索引直接与模型进行交互。

    总的来说,model/view 架构将传统的 MV 模型分为三部分:模型、视图和委托。每一个组件都由一个抽象类定义,这个抽象类提供了基本的公共接口以及一些默认实现。模型、视图和委托则使用信号槽进行交互:

    • 来自模型的信号通知视图,其底层维护的数据发生了改变;
    • 来自视图的信号提供了有关用户与界面进行交互的信息;
    • 来自委托的信号在用户编辑数据项时使用,用于告知模型和视图编辑器的状态。
      所有的模型都是QAbstractItemModel的子类。这个类定义了供视图和委托访问数据的接口。模型并不存储数据本身。这意味着,你可以将数据存储在一个数据结构中、另外的类中、文件中、数据库中,或者其他你所能想到的东西中。我们将在后面再详细讨论这些内容。

    QAbstractItemModel提供的接口足够灵活,足以应付以表格、列表和树的形式显示的数据。但是,如果你需要为列表或者表格设计另外的模型,直接继承QAbstractListModelQAbstractTableModel类可能更好一些,因为这两个类已经实现了很多通用函数。关于这部分内容,我们也会在后文中详述。

    Qt 内置了许多标准模型:

    • QStringListModel:存储简单的字符串列表。
    • QStandardItemModel:可以用于树结构的存储,提供了层次数据。
    • QFileSystemModel:本地系统的文件和目录信息。
    • QSqlQueryModelQSqlTableModelQSqlRelationalTableModel:存取数据库数据。
      正如上面所说,如果这些标准模型不能满足你的需要,就必须继承QAbstractItemModelQAbstractListModel或者QAbstractTableModel,创建自己的模型类。

    Qt 还提供了一系列预定义好的视图:QListView用于显示列表,QTableView用于显示表格,QTreeView用于显示层次数据。这些类都是QAbstractItemView的子类。这意味着,如果你要创建新的视图类,则可以继承QAbstractItemView

    QAbstractItemDelegate
    则是所有委托的抽象基类。自 Qt 4.4 依赖,默认的委托实现是QStyledItemDelegate。但是,QStyledItemDelegateQItemDelegate都可以作为视图的编辑器,二者的区别在于,QStyledItemDelegate使用当前样式进行绘制。在实现自定义委托时,推荐使用QStyledItemDelegate作为基类,或者结合 Qt style sheets。

    如果你觉得 model/view 模型过于复杂,或者有很多功能是用不到的,Qt 还有一系列方便使用的类。这些类都是继承自标准的视图类,并且继承了标准模型。这些类并不是为其他类继承而准备的,只是为了使用方便。它们包括QListWidgetQTreeWidgetQTableWidget。这些类远不如视图类灵活,不能使用另外的模型,因此只适用于简单的情形。