
MVC把图形界面分为三个部分:模型(Model),视图(View)和控制器(Controller),
MVC把需要处理的数据及其显示分离开来。
Qt实现的MVC模型:
其实Qt中的MVC并不叫MVC,而是叫“MVD”,Qt中没有Controller的说法,而是使用了另外一种抽象: Delegate (委托) ,其行为和传统的MVC是相同的。


Qt对模型/视图结构的具体实现
模型:Qt使用抽象类QAbstractItemModel来描述模型,所有的模型都是通过子类化该抽象类而实现的。Qt实现了一些标准的现成模型,比如:
视图:Qt使用抽象类QAbstractItemView来描述视图,所有的视图都是通过子类化该抽象类而实现的。Qt实现了一些标准的现成视图,比如QListView,QTableView,QTreeView等等。
委托:Qt使用抽象类QAbstractItemDelegate来描述委托,Qt实现了两个委托类,QStyledItemDelegate和QItemDelegate,这两个委托之中只能使用其中一个,其区别在于QItemDelegate总是使用一种默认的样式绘制数据项,而QStyledItemDelegate使用当前的样式来绘制数据项,通常使用的是QStyledItemDelegate。Qt默认使用QStyledItemDelegate。
使用Qt MVC 模型/视图的步骤示意如下:
# 创建一个3行3列的表格结构的模型
model = QStandardItemModel(3,3)
# 创建一个表格视图
my_table_view = QTableView()
# 设置模型的数据
model.setData(model.index(0,0),123);
model.setData(model.index(0,1),222);
model.setData(model.index(0,2),333);
model.setData(model.index(1,0),444);
model.setData(model.index(1,1),555);
model.setData(model.index(1,2),666);
model.setData(model.index(2,0),777);
model.setData(model.index(2,1),888);
model.setData(model.index(2,2),999);
# 设置视图的模型
my_table_view.setModel(model)
# 显示视图
my_table_view.show()

模型的结构
在Qt中,无论数据被存储为何种数据结构,模型总是以层次结构(即树形结构)来表示数据,视图按照此约定来访问模型中的数据,若数据是列表或表格结构的数据,则可以把其看做是只含有顶层节点,不含任何叶子节点的树形结构。也就是说,我们在子类化QAbstractItemModel来定义自己的模型结构时,始终应以树形结构为出发点,来组织自己的模型结构。
模型索引(简称索引)
Qt使用模型索引来使数据的表示和访问相分离,视图和委托使用索引来访问模型中的数据项,因此,只有模型知道怎样获取数据。
模型索引不像普通索引仅使用一个数字就能描述,模型索引需要使用3个属性进行描述:行号、列号、父模型索引。虽然模型索引使用行号、列号来定位数据项,但这并不意味着,数据是存储在数组或表格结构中的,使用行号、列号只是允许模型与视图、代理进行通信的一种约定。之所以还需要父模型索引,是因为Qt的模型都是以层次结构(树形结构)来组织的,Qt具体实现时,顶级数据项的父模型索引,使用“无效模型索引”来表示。
模型索引包含一个指向父索引的参数,当使用多个模型时,可避免混淆。
Qt使用QModelIndex来实现模型索引,该类提供的索引是一个临时索引,因为模型可能会随时重新组织其内部结构(即数据项可能随时发生变化,比如删除,增加新数据项等),因此模型索引可能会变得无效,所以模型索引不需要也不应该被存储,若需要对数据项进行长时间的引用,应使用QPersistentModelIndex类创建一个持久模型索引。
使用模型索引引用模型中数据项的方法是使用QAbstractItemModel::index()函数。
无效索引模式:使用零参数的QModelIndex类的构造函数创建,即QModelIndex()就表示创建了一个无效模型索引。

树形结构
// 获取数据项A的模型索引,A的父索引由一个无效模型索引指定
QModeIndex indexA = model.index(0, 0, QModelIndex())
// 获取数据项B的模型索引,B位于父索引indexA的第一行第一列的位置
QModexIndex indexB = model.index(1, 1, indexA)
表格结构
// 获取数据项B的模型索引,B是顶级数据项,因此其父索引是一个无效索引
QModelIndex indexB = model.index(1, 2, QModelIndex())
列表结构
// 获取数据项B的模型索引,B是顶级数据项,因此其父索引是一个无效索引
QModelIndex indexB = model.index(1, 0, QModelIndex())
同一个类型的数据可以作为不同的角色(或作用)来使用,比如对于字符串"AAA",可以把该字符串以文本的形式显示在视图的相应位置上,也可以把该字符串作为工具提示使用,还可以把该字符串作为what’s this的帮助提示等。由此可见,数据的角色,决定了该数据在视图中的显示方法,角色不同,显示方式也不同。
数据项与数据元素:位于同一个位置的数据项,并不仅仅只是一个数据元素,本文把组成数据项的数据称为数据元素,每个数据元素都有其自身的角色,比如一个数据项可能会同时含有图标数据元素(角色为Qt::DecorationRole)、文本数据元素(角色为Qt::DisplayRole)、工具提示数据元素(角色为Qt::ToolTipRole)等等。见下图中的数据项"222"

因为数据项可以由多个数据元素组成,因此把数据项使用一个单独的类来管理是比较方便的,比如对于QStandardItemModel模型的数据项,就使用了类QStandardItem来专门管理其数据项。
为了避免概念上的混乱,下面对数据、数据项、项(项目)、节点、单元格、数据元素、模型索引(索引)做一简介
数据:是一个统称,既可以是数据项也可以是数据元素。
数据项:是由多个数据元素组成的,每个数据元素都有自已的角色。
单元格、节点、项目:这三个概念通常用于指模型中的某一个数据项所在的位置, 只是对于不同的模型结构会有不同的称呼, 比如树形结构通常称为节点,表格结构通常称为单元格,而项目是一种更通用的称呼, Qt 中通常称其为项目(item)或数据项;有时也会对以上概念加以区别对待,比如单元格XXX的数据项,此时单元格表示位置,数据项就表示实际存储的数据。不管怎样,模型中某个数据项所在位置都是需要使用模型索引来指定的,因此单元格、节点、项目、数据项、模型索引这几个概念通常会根据不同的情形用于表示模型中的某个位置。
标准的数据角色由枚举Qt::ItemDataRole来描述,见下表。数据角色由QAbstractItemModel::setData()函数的第 3 个参数指定,其使用方法如下,其效果见第 2 点图示中第 1 行第 2 列的单元格:

选择模型: 对视图内项目的选择
Qt 使用 QItemSelectionModel 类来实现。所有的标准视图都有自已默认的选择模型。视图可以使用 QAbstractItemView::selectionModel()函数和QAbstractItemView::setSelectionModel()来获取和设置选择模型。通常不需要对选择模型进行设置。
Qt 通过模型/视图框架结构实现了一些标准的方便使用的部件,使用这些便利部件比较简单。
这些类是 QListWidget、 QTreeWidget、 QTableWidget。