本文翻译自QT官方文档QT 4.8 Model/View Programming
一、Model/View框架简介
Qt4推出了一组新的项视图类,使用Model/View框架来管理数据与表示层的关系。Model/View框架带来的功能上的分离给了开发人员更大的弹性来定制数据项的表示,并且提供一个标准的model接口,使得更多的数据源可以被项视图类使用。本文简要介绍了Model/View架构,对涉及的概念做了简单的概述,阐述了项视图系统。架构中的每一个组件都将一一作出解释,同时将用实例对如何使用这些类进行说明。
1、Model/View框架简介
Model-View-Controller(MVC), 是从Smalltalk发展而来的一种设计模式,常被用于构建用户界面。在设计模式中对MVC模式的描述如下:MVC由3种对象组成:模型是应用程序对象,视图是它的屏幕显示方式,控制器定义用户接口对用户输入反应的方式。在MVC设计模式之前,用户界面设计倾向于三者揉合在一起,MVC对它们进行了解耦,提高了灵活性与重用性。
MVC 由三种对象组成,Model负责维护数据(如管理数据库),View负责显示与用户交互(如各种界面),Controller将控制业务逻辑。如果把View与Controller结合在一起,结果就是Model/View框架。Model/View框架依然是把数据存储与数据表示进行了分离,与MVC都基于同样的思想,但更简单。数据存储与数据显示的分离使得在几个不同的View上显示同一个数据成为可能,也可以重新实现新的View,而不必改变底层的数据结构。为了更灵活的对用户输入进行处理,引入了Delegate,使得数据项的传递与编辑可以进行定制。
Model负责与数据源通讯,并提供接口给结构中的别的组件使用。通讯的实质依赖于数据源的类型与Model实现的方式。
View从Model获取模型索引,模型索引是数据项的引用。通过把模型索引提供给Model,View可以从数据源中获取数据。
在标准的Views中,Delegate渲染数据项,当某个数据项被编辑时,Delegate通过模型索引与Model直接进行交互。Model/View相关类可以被分成上面所提到的三组:Models,Views,Delegates。这些组件通过抽象类来定义,提供了共同的接口,在某些情况下,还提供了默认的实现。抽象类意味着需要子类化,以便为其他组件提供完整的功能,同时也可以用来实现定制的组件。
Models、Views、Delegates之间通过信号-槽机制来进行通讯:
从Model发出的信号通知View关于数据源中的数据发生的改变。
从View发出的信号提供了有关被显示的数据项与用户交互的信息。
从Delegate发射的信号被用于在编辑时通知Model和View关于当前编辑器的状态信息。
Model/View框架中,所有模型类具有共同的抽象基类QAbstractItemModel,所有视图类具有共同的抽象基类QAbstractItemView,所有委托类具有共同的抽象基类QabstractItemDelegate。
2、Models
所有的Models都基于QAbstractItemModel类,QAbstractItemModel类定义了用于Views和Delegates访问数据的接口。数据本身不必存储在Model,可存储在一个数据结构或另外的类、文件、数据库或别的程序组件中。
QAbstractItemModel提供给数据一个接口,非常灵活,基本满足Views的需要,无论数据用以下任何样的形式表现,如tables,lists,trees。然而,当重新实现一个Model时,如果Model基于table或list形式的数据结构,最好从QAbstractListModel、QAbstractTableModel开始做起,因为它们提供了适当的常规功能的缺省实现。这些类可以被子类化以支持特殊的定制需求。子类化model的过程在Create New Model部分讨论
QT提供了一些现成的Models用于处理数据项:
QStringListModel:用于存储简单的QString列表。
QStandardItemModel :管理复杂的树型结构数据项,每项都可以包含任意数据。
QDirModel :提供本地文件系统中的文件与目录信息。
QSqlQueryModel、QSqlTableModel、QSqlRelationTableModel:用来访问数据库。
当标准Model不能满足需要时,可以子类化QAbstractItemModel,
QAbstractListModel或是QAbstractTableModel来定制。
3、Views
不同的View都完整实现了各自的功能:QListView把数据显示为一个列表,QTableView把Model中的数据以表格的形式表现,QTreeView用具有层次结构的列表来显示Model中的数据。QListView、QTableView、QTreeView都基于QAbstractItemView抽象基类,都完整的进行了实现,但都可以用于子类化以便满足自定义视图的需求。
4、Delegates
QAbstractItemDelegate是Model/View架构中的用于Delegate的抽象基类。
从Qt4.4开始,默认委托实现由 QStyledItemDelegate提供,被作为Qt标准视图的默认委托来使用,但是,QStyledItemDelegate 和 QItemDelegate是为视图项提供绘制和编辑器的两个独立的委托。他们之间的不同在于,QStyledItemDelegate使用当前的样式来绘制它的项。因此在实现自定义委托或使用Qt样式表时,我们建议使用QStyledItemDelegate作为基类。
5、排序
在model/view架构中,有两种方法可以实现排序,选择哪种方法依赖于底层Model。如果model是可排序的,即模型重新实现了QAbstractItemModel::sort()函数,QTableView与QTreeView都提供了API,允许以编程的方式对Model数据进行排序。此外,可以通过把QHeaderView::sortIndicatorChanged()信号与 QTableView::sortByColumn()槽或QTreeView::sortByColumn()槽的分别进行连接,也可以进行互动式的排序(比如,允许用户单击表头来对数据进行排序)。
如果模型没有所要求的接口,或想用列表视图Listview来显示数据,另一个方法是就在视图显示数据之前使用代理模型(PROXY MODEL)来转换模型的结构。
6、便利类
为了使应用程序可以使用QT基于项的视图和表格类,从标准的视图类中衍生出了一些项视图的便利类。他们的目的不是用于子类化的,他们的存在只是为了给QT3中相应的类提供一个类似的接口。这些类包括QListWidget、QTreeWidget和QTableWidget,他们分别提供了QT3中的QListBox、QListView、和QTable相似的行为。
这些类没有视图类那么灵活,也不能用于任意模型。除非你强烈需要一套基于项的便利类,否则我们推荐你在处理项视图数据时使用模型/视图的方法。
如果希望利用模型/视图方法所提供的特性,同时又想使用基于项的接口,那就考虑把QStandardItemModel类与视图类如QListView、QTableView和 QTreeView等搭配使用。
7、Models 和Views的使用
QT提供了两个标准的Models:QStandardItemModel和QFileSystemModel。 QStandardItemModel是一个多用途的Model,可用于表示list,table,tree等Views所需要的各种不同的数据结构。QStandardItemModel本身持有数据。QFileSystemModel维护目录内容的相关信息,本身不持有数据,只是对本地文件系统中的文件与目录的简单显示。QFileSystemModel是一个现成的Model,很容易进行配置以用于现存的数据。使用QFileSystemModel可以很好地展示如何给一个现成的View设定Model,研究如何用Model indexes来操纵数据。
QListView与QTreeView很适合与QFileSystemModel搭配使用。下面的例子在树形视图与列表视图显示了相同的信息。两个视图共享用户选择,用户选中的项在两个视图中都会被高亮。
先创建一个QFileSystemModel以供使用,再创建多个Views去显示目录的内容。Model的创建与使用都在main()函数中完成:
#include #include #include #include #include int main(int argc, char *argv[]){ QApplication a(argc, argv); QSplitter *splitter = new QSplitter; QFileSystemModel *model = new QFileSystemModel; model->setRootPath(QDir::currentPath()); QTreeView *tree = new QTreeView(splitter); tree->setModel(model); tree->setRootIndex(model->index(QDir::currentPath())); QListView *list = new QListView(splitter); list->setModel(model); list->setRootIndex(model->index(QDir::currentPath())); splitter->setWindowTitle("Two views onto the same file system model"); splitter->show(); return a.exec();}
设置View显示Model中的数据,需要调用setModel(),并把Model参数传递
setRootIndex()设置Views显示哪个目录的信息,需要提供一个model index参数,index()函数把一个目录做为参数,得到需要的model index。
二、Model类
1、Model简介
Model/View构架中,Model为View和Delegates使用数据提供了标准接口。
Model里面并不真正存储数据(数据少的话也可以直接存储在Model里),只是负责从诸如磁盘文件、数据库、网络通讯等获得源数据,并提供给View,View对数据进行修改,然后再通过Model更新源数据。在QT中,标准接口通过QAbstractItemModel类被定义。QT内置了多种标准模型:
QStringListModel:存储简单的字符串列表。
QStandardItemModel:可以用于树结构的存储,提供了层次数据。
QFileSystemModel:本地系统的文件和目录信息。
QSqlQueryModel、QSqlTableModel和QSqlRelationalTableModel:存取数据库数据。
自定义模型:继承QAbstractItemModel创建新的Model。QAbstractListModel或QAbstractTableModel提供了一些基本的实现,继承QAbstractListModel或QAbstractTableModel可能是更好的选择。
不管数据在底层以何种数据结构存储,所有QAbstractItemModel的子类都将以包含项表格的层次结构来呈现这些数据。视图使用这个约定来存取模型中数据 的项,但是他们向用户显示信息的方法不会受到限制。数据发生改变时,model通过信号槽机制来通知关联的views。
2、模型索引
为了确保数据显示与数据访问分离,引入了模型索引的概念。通过Model获取的数据项可以通过模型索引显示。Views和Delegates都使用索引来访问数据项,然后显示出来。因此,只有Model需要了解如何获取数据,并且Model管理的数据类型可以定义地非常广泛。模型索引包含一个指向创建它们的Model的指针,这样可以避免使用多个Model时引起混淆。
QAbstractItemModel *model = index.model();
模型索引为信息块提供了临时参照,通过它可以用来提取或修改Model中的数据。由于Model经常会重新组织内部的结构,使得模型索引失效,因此不应保存模型索引。如果需要一个对信息块的长期参照,必须创建一个永久的模型索引。这样会为不断更新的Model信息提供一个参照。临时模型索引由QModelIndex类提供,而永久模型索引则由QPersistentModelIndex类提供。
为了获取相应数据项的模型索引,必须指定Model的三个属性:行数,列数,父项的模型索引。
3、行数和列数
在Model最基本的形式中,Model可以使用简单的表格进行存取,表格中的项根据行号和列号确定。但这并不意味底层数据以数组结构储存,使用行号和列号只是部件之间相互通信的一个约定。我们可以提取任何指定行号和列号的Model项的信息,同时得到一个代表这个项的索引。
QModelIndex index = model->index(row, column, ...);
Model为简单且单一层次数据结构如列表和表格提供接口的模型不需要提供任何其他的信息,但是,就像上面的例子所标明的一样,当要获得一个模型索引时我们要提供更多的信息。
图表显示了一个基本的table Model,表格的每一项用一对行列数来定位。通过行列数,可以获取代表一个数据项的模型索引。
QModelIndex indexA = model->index(0, 0,QModelIndex());
QModelIndex indexB = model->index(1, 1,QModelIndex());
QModelIndex indexC = model->index(2, 1,QModelIndex());
Model的顶层项总是通过指定QModelIndex()函数来作为他们的父项参照。
4、父项
当在一个表格或列表视图中使用数据时,模型提供的表格型数据接口是较为理想的。行列号系统正好映射到Views显示项的方法。然而,诸如树型视图的结构要求Model对其内部项要有更灵活的接口。因此,每一个项也可能是另外一个表的父项,同样地,树型视图的顶级项也可以包含另一个列表。
当需要Model项一个索引时,我们必须提供一些关于这个项的父项的一些信息。在Model外,引用一个项的唯一方法就是通过模型索引,所以一个父项的模型索引也必须要提供。
QModelIndex index = model->index(row, column, parent);
图表显示了一个树Model的表示法,树Model的项由父项,行和列号定位。项”A”和项”C”代表Model中的顶层成员。
QModelIndex indexA = model->index(0, 0, QModelIndex());
QModelIndex indexC = model->index(2, 1, QModelIndex());
项“A”有一个子成员,项“B”的Model index可以用以下的代码获得:
QModelIndex indexB = model->index(1, 0, indexA);
5、数据项的角色
Model中的项可以为其他部件扮演不同的角色,允许为不同的情形提供各种不同的数据。例如,Qt::DisplayRole就是用于存取一个可以在视图中以文字显示的字符串。通常情况下,项包含各种不同的角色的数据,标准的角色由Qt::ItemDataRole定义。通过传递对应项的模型索引给Model,并指定一个角色以取得我们想要的数据的类型,我们就可以从Model中取得项的数据:
QVariant value = model->data(index, role);
角色为Model指明哪一种数据被参照。视图以不同的方式显示角色,所以为每一种角色提供合适的信息是很重要的。Qt::ItemDataRole里定义的标准角色涵盖了项数据最常用的用途。通过为每个角色提供合适的项数据,Model就可以为视图和委托提供关于怎样向用户显示项的提示。各种不同的视图都有根据需要中断或忽略这些信息的自由,同时也可以为特定的程序要求定义另外的角色。
6、总结
模型索引以一种独立于任何底层数据结构之外的方法,为视图和委托提供关于模型中项的定位信息。
项以他们的行号和列号,以及他们的父项的模型索引作为参照
模型索引应其他部件如视图和委托的要求由模型来构造。
当使用index()函数请求一个索引时,如果指定一个有效的模型索引作为父项索引,则返回的索引指向模型里这个父项下面的一个项。这个索引获得该项的子项的一个参照。
当使用index()函数请求一个索引时,如果指定一个无效的模型索引为父项索引,则返回的索引指向模型里的一个顶级项。
角色识别一个项中的各种不同类型的相关数据。
7、Model indexes使用
为了演示如何使用模型索引从模型中获得数据,我们创建了一个没有视图的QFileSystemModel,把文件名和目录名显示在一个部件中。虽然这个例子并没有显示使用模型的常用方法,但是说明了使用模型索引时Model所使用的约定。我们用下面的方法构建一个文件系统模型:
QFileSystemModel *model = new QFileSystemModel;
QModelIndex parentIndex = model->index(QDir::currentPath());
int numRows = model->rowCount(parentIndex);
在这个例子中,我们创建了一个默认的QFileSystemModel,使用这个Model的index()函数提供的一个特定的实现获得一个父索引,同时使用rowCount()函数计算出这个模型的行数。
为简单起见,我们只关注模型第一列的数据。我们按顺序逐行检查,获得每行第一个项的模型索引,然后读取存储在Model项里的数据。
for (int row = 0; row < numRows; ++row)
{
QModelIndex index = model->index(row, 0, parentIndex);
}
为了获得一个模型索引,我们指定行号,列号(第一列为0),以及我们想要的所有数据项的父项模型索引。储存于每个项中的文本可以用Model的data() 函数获得。我们指定一个模型索引以及DisplayRole来取得一个字符串形式的项数据。
QString text = model->data(index, Qt::DisplayRole).toString();
从模型中提取数据的一些基本原则:
A、Model的大小可以用rowCount() 和 columnCount()得到。这两个函数通常要指定一个父模型索引。
B、模型索引用于存取Model里的项。指定项必须要有行号,列号以及父模型索引。
C、要存取Model的顶级项,就用QModelIndex()函数指定一个空的模型索引作为父模型索引。
D、项包含不同角色的数据。要获得一个特定角色的数据,必须要为Model提供模型索引和角色。
通过实现 QAbstractItemModel提供的标准接口可以创建新的模型。
8、自定义Model
QAbstractItemModel定义了Model的标准接口。QAbstractItemModel及其派生类均以表格的形式提供访问数据。
自定义Model需要继承QAbstractItemModel并重写下列函数:
QVariant QAbstractItemModel::data(const QModelIndex & index,
int role = Qt::DisplayRole) const
访问数据的接口,QModelIndex是存储Model表格的索引,index.row()和index.column()可以得到索引中指向的行或列。
role是一个枚举代表了数据的渲染方式,QVariant是变体型可以被转换为任意Qt兼容的数据类型。
bool QAbstractItemModel::setData(const QModelIndex & index, const QVariant & value, int role = Qt::EditRole)
dataChanged信号
void QAbstractItemModel::dataChanged(const QModelIndex & topLeft, const QModelIndex & bottomRight, const QVector & roles = QVector ())
在数据被改变后由setData()方法发送dataChanged()信号,通知视图刷新数据。使用两个QModelIndex通知刷新的范围。
rowCount() / columnCount()
返回模型的行数 / 列数。
headerData()
返回表头信息。
三、View类
1、View简介
在Model/View架构中,View从Model中获得数据项然后显示给用户。数据显示的方式不必与Model提供数据的表示方式相同,可以与底层存储数据项的数据结构完全不同。内容与显式的分离是通过由QAbstractItemModel提供的标准模型接口,由QAsbstractItemview提供的标准视图接口和用来表示数据项的模型索引共同实现的。View负责管理从Model中读取的数据的外观布局。
它们自己可以去渲染每个数据项,也可以利用Delegate来既处理渲染又进行编辑。
除了显示数据,Views也处理数据项的导航,参与有关于数据项选择的部分功能。View也实现一些基本的用户接口特性,如上下文菜单与拖拽功能。View也为数据项提供了默认编程功能,也可搭配Delegate实现自定义的编辑器。
View创建时不必需要Model,但在View能显示一些真正有用的信息之前必须提供一个Model。View通过使用选择来跟踪用户选择的数据项,这些数据项可以由单个View独立维护,也可以由多个View共享。像QTableView和QTreeView这样的视图,除数据项之外也可显示标题(Headers),标题部分通过QHeaderView来实现。标题与View一样总是访问包含他们的同样的Model。通常使用QAbstractItemModel::headerData()函数从Model中获取数据,标题通常以表格的形式显示在标签中。为了为View提供更多特定的标签,新标题需要子类化QHeaderView。
2、View使用
Qt提供了三个现成的View 类,能够以用户熟悉的方式显示Model中的数据。QListView能够以列表的形式将Model中的数据项显示,或是以经典的图标视图形式显示。QTreeView能够将Model中的数据项作为具有层次结构的列表的形式显示,允许以紧凑的深度嵌套的结构进行显示。QTableView能够架构Model中数据项以表格的形式展现,更像是一个电子表格应用程序的外观布局。
以上这些标准View的默认行为足以应付大多的应用程序,它们提供了基本的编辑功能,也可以定制特殊的需求。
3、单个Model使用
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
QStringList numbers;
numbers << "One" << "Two" << "Three" << "Four" << "Five";
QAbstractItemModel *model = new QStringListModel(numbers);
QListView *view = new QListView;
view->setModel(model);
view->show();
return app.exec();
}
View会表示通过Model的接口来访问Model中的数据内容。当用户试图编辑数据项时,View会使用默认Delegate来提供一个编辑组件。
上图显示QListView如何显示字符串列表模型的内容。由于模型是可编辑的,视图会自动允许列表中的每一项使用默认的委托编辑。
4、单个模型对多个视图使用
为多个Views提供相同的Model是非常简单的事情,只要为每个View设置相同的Model。以下代码创建了两个表格视图,每个视图使用同样的简单的表格模型。
QTableView *firstTableView = new QTableView;
QTableView *secondTableView = new QTableView;
firstTableView->setModel(model);
secondTableView->setModel(model);
在Model/View架构中,信号-槽机制的使用意味着Model中发生的改变会传递到所有连接的View中,保证了不管我们使用哪个View,访问的都是同样的一份数据。
上图展示了同一Model上的两个不同的Views,每个视图中包含一点数量的选中项。尽管在不同的View中显示的Model中的数据是一致的,每个View都维护它们自己的内部选择模型,但有时候在某些情况下,共享一个选择模型也是需要的。
5、处理数据项的选择
多个View中数据项选择处理机制由QItemSelectionModel类提供。所有标准的View默认都构建自己的选择模型,以标准的方式与它们交互。视图使用的选择模型可以用selectionModel()函数获得,通过setSelectionModel()函数可以设置选择模型。当我们要在一个Model上提供多个一致的Views时,视图中对选择模型的控制能力是非常有用的。通常来讲,除非子类化一个Model或View,不必直接操作选择的内容。
多视图间共享选择
虽然视图默认提供的选择模式很方便,当我们在同一Model使用多个视图时,在多个视图间方便地显示Model数据和用户选择是需要的。由于View允许自身内部的选择模式可以被设置,使用以下代码可以在多个视图间实现一样的选择:
secondTableView->setSelectionModel(firstTableView->selectionModel());
第二个视图设置了与第一个视图一样的选择模式。两个视图操作同样的选择模式,保持了数据与选中项的同步。
以上例子显示,同样类型的两个视图显示了同一个模型的数据。然而,如果使用两个不同类型的视图,在每个视图显示的选中项将会不同。例如,在一个表格视图中持续选择可能会在数学视图中显示的是高亮。
四、Delegate类
1、Delegate类简介
不同于MVC模式,模型/视图设计并不包含用于处理与用户交互的完全独立的部件。 通常,视图负责把模型数据显示给用户,以及处理用户的输入。为了让这种输入有灵活性,这种交互由Delegates来完成。这些部件在视图中提供输入功能,同时负责传递视图中的单个项。控制Delegates的标准接口在 QAbstractItemDelegate类中定义。
QQAbstractItemDelegate则是所有Delegate的抽象基类。自Qt 4.4之后,默认的Delegate实现是QStyledItemDelegate。但QStyledItemDelegate和QItemDelegate都可以作为视图的编辑器,二者的区别在于,QStyledItemDelegate使用当前样式进行绘制。在实现自定义委托时,推荐使用 QStyledItemDelegate作为基类。
Delegates通过实现 paint() 和 sizeHint()函数来传递他们本身的内容。但是,简单的基于部件的Delegates可以子类化QItemDelegate 类而不是 QAbstractItemDelegate类,这样就可以利用这些函数的默认实现。
Delegates的编辑器可以通过使用部件来管理编辑的过程来实现,也可以通过直接处理事件来实现。第一种方法在这一节的后面会讲到,在Spin Box Delegate这个例子中也是使用的这种方法。
Pixelator这个例子演示了如何建立一个专门用于表格视图的自定义委托。
2、Delegate使用
QT提供的标准视图使用QItemDelegate的实例提供编辑功能。Delegates接口的默认实现以通常的样式将项传递给每一个标准视图:QListView, QTableView, 和 QTreeView。所有的标准角色都由标准视图的默认Delegates处理。
视图所使用的Delegates可以用itemDelegate() 函数返回。setItemDelegate()函数允许你为一个标准视图安装一个自定义的Delegates,当为自定义的视图设定一个Delegates时必须要用到这个函数。
下面要实现的Delegates用一个QSpinBox来提供编辑功能,主要是想用于显示整数的Model。虽然我们基于这个目的建立了一个基于整数的自定义模型,但是我们可以很容易地以QStandardItemModel代替,因为自定义委托控制数据的输入。我们构造一个表格视图以显示模型里的内容,同时会使用自定义的Delegates来进行编辑。
我们用QItemDelegate类来子类化这个Delegate,因为我们不想去写那些自定义的显示函数。然而,我们必须提供下面的函数以管理编辑器部件:
class SpinBoxDelegate : public QItemDelegate
{
Q_OBJECT
public: SpinBoxDelegate(QObject *parent = 0);
QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const;
void setEditorData(QWidget *editor, const QModelIndex &index) const;
void setModelData(QWidget *editor, QAbstractItemModel *model,
const QModelIndex &index) const;
void updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option, const QModelIndex &index) const;
};
注意,构建Delegate时编辑器部件是没有建立的。只有需要时我们才构建一个编辑器部件。
3、提供编辑器
当表格视图需要提供编辑器时,就向Delegate请求提供一个适合当前被修改项的编辑器部件。createEditor()函数提供了Delegate用于建立一个合适部件需要的所有东西:
QWidget *SpinBoxDelegate::createEditor(QWidget *parent,
const QStyleOptionViewItem &,
const QModelIndex &) const
{
QSpinBox *editor = new QSpinBox(parent);
editor->setMinimum(0);
editor->setMaximum(100);
return editor;
}
注意,我们不需要保留一个指向编辑器部件的指针,因为当不再需要编辑器的时候,视图会负责销毁它。
我们把Delegate默认的事件过滤器安装在编辑器上,以确保它提供用户所期望的标准编辑快捷键。额外的快捷键也可以增加到编辑器上以允许更复杂的行为。
通过调用我们稍后定义的函数,视图确保能正确地设定编辑器的数据和几何尺寸大小。根据视图提供的模型索引,我们可以建立不同的编辑器。比如,我们有一列数据是整数,一列数据是字符,那根据当前被编辑的列,我们可以返回一个SpinBox 或 QLineEdit。
Delegate必须提供一个函数以便将Model数据复制到编辑器里。在这个例子中,我们读取储存在displayrole里的数据,并相应的把这个值设定在编辑器spin box中。
void SpinBoxDelegate::setEditorData(QWidget *editor,
const QModelIndex &index) const
{
int value = index.model()->data(index, Qt::EditRole).toInt();
QSpinBox *spinBox = static_cast(editor);
spinBox->setValue(value);
}
在这个例子中,我们知道编辑器部件是一个spin box,但是在Model中,我们可能会因应不同的数据类型提供不同的编辑器,在存取它的成员函数前,我们需要将这个部件转换成适合的类型。
4、提交数据给模型
当用户在spin box中完成编辑数值时,通过调用 setModelData()函数,视图要求Delegate将被编辑后的值储存到Model中。
void SpinBoxDelegate::setModelData(QWidget *editor, QAbstractItemModel *model,
const QModelIndex &index) const
{
QSpinBox *spinBox = static_cast(editor);
spinBox->interpretText();
int value = spinBox->value();
model->setData(index, value, Qt::EditRole);
}
由于视图为Delegate管理编辑器部件,所以我们只要以编辑器提供的内容更新模型。在这个例子中,我们确保spinbox的内容是最新的,同时通过指定的index把spinbox包含的值更新到模型中。
当Delegate完成编辑时,标准的QItemDelegate类会发出closeEditor()信号通知视图。视图会确保关闭和销毁编辑器部件。在这个例子中,我们只提供简单的编辑功能,所以我们不需要发出这个信号。
所有的数据操作都是通过QAbstractItemModel提供的接口进行的。这使得Delegate最大程度地独立于它所操控的数据类型。但是为了使用某些类型的编辑器部件,一些假设是必须要做的。在这个例子中,我们假设Model包含的数据全部是整数值,但是我们仍然可以将这个Delegate用于不同种类的Model,因为QVariant可以为没有考虑到的数据提供合理的默认值。
5、更新编辑器几何外形
编辑器的几何外形由Delegate负责管理。当创建编辑器时,以及视图中项的大小及位置改变时,都必须设定编辑器的几何外形。幸好,在视图中,View 选项对象中提供了所有必需的几何尺寸信息。
void SpinBoxDelegate::updateEditorGeometry(QWidget *editor,
const QStyleOptionViewItem &option, const QModelIndex &) const
{
editor->setGeometry(option.rect);
}
在这个例子中,我们只使用了View选项提供的项矩形几何尺寸信息。传递多个要素项的Delegate不会直接使用项矩形。它会定位与项中的其它要素编辑器的位置。
6、编辑提示
编辑后,Delegate应该为其他部件提供关于编辑过程结果的提示,同时提供协助后续编辑操作的提示。这可以通过发射closeEditor()信号以及一个合适的提示来实现。这个过程由默认的QItemDelegate事件过滤器负责,在构造spinbox时,事件过滤器就已安装了。
Spinbox的行为可以稍作调整以使它更易用。在QItemDelegate提供的默认事件过滤器中,如果用户按回车来确认他们在spinbox中的选择,Delegate就把值提交给Model并关闭spinbox。通过在spinbox中安装自定义的的事件过滤器,可以改变这种行为,并提供适合我们需要的编辑提示。例如,我们可以用EditNextItem提示来发射closeEditor()信号,来自动地开始视图中下一个项的编辑。
另一种不需要使用事件过滤器的方法就是提供我们自己的编辑器部件,或者是为了方便可以子类化QSpinbox。这种可选方法让我们对编辑器的行为有更多的控制,而这需要以写额外的代码作为代价。如果要自定义一个标准Qt编辑器部件的行为,在Delegate中安装事件过滤器通常是较容易的。
Delegate不必一定要发射这些提示,但相对于那些发射提示以支持通用编辑动作的Delegate,他们跟程序的整合性就会降低,所起的作用也很小。