Qt包含了一系列item view类,它们使用model/view架构来管理数据及其显示方式的关系。模型(model)提供标准接口来存取数据,视图(view)定义数据的显示方式。即数据的存储和数据的显示是分开的。
model/view架构
Model-View-Contoller(MVC,模型-视图-控制器)是一种设计模式,最初源于Smalltalk,经常用来构建GUI。在设计模式中,对MVC的描述如下:
MVC包含3种对象。Model是应用对象,View是屏幕的显示,Controller定义UI对用户输入的响应方式。在MVC之前,UI管理所有的对象,而MVC把他们进行了解耦,提供了更大的灵活性。(数据-显示-控制 进行了分离)
如果view和controller合二为一,就变成了Model/View的架构。这仍然把数据存储的方式和数据显示的方式进行了分离,但提供了基于相同原则的更简单的框架。这种分离使得在不同的view里显示相同的数据变成可能,以及当实现新的view时不用改变底层的数据结构。为了灵活的处理用户的输入,Qt引入了代理(delegate)的概念。
在Qt框架(特别是其模型/视图编程范式中),模型(models)负责存储和管理数据,而视图(views)负责数据的可视化显示。代理(delegates)则用于定制视图中的项目渲染和编辑行为。模型索引是连接这三者的关键,它允许视图和代理以一致的方式引用模型中的数据,而无需关心这些数据是如何在模型中存储的。这种方式使得模型/视图架构非常灵活,可以处理各种不同类型和结构的数据。
model和数据源进行通信,为其他组件提供接口。通信的本质取决于数据源的类型以及模型的实现方式。
view从model中获取model index,后者提供了对数据项的引用。通过向model提供model index,view可以检索数据源中的数据项。
在标准视图中,代理负责渲染数据项。当数据项被编辑时,代理会直接使用模型索引(model index)与模型(model)进行通信。
通常,如上所述,模型/视图类可以分为三组:模型(models)、视图(views)和代理(delegates)。这些组件中的每一个都由抽象类定义,这些抽象类提供了公共接口,并在某些情况下提供了特性的默认实现。抽象类是为了被继承以提供其他组件所期望的完整功能集;这也允许编写专门的组件。
模型、视图和代理相互之间通过信号和槽进行通信:
模型的信号通知视图关于数据源中数据的更改。
视图的信号提供关于用户与正在显示的项目交互的信息。
代理在编辑过程中使用信号来告诉模型和视图关于编辑器状态的信息。
基类QAbstractItem*:模型-QAbstractItemModel,视图-QAbstractItemView,代理-QAbstractItemDelegate.
模型Models
所有的模型类都基于QAbstractItemModel类。该类定义了可以被视图和代理使用的、访问数据的接口。数据本身不必存储在模型中,数据可以存储在文件、数据库或其他组件中。
QAbstractItemModel提供了访问数据的接口,它为视图(如表格、列表和树)展示数据提供了足够的灵活性。然而,当为类似列表和表格的数据结构实现新模型时,QAbstractListModel 和 QAbstractTableModel 类是更好的起点,因为它们为常见函数提供了适当的默认实现。这些类中的每一个都可以被子类化,以提供支持特定类型列表和表格的模型。
Qt提供了一些现成的模型,可以用来处理数据项:
QStringListModel:用来存储简单的QStringList对象。
QStandardItemModel:管理更复杂的树形结构的项,每个可以包含任意数据。
QFileSystemModel:提供本地文件系统中关于文件和路径的信息。
QSqlQueryModel,QSqlTableModel,QSqlRelationalTableModel用于访问数据库。
如果这些标准的模型不能满足你的需求,你可以继承QAbstractItemModel,QAbstractListModel或QAbstractTableModel来创建自己的模型。
视图Views
完全实现的类包括:QListView显示一列项, QTableView用表格显示数据,QTreeView用层次结构显示数据。上述3类都继承自QAbstractItemView。这些类可以直接使用,也可以从这些类中派生新的类。
代理Delegates
QAbstractItemDelegate是所有代理的基类。默认的实现的类是QStyledItemDelegete,这是Qt的标准视图默认使用的类。二者的区别在于,QStyledItemDelegate 使用当前样式表进行绘制。在实现自定义委托时,推荐使用QStyledItemDelegate 作为基类,或者结合 Qt style sheets。
排序Sorting
在模型/视图的架构下有2种排序,选择哪种取决于底层的模型。
如果你的模型是可排序的,例如它实现了QAbstractItemModel::sort()函数,QTableView和QTreeView提供了API允许你通过编程的方式对模型进行排序。此外,我们可以使能交互式排序,即运行用户通过点击视图的表头进行排序,只要把QHeaderView::sortindicatorChanged()信号连接到QTableView::sortByColumn()槽,和QTreeView::sortByCOlumn()槽。
便捷类
便捷类包括QListWidget,QTreeWidget和QTableWidget。
这些类没有Views类灵活,不能和model使用。推荐使用view类来处理数据。
如果想利用model/view,又想使用item-based接口,可以考虑使用view类,如QListView,QTreeView和QTableView,但和QStandartItemModel一起使用。
使用模型和视图
下面的内容解释如何在qt中使用模型/视图的模式。
Qt包含的2中模型
qt提供了2中基本的模型:QStandardItemModel和QFileSystemModel。QStandardItemModel是个多功能的模型,可用来表示list,table和tree需要的各种类型的数据结构。该模型中保持数据项。
QFileSystemModel用来维护一个目录中内容的信息,它不持有任何数据项,而是仅仅简单的展示文件系统中文件夹内容。
模型类
基本概念
在模型/视图架构中,模型为视图和代理提供了访问数据的标准接口。在Qt中,标准接口定义在QAbstractItemModel类中。不管底层数据项的结构如何, QAbstractItemModel的所有子类用层次结构的方式来展现数据,包括表格。视图使用它来访问模型中的数据,但和显示给用户的信息并不需要严格一致。
模型通过信号和槽的机制通知所有附属的视图,数据改变了。
模型索引 model index
为了保证数据的显示和数据的访问分离,引入了模型索引的概念。每一片可以通过模型来获取的信息都被表示成一个模型索引。视图和代理通过模型索引来请求被显示的数据。
因此,只有模型需要知道如何获取数据,且被模型所管理的数据类型可以被定义地非常的通用。模型索引中包含一个创建他们的模型的指针,当多个模型同时工作时,不会导致困扰。
QAbstractItemModel *model = index.model();
模型索引提供了对信息的暂时的引用,还能被用来获取或修改数据,但需要通过模型来实现。由于模型可能随时重构它内部的结构,模型索引可能变得无效,因此不应该被存储。如果需要长期引用一条信息,一个persistent model index(持久模型索引)需要被创建。暂时的模型索引用QModelIndex类,而持久的模型索引用QPersitentModelIndex类。
想要获取数据项的模型索引,需要项模型提供3个必要属性:行号row,列号column,和父项的模型索引。
行和列
最基本的形式,模型访问表格,数据项用行号和列号来定位。如下所示:
QModelIndex index = model->index(row, column, ...);
数据项的父项(parents of items)
对于树形结构,一个项还可能是其他项的父项。因此在获取模型索引时,还要提供父项:
QModelIndex index = model->index(row, column, parent);
项的角色(Item Roles)
模型中的数据项可以实现不同的角色,例如Qt::DisplayRule是用来访问字符串的,在视图中显示为text。标准的角色定义在Qt::ItemDataRole中。视图用不同的方式展示各种角色的数据,如下图所示。因此给每个角色提供合适的信息很重要。
QVariant value = model->data(index, role);
最常用的角色都定义在 Qt::ItemDataRole里。通过提供合适的数据给每个角色,模型可以提供暗示(hint)给视图和代理,告诉他们应该如何把数据项展示给用户。
总结:
1.模型索引(Model indexes)为视图(views)和代理(delegates)提供了关于模型中项目位置的信息,这种提供信息的方式不依赖于任何底层数据结构。
2.数据项通过row,column以及他们父对象的索引来定位。
3.模型索引被模型构建,当视图和代理需要的时候。
4.模型索引有父索引。
5.如果给一个索引提供了一个invalid的父索引,则表示表示该索引是顶层索引。
6.角色用来区分一个item上所包含的不同数据。
视图类
在模型/视图架构中,视图从模型中获取数据项并展示给用户看。视图中展现数据的格式可能与数据存储的结构完全不同,这就是模型/视图的优点。
视图通常用来管理数据的布局。视图可以渲染特定的数据项,或通过代理来处理渲染和编辑。
除了展示数据外,视图提供在item中导航,以及item的选择。
视图构建时可以没有模型,但想要视图显示信息,就必须提供模型。视图通过使用selections来跟踪用户选择的items,然后每个视图维护各自的选择,或分享给多个视图。
有些视图,如QTableView和QTreeView除了显示items,还显示header(表头),这是通过另一个视图类实现的,QHeaderView.
使用现成的视图
Qt提供了3个现成的视图:QListView, QTreeView和QTableView。
这三种视图对绝大多数应用来说是足够了。
多个视图可以共享选择项,如下所示。
secondTableView->setSelectionModel(firstTableView->selectionModel());
代理类
不同于MVC的模式,模型/视图结构没有单独的组件来管理用户的交互。通常,视图负责把模型中的数据展示给用户,并负责处理用户的输入。为了更灵活的处理用户的输入,交互用代理(delegate)来实现。代理负责提供输入的能力,并且也负责数据项的渲染。代理的标准接口定义在QAbstractItemDelegate类中。
代理被期望能够渲染他们的内容,通过实现paint()和sizeHint()函数。
代理(delegates)中的编辑器可以通过使用控件(widgets)来管理编辑过程,或者直接处理事件来实现。第一种方法(使用控件)将在本节的后续部分中介绍,并且在“SpinBox Delegate”示例中也有所展示。
Pixelator例子展示了如何创建一个用户代理来实现对table view的特定渲染。