QT开发(三十七)——Model/View官方文档

来源:互联网 发布:apache php -v phpinfo 编辑:程序博客网 时间:2024/06/05 22:41

QT开发(三十七)——Model/View官方文档

    本文翻译自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,使得数据项的传递与编辑可以进行定制。

wKiom1hCXxyieU1IAABTNuA6La8117.png

    Model负责与数据源通讯,并提供接口给结构中的别的组件使用。通讯的实质依赖于数据源的类型与Model实现的方式。

    View从Model获取模型索引,模型索引是数据项的引用。通过把模型索引提供给Model,View可以从数据源中获取数据。

    在标准的Views中,Delegate渲染数据项,当某个数据项被编辑时,Delegate通过模型索引与Model直接进行交互。Model/View相关类可以被分成上面所提到的三组:Models,Views,Delegates。这些组件通过抽象类来定义,提供了共同的接口,在某些情况下,还提供了默认的实现。抽象类意味着需要子类化,以便为其他组件提供完整的功能,同时也可以用来实现定制的组件。

    Models、ViewsDelegates之间通过信号-槽机制来进行通讯:

    从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形式的数据结构,最好从QAbstractListModelQAbstractTableModel开始做起,因为它们提供了适当的常规功能的缺省实现。这些类可以被子类化以支持特殊的定制需求。子类化model的过程在Create New Model部分讨论
    QT提供了一些现成的Models用于处理数据项:
    QStringListModel:用于存储简单的QString列表。
    QStandardItemModel :管理复杂的树型结构数据项,每项都可以包含任意数据。
    QDirModel :提供本地文件系统中的文件与目录信息。
    QSqlQueryModelQSqlTableModelQSqlRelationTableModel:用来访问数据库。
当标准Model不能满足需要时,可以子类化QAbstractItemModel,

QAbstractListModel或是QAbstractTableModel来定制。

3Views

    不同的View都完整实现了各自的功能:QListView把数据显示为一个列表,QTableView把Model中的数据以表格的形式表现,QTreeView用具有层次结构的列表来显示Model中的数据。QListViewQTableViewQTreeView都基于QAbstractItemView抽象基类,都完整的进行了实现,但都可以用于子类化以便满足自定义视图的需求。

4Delegates

    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中相应的类提供一个类似的接口。这些类包括QListWidgetQTreeWidget和QTableWidget,他们分别提供了QT3中的QListBoxQListView、和QTable相似的行为。

    这些类没有视图类那么灵活,也不能用于任意模型。除非你强烈需要一套基于项的便利类,否则我们推荐你在处理项视图数据时使用模型/视图的方法。
    如果希望利用模型/视图方法所提供的特性,同时又想使用基于项的接口,那就考虑把QStandardItemModel类与视图类如QListViewQTableView和 QTreeView等搭配使用。

7、Models Views的使用

    QT提供了两个标准的Models:QStandardItemModel和QFileSystemModel    QStandardItemModel是一个多用途的Model,可用于表示list,table,treeViews所需要的各种不同的数据结构。QStandardItemModel本身持有数据。QFileSystemModel维护目录内容的相关信息,本身不持有数据,只是对本地文件系统中的文件与目录的简单显示。QFileSystemModel是一个现成的Model,很容易进行配置以用于现存的数据。使用QFileSystemModel可以很好地展示如何给一个现成的View设定Model,研究如何用Model indexes来操纵数据。

    QListView与QTreeView很适合与QFileSystemModel搭配使用。下面的例子在树形视图与列表视图显示了相同的信息。两个视图共享用户选择,用户选中的项在两个视图中都会被高亮。

    先创建一个QFileSystemModel以供使用,再创建多个Views去显示目录的内容。Model的创建与使用都在main()函数中完成:

wKioL1hCX0LTzNBsAAC5QyJjGRQ977.png

#include <QtGui/QApplication>#include <QSplitter>#include <QFileSystemModel>#include <QTreeView>#include <QListView>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

1Model简介

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。

wKiom1hCX2fhp7gJAABaIKq9UGQ488.png

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为简单且单一层次数据结构如列表和表格提供接口的模型不需要提供任何其他的信息,但是,就像上面的例子所标明的一样,当要获得一个模型索引时我们要提供更多的信息。

wKioL1hCX43TD9UnAAA04VNna-U552.png

    图表显示了一个基本的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);

wKiom1hCX6uAyTwzAAApW-xsRT4888.png

    图表显示了一个树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);

wKioL1hCX8yhWbt9AACX8lStoK4767.png

    角色为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,使用这个Modelindex()函数提供的一个特定的实现获得一个父索引,同时使用rowCount()函数计算出这个模型的行数。
    为简单起见,我们只关注模型第一列的数据。我们按顺序逐行检查,获得每行第一个项的模型索引,然后读取存储在Model项里的数据。

for (int row = 0; row < numRows; ++row)

{
     QModelIndex index = model->index(row, 0, parentIndex);

}

    为了获得一个模型索引,我们指定行号,列号(第一列为0),以及我们想要的所有数据项的父项模型索引。储存于每个项中的文本可以用Modeldata() 函数获得。我们指定一个模型索引以及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

1View简介

    在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

2View使用

    Qt提供了三个现成的View 类,能够以用户熟悉的方式显示Model中的数据。QListView能够以列表的形式将Model中的数据项显示,或是以经典的图标视图形式显示。QTreeView能够将Model中的数据项作为具有层次结构的列表的形式显示,允许以紧凑的深度嵌套的结构进行显示。QTableView能够架构Model中数据项以表格的形式展现,更像是一个电子表格应用程序的外观布局。

wKioL1hCX_XQhKrWAACt9TupChs939.png

    以上这些标准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来提供一个编辑组件。

wKioL1hCYBLxgFkCAAAZ8YuDFpk641.png

    上图显示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,访问的都是同样的一份数据。

wKiom1hCYGaDIzgCAABRh4ixVxE710.png

    上图展示了同一Model上的两个不同的Views,每个视图中包含一点数量的选中项。尽管在不同的View中显示的Model中的数据是一致的,每个View都维护它们自己的内部选择模型,但有时候在某些情况下,共享一个选择模型也是需要的。

5、处理数据项的选择

    多个View中数据项选择处理机制由QItemSelectionModel类提供。所有标准的View默认都构建自己的选择模型,以标准的方式与它们交互。视图使用的选择模型可以用selectionModel()函数获得,通过setSelectionModel()函数可以设置选择模型。当我们要在一个Model上提供多个一致的Views时,视图中对选择模型的控制能力是非常有用的。通常来讲,除非子类化一个Model或View,不必直接操作选择的内容。

    多视图间共享选择

    虽然视图默认提供的选择模式很方便,当我们在同一Model使用多个视图时,在多个视图间方便地显示Model数据和用户选择是需要的。由于View允许自身内部的选择模式可以被设置,使用以下代码可以在多个视图间实现一样的选择:

secondTableView->setSelectionModel(firstTableView->selectionModel());

    第二个视图设置了与第一个视图一样的选择模式。两个视图操作同样的选择模式,保持了数据与选中项的同步。

wKiom1hCYJKQS52oAABRh4ixVxE877.png

    以上例子显示,同样类型的两个视图显示了同一个模型的数据。然而,如果使用两个不同类型的视图,在每个视图显示的选中项将会不同。例如,在一个表格视图中持续选择可能会在数学视图中显示的是高亮。

四、Delegate

1Delegate类简介

    不同于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来进行编辑。

wKioL1hCYMKwaLygAAAXhhkkKt0626.png

    我们用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 &/* option */,

    const QModelIndex &/* index */) 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<QSpinBox*>(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<QSpinBox*>(editor);

    spinBox->interpretText();

    int value = spinBox->value();

 

    model->setData(index, value, Qt::EditRole);

}

    由于视图为Delegate管理编辑器部件,所以我们只要以编辑器提供的内容更新模型。在这个例子中,我们确保spinbox的内容是最新的,同时通过指定的indexspinbox包含的值更新到模型中。

    当Delegate完成编辑时,标准的QItemDelegate类会发出closeEditor()信号通知视图。视图会确保关闭和销毁编辑器部件。在这个例子中,我们只提供简单的编辑功能,所以我们不需要发出这个信号。

    所有的数据操作都是通过QAbstractItemModel提供的接口进行的。这使得Delegate最大程度地独立于它所操控的数据类型。但是为了使用某些类型的编辑器部件,一些假设是必须要做的。在这个例子中,我们假设Model包含的数据全部是整数值,但是我们仍然可以将这个Delegate用于不同种类的Model,因为QVariant可以为没有考虑到的数据提供合理的默认值。

5、更新编辑器几何外形

    编辑器的几何外形由Delegate负责管理。当创建编辑器时,以及视图中项的大小及位置改变时,都必须设定编辑器的几何外形。幸好,在视图中,View 选项对象中提供了所有必需的几何尺寸信息。

void SpinBoxDelegate::updateEditorGeometry(QWidget *editor,

    const QStyleOptionViewItem &option, const QModelIndex &/* index */) 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,他们跟程序的整合性就会降低,所起的作用也很小。

五、项视图的选择处理

1、项视图的选择处理简介

    项视图类中使用的选择模型较QT3中有了很多的改进,为基于Model/View架构的选择提供了更全面的描述。虽然项视图提供的处理选择的标准类已经是很充足,但可以创建专门的选择模型以适应自定义的模型和视图的需要。

    视图中关于选中项的信息存储在QItemSelectionModel类的一个实例中,用于保存数据项的模型索引信息,并且独立于任何视图。由于一个模型可以用于很多视图,所以项视图共用选择也是可行的,应用程序就可以以一致的方式显示多个视图。

    选择由多个选择范围组成。仅仅通过记录选中项的开始模型索引与结束模型索引范围,维护大量项的选择信息。为了描述选择,构建多个非连续选择数据项。

    选择是选择模型保留模型索引的集合。最近选择的数据项被称为current selection。即使在使用之后,应用程序可以通过使用某种类型的选择命令来修改选择的效果。

    当前项与选中项

    在视图中,有一个当前项和一个选中项两种独立状态。一个项有可能同时是当前项与选中项。例如,当使用键盘导航时,视图负责确保当中总是有一个当前项。下面的表格列出了当前项与被选择项的不同之处:

    A、只能有一个当前项,可以有多个选中项;

    B、当前项会随着键盘或鼠标按钮点击而改变;当用户交互作用于项时,项选择状态的设定或者撤销要看几个预定义的模式而定—如单选,多选等。

    C、如果编辑键F2按下或选项被双击,当前项将被编辑;当前项被用作一个支撑点来定义一个选择或反向选择(或两者的混合)的范围。

    D、当前项由焦点矩形标注,选中的项由选择矩形标注。

    在处理选择时,考虑使用QItemSelectionModel来记录一个项模型中所有项的选择状态是很有帮助的。只要建立一个选择模型,项的集合就可以选择,撤销选择,或者反向选择,而不需要知道哪些项已经被选择。任何时候都可以提取所有被选择项的索引,同时通过信号和槽机制,其他的部件也可以知道选择 模型的变动。

2、选择模型的使用

    标准视图类提供了大多数应用程序都可以使用的默认选择模型。一个视图所使用的选择模型可以通过视图的selectionModel()函数获取,用setSelectionModel()函数可以在多个视图中共用同一选择模型,所以一般不需要构造新的选择模型。

    指定一个模型,同时为QItemSelection指定一对模型索引,就可以创建一个选择。使用索引指向指定模型中的项,并把解释成一个选中项方块中左上角和右下角的项。要把选择应用于Model里的项,必须把选择提交给选择模型。可以通过多种方法实现,每一种方法在显示选择模型的选择都有不同的效果。

    选择项

    为了演示选择的一些基本特征,我们构建了一个共有32个项的自定义表格模型的实例,并且用一个表格视图显示它的数据。

   TableModel *model = new TableModel(8, 4, &app);
  QTableView *table = new
 QTableView(0);
  table->setModel(model);
  QItemSelectionModel*
 selectionModel = table->selectionModel();

    提取视图的默认选择模型以备后用。我们不修改模型里的任何项,而是选择一些显示在视图左上角的项。要达到这个效果,我们要提取选择区域中左上角及右下角相应的模型索引:

   QModelIndex topLeft;
  QModelIndex bottomRight;
  topLeft =
 model->index(0, 0, QModelIndex());
  bottomRight =
 model->index(5, 2, QModelIndex());

    要选择模型中的这些项,并看到视图上相应的变化,我们需要构建一个选择对象,并把它应用于选择模型:

  QItemSelection selection(topLeft, bottomRight);
  selectionModel->select(selection,QItemSelectionModel::Select);

    通过使用一选择标识组合定义的命令就可以将选择应用于选择模型。在本例中,不管项的原来的状态是怎样,使用的标识会使选择对象记录的项都包含在选择模型中。选择的结果由视图显示。

wKioL1hCYOiT_NKZAAChrwbVEWo069.png

    项的选择可以通过由选择标识定义的不同操作进行修改。由此而产生的选择结果可能有一个复杂的结构,但能通过选择模型被有效地呈现。

    读取选择状态

    储存在选择模型里的模型索引可以通过selectedIndexes()函数读取。selectedIndexes()函数返回一个未排序的模型索引列表,只要我们知道这些模型索引属于哪一个Model,就可以历遍这些选择项。

QModelIndexList indexes = selectionModel->selectedIndexes();

QModelIndex index;

foreach(index, indexes)

{

    QString text = QString("(%1,%2)").arg(index.row()).arg(index.column());

    model->setData(index, text);

}

    以上代码使用QTforeach关键字来历遍及修改选择模型返回的索引所对应的项。

选择模型发射信号以表明选择的变动。这些信号通知其它部件关于项模型中整体选择及当前焦点项的改变。我们可以把selectionChanged()信号 连接到一个槽,当选择改变时,就可以检查模型中被选择或撤销选择的项。这个槽的呼叫要用到两个QItemSelection对象:一个包含新选择项对应的 索引列表,另一个包含新撤销选择项的对应索引列表。以下代码我们提供一个接受selectionChanged() 信号的槽,用一个字符串填满选择的项,并清除撤销选择项的内容。

void MainWindow::updateSelection(constQItemSelection &selected,
     const QItemSelection&deselected)
{
     QModelIndex index;
     QModelIndexList items =selected.indexes();
     foreach (index, items) {
    
 QString text =QString("(%1,%2)").arg(index.row()).arg(index.column());
    
 model->setData(index, text);
     }
     items =
 deselected.indexes();
     foreach (index, items)
        model->setData(index, "");
}

    将currentChanged()信号连接到一个使用两个模型索引为参数的槽函数,我们就能够保持对当前焦点项的跟踪。这两个索引分别对应前一个焦点项和当前焦点项。下面的代码我们提供一个接受currentChanged()的槽,并使用提供的信息更新QMainWindow的状态栏:

void MainWindow::changeCurrent(const QModelIndex ¤t,

    const QModelIndex &previous)

{

    statusBar()->showMessage(

        tr("Moved from (%1,%2) to (%3,%4)")

            .arg(previous.row()).arg(previous.column())

            .arg(current.row()).arg(current.column()));

}

    用户通过这些信号对选择进行监控,但是我们也可以直接更新选择模型。

    更新选择

    选择命令是通过QItemSelectionModel::SelectionFlag定义的一个选择标识组合来执行的。当任何一个select()函数被调用时,每一个选择标识就会通知选择模型应怎样更新选择项的内部记录。最常用的标识就是Select,它指示选择模型把指定的项记录为选中的状态。 Toggle标识使选择模型转换指定项的状态,选择原来没有选中的项,撤销对当前选中项的选择。Deselect标识撤销对指定项的选择。

    选择模型里的单个项可以通过建立一个选择项,然后把这些选择项应用到选择模型来更新。在下面的代码中,我们把第二个选择项应用于前面的表格模型,然后用Toggle指令来转换指定项的选择状态。

QItemSelection toggleSelection;
  topLeft =model->index(2, 1, QModelIndex());
  bottomRight =model->index(7, 3, QModelIndex());
  toggleSelection.select(topLeft, bottomRight);
  selectionModel->select(toggleSelection, QItemSelectionModel::Toggle);

这个操作的结果显示在下面的表格视图中,直观地显示了我们达到的效果:

wKiom1hCYQqDSqB4AAChy_UUTfQ450.png

    默认情况下,选择指令只对模型索引指定的单个项进行操作。然而,用于描述选择指令的标识可以跟额外的标识搭配使用,以改变整行和整列的选择。例如,如果用一个索引调用select(),但是跟一个有Select和Rows组合的指令使用,该项所在的整行都会被选择。下面的代码演示了Rows和 Columns的使用:

 QItemSelection columnSelection;

    topLeft = model->index(0, 1, QModelIndex());

    bottomRight = model->index(0, 2, QModelIndex());

    columnSelection.select(topLeft, bottomRight);

    selectionModel->select(columnSelection,

    QItemSelectionModel::Select | QItemSelectionModel::Columns);

    QItemSelection rowSelection;

    topLeft = model->index(0, 0, QModelIndex());

    bottomRight = model->index(1, 0, QModelIndex());

    rowSelection.select(topLeft, bottomRight);

    selectionModel->select(rowSelection,

    QItemSelectionModel::Select | QItemSelectionModel::Rows);

    虽然只指定了四个索引给选择模型,但Columns 和 Rows 选择标识的使用意味着选择了两列和两行。下图显示了这两个选择的结果:

wKioL1hCYS-iSMXOAACiEgtwmmg048.png

    应用于本例模型的命令全部都是涉及累积模型中项的选择的。其实它还可以清除选择,或者是以新的选择替换当前的选择。

    要用一个新选择替换当前的选择,就要用Current标识跟其它选择标识搭配使用。使用这个标识的指令通知选择模型用select()函数里指定的模型索引集合来替换当前的模型索引集合。在开始增加新的选择前,如果要清除所有的选择,就要用Clear标识跟其它选择标识搭配使用。它有重设选择模型的索引集合的效果。

    选择模型的所有项

    为了选择模型里的所有项,必须为模型的每一层建立一个选择,以涵盖该层的所有项。我们通过一个给定的父索引并提取左上角和右下角相应的索引来实现这个操作。

QModelIndex topLeft = model->index(0, 0, parent);

QModelIndex bottomRight = model->index(model->rowCount(parent)-1,

        model->columnCount(parent)-1, parent);

    以这些索引和模型构建一个选择,到时相应的项就会在选择模型中被选中。

QItemSelection selection(topLeft, bottomRight);

selectionModel->select(selection, QItemSelectionModel::Select);

    对模型所有的层都要执行这样的操作,对于顶层项,我们以通常的方法定义父项索引:

QModelIndex parent = QModelIndex();

    对于等级层次类型的模型,使用hasChildren() 函数可以确定一个指定的项是否是另一层次项的父项。

六、自定义Model

    模型与视图的部件在功能上的分离,使得可以创建模型并能利用现有的视图。这种方法让我们可以使用标准的图形用户界面部件,如QListView, QTableView, 和QTreeView,来呈现多样化来源的数据。

    QAbstractItemModel类提供了一个足够灵活的接口,它支持以层次结构安排信息的数据源,为数据的插入,移动,修改或排序提供了可能性。它同时对拖放操作也提供支持。

    QAbstractListModel 和 QAbstractTableModel 类为较简单的无层次数据结构的接口提供支持,同时用作简单列表和表格模型的起点(模型基类)会比较容易使用。

    本节中,我们建立一个简单的只读模型来了解模型/视图结构的基本原则。然后我们改写这个简单模型使得用户可以修改项。
    对于更为复杂的模型,请看例子Simple Tree Model 关于子类化QAbstractItemModel 的要求在 Model Subclassing Reference子类化模型参考的文档中会有更加详细的描述。

1、模型设计

    当为一个现有的数据类型创建一个新的模型时,考虑哪一种类型的模型适合于为数据提供接口是很重要的。如果数据结构可以用一个列表或表格的项来表示,可以子类化QAbstractListModel 或QAbstractTableModel,因为这两个类为很多函数提供了合适的默认实现。

    然而,如果底层的数据结构只能以层次树结构来表示,那就必须子类化 QAbstractItemModel类,在Simple Tree Model 例子中使用的就是这种方法。
    在本节中,我们实现一个基于字符串列表的简单模型,所以QAbstractListModel是一个理想的基类。

    无论底层的数据结构如何组织,在自定义的模型中提供QAbstractItemModel的接口函数是很好的思路,这可以很好的访问底层的数据结构。这样使得操作带数据的模型更容易,而且可以使用标准API与其他通用的Model/View组件进行交互。

2、只读模型

    我们这里实现的模型是一个简单的、无层次的、基于标准的QStringListModel类的只读数据模型。它包含一个QStringList字符串列表作为内部数据源,并且只实现所需要的东西作为功能性模型。为了使实现更加容易,我们子类化 QAbstractListModel,因为它为列表模型定义了合理的默认行为,并且有比QAbstractItemModel更简单的接口。

    当实现一个模型时,很重要的一点是QAbstractItemModel本身并不存储任何数据,只提供一个视图用于存取数据的接口。对于一个微型只读模型,只需要实现几个函数就可以,因为大多数接口都有默认的实现。类的声明如下:

class StringListModel :public QAbstractListModel
{
     Q_OBJECT

public:
     StringListModel(constQStringList &strings, QObject *parent = 0)
         : QAbstractListModel(parent),stringList(strings) {}

     int rowCount(constQModelIndex &parent = QModelIndex()) const;
     QVariant data(constQModelIndex &index, int role) const;
     QVariant headerData(intsection, Qt::Orientation orientation,
                         int role = Qt::DisplayRole)const;

private:
     QStringList stringList;
};

    除了模型的构造函数,我们只需要实现两个函数:rowCount()返回模型里的行数,data()返回指定模型索引对应项的数据。设计良好的模型同时还要实现headerData(),以便让树型或表格视图显示它们的表头信息。

    需要注意的是这是一个非层次结构的模型,所以我们不必担心父-子项关系。如果我们的模型是层次结构的,那我们还必须实现index() and parent()函数。字符串列表储存在内部的私有成员变量stringList中。

3、模型的维度

    我们想要模型的行数跟字符串列表中字符串的个数一样,在实现rowCount()函数时要记住这一点。

int StringListModel::rowCount(const QModelIndex &parent) const
{
     return stringList.count();
}

    因为模型是非层次结构的,因此我们可以忽略父项对应的模型索引。从QAbstractListModel派生出来的模型默认只保留一栏,所以我们不必重新实现 columnCount()函数。

4、模型的表头和数据

    对于视图中的项,我们要返回字符串列表中的字符串。data()函数就是负责返回对应索引参数项的数据的:

QVariant StringListModel::data(const QModelIndex &index, int role) const
{
     if (!index.isValid())
         return QVariant();
     if (index.row() >=stringList.size())
         return QVariant();
     if (role ==Qt::DisplayRole)
         return stringList.at(index.row());
     else
         return QVariant();
}

    如果提供的模型索引有效,行数在字符串列表的项范围内,且要求的角色是支持的,那我们只返回一个有效的QVariant。
    一些视图,如QTreeView 和 QTableView,表头可以跟项数据一起显示。如果我们的模型显示在带表头的在视图中,我们想让表头显示行号和列号。我们可以通过子类化headerData() 函数来提供表头的相关信息。

QVariantStringListModel::headerData(int section, Qt::Orientation orientation,
                    int role) const
{
     if (role !=Qt::DisplayRole)
         return QVariant();
     if (orientation ==Qt::Horizontal)
         returnQString("Column %1").arg(section);
     else
         returnQString("Row %1").arg(section);
}

    再次强调,只有是我们支持的角色才返回一个有效的QVariant。当要确定返回的确切数据时,表头的方向也是要考虑的。并不是所有的视图在显示项数据时都显示表头,那些不显示表头的视图可能已设定了隐藏表头。但是,我们还是建议实现headerData()函数以便提供模型数据的相关信息。

    一个项可以有几个角色,依据指定的角色可以给出不同的数据。在我们现在创建的模型中,项只有一个角色DisplayRole,所以不管指定什么角色,我们都返回角色指定相应项的数据。然而,我们可以重新利用DisplayRole角色提供的数据用在其它角色上,如ToolTipRole角色,视图可以以工具提示的形式显示项的信息。

5、可编辑模型

    只读模型显示了如何向用户呈现简单的选项,但是对于大多数的应用程序,一个可编辑的列表模型会更有用。我们可以通过修改为只读模型而实现的 data()函数,以及实现另外两个函数flags() 和 setData(),来把只读模型改为可以编辑项的模型。下面的函数声明要加到类定义中:

Qt::ItemFlags flags(constQModelIndex &index) const;
  bool setData(constQModelIndex &index, const QVariant &value,
                  int role =Qt::EditRole);

6、可编辑模型的标识

    在创建编辑器之前,委托会检查项是否可以编辑。模型必须让委托知道它的项是可以编辑的。我们可以通过为模型里的每一个项返回正确的标识来实现这个要求。在这个例子中,我们把所有的项激活并把它们设定为可选择及可编辑的:

Qt::ItemFlagsStringListModel::flags(const QModelIndex &index) const
{
     if (!index.isValid())
         returnQt::ItemIsEnabled;
     returnQAbstractItemModel::flags(index) | Qt::ItemIsEditable;
}

    我们不需要知道委托怎样执行实际的编辑过程。我们只需要为委托提供一个在模型中设定数据的方法。这个方法可以通过setData()函数来实现:

bool StringListModel::setData(const QModelIndex &index,
               const QVariant &value, int role)
{
     if (index.isValid()&& role == Qt::EditRole) {
         stringList.replace(index.row(),value.toString());
         emitdataChanged(index, index);
         return true;
     }
   return false;
}

    在这个模型中,对应模型索引的字符串项被提供的值所代替。然而,在我们修改字符串列表之前,我们必须保证索引是有效的,项是正确的类型,并且角色是支持的角色。通常,角色我们要用EditRole,因为它是标准项委托所使用的角色。然而,对于逻辑值,可以使用角色 Qt::CheckStateRole,并设定标识为Qt::ItemIsUserCheckable;这样就可以用一个复选框来编辑这个值。对于所有的角色,模型中的底层数据都是一样的,这只是为了让模型与标准部件结合使用时更加容易。

    当设定数据以后,模型必须要让视图知道某些数据已经被改动。这个可以通过发射 dataChanged()信号来完成。因为只有一个项的数据有更改,所以信号中所指定的项范围仅限于一个模型索引。
    同时,data()函数也要更改以增加对 Qt::EditRole角色的检测

QVariant StringListModel::data(constQModelIndex &index, int role) const
{
     if (!index.isValid())
         return QVariant();
     if (index.row() >=stringList.size())
         return QVariant();
     if (role ==Qt::DisplayRole || role == Qt::EditRole)
         returnstringList.at(index.row());
     else
         return QVariant();
}

7、插入、删除行

    在一个模型中,行数和列数是可以改变的。在字符串列表模型中,只有行数改变有意义,所以我们只需要重新实现插入和删除行的函数。这些函数在类定义中的声明如下:

bool insertRows(intposition, int rows, const QModelIndex &index = QModelIndex());
  bool removeRows(intposition, int rows, const QModelIndex &index = QModelIndex());

    由于字符串列表模型中行数对应的字符串列表中的字符串数量,所以insertRows()函数在字符串列表中指定的位置之前插入几个空的字符串。插入的字符串的个数跟所指定的行数是相等的。

    父索引通常是用来决定行应加在模型的什么位置。在本例中,我们只有一个顶层的字符串列表,所以我们插入空字符串到这个列表就可以。

bool StringListModel::insertRows(int position, int rows, const QModelIndex&parent)
{
     beginInsertRows(QModelIndex(),position, position+rows-1);
     for (int row = 0; row< rows; ++row) {
        stringList.insert(position, "");
     }
     endInsertRows();
     return true;
}

    模型首先调用beginInsertRows()函数以通知其它部件行数将要改变。这个函数指定了所插入行的首行和末尾行的行号,以及父项的模型索引。当改变了字符串列表后,再调用endInsertRows()函数以完成操作并通知其它部件模型的维度已经改变,同时返回true值表明插入行的操作成功。

    从模型中删除行的函数写起来也很简单。通过给出位置和行数就可以从模型中删除指定的行。为了简化我们的实现,我们忽略了父索引,只从字符串列表中删除对应的项。

bool StringListModel::removeRows(int position, int rows, const QModelIndex&parent)
{
    beginRemoveRows(QModelIndex(), position, position+rows-1);
     for (int row = 0; row< rows; ++row) {
        stringList.removeAt(position);
     }
     endRemoveRows();
     return true;
}

    beginRemoveRows()函数必须要在删除任何底层数据之前调用,并且要指定删除行的首行和末尾行。这样就可以让其它部件在数据失效前存取数据。行被删除后,模型发射endRemoveRows()信号以完成操作,并通知通知其它部件模型的维度已经改变。

    通过使用QListView 类将模型的项展现在一个垂直列表表格中,我们就可以显示这个模型或其它模型提供的数据。对于字符串列表模型,视图同时也提供了一个默认的编辑器,以便对项进行操作。

    在模型子类化参考这节文档中详细地讨论了子类化QAbstractItemModel的要求,同时为那些必须实现的虚函数提供指引,以便在不同类型的模型中赋予各种特性。

七、项视图便利类

    Qt4 同时也引入了一些标准部件以提供传统的基于项的容器部件。这些部件的作用跟Qt3里的项视图类相似,但为了使用底层的模型/视图框架以改善性能以及方便维护,已经对这些类进行了重写。这些旧的项视图类在兼容库中仍然是可用的

    这些基于项的部件已经按他们的用途进行命名:QListWidget提供项的一个列表,QTreeWidget显示多层次树形结构, QTableWidget提供单元格项的一个表格。每一个类都继承基类 QAbstractItemView的行为,这个基类实现了项选择和表头管理等一些常用的行为。

1、列表组件

    单层次列表的项用一个 QListWidget和一些QListWidgetItems来显示。列表部件的构造跟任何其它部件一样:

QListWidget *listWidget = new QListWidget(this);

    列表项可以在构造的时候直接的添加到列表部件中:

new QListWidgetItem(tr("Sycamore"), listWidget);
  new
 QListWidgetItem(tr("Chestnut"), listWidget);
  new
 QListWidgetItem(tr("Mahogany"), listWidget);

    列表项在构造时也可以不指定父列表部件,而是在之后添加到列表中:

QListWidgetItem *newItem = new QListWidgetItem;
  newItem->setText(itemText);
  listWidget->insertItem(row, newItem);

    列表里的每个项都可以显示文字标识和一个图标。表现文字的颜色和字体也可以可以改变,以便为项提供一个自定义的外观。工具提示, 状态栏提示, 以及“这是什么”等帮助功能都可以很容易地设定,以确保这个列表可以完全地跟应用程序融合在一起。

newItem->setToolTip(toolTipText);
  newItem->setStatusTip(toolTipText);
  newItem->setWhatsThis(whatsThisText);

    默认情况下,列表里的项按照他们创建时的顺序来显示。列表项可以按照Qt::SortOrder指定的规则进行排序,以产生一个按升序或降序排列的列表:

listWidget->sortItems(Qt::AscendingOrder);
  listWidget->sortItems(Qt::DescendingOrder);

2、树形组件

    树形或层次结构型的列表项由QTreeWidget 和QTreeWidgetItem类提供。树形部件里的每个项都可以有它们自己的子项,同时也可以显示多列信息。树形部件的创建跟其它部件一样:

QTreeWidget *treeWidget = new QTreeWidget(this);

    在把项加到树形部件之前,必须先设定列数。例如,我们可以定义两列,然后创建一个表头,以便在每一列的顶部提供一个标识:

treeWidget->setColumnCount(2);
  QStringList headers;
  headers<< tr("Subject") << tr("Default");
  treeWidget->setHeaderLabels(headers);

    为每一列建立标识最容易的方法就是提供一个字符串列表。对于更复杂的表头,可以构造一个树形项,按照你的要求进行布置,并把它作为树形部件的表头来使用。

    树形部件的顶级项以树形部件作为它们的父部件来构造。它们可以以任意的顺序插入,或者在构造每个项时,你可以通过指定前一个项来确保以特定的顺序列出这些项:

 QTreeWidgetItem *cities = new QTreeWidgetItem(treeWidget);
 cities->setText(0,tr("Cities"));
 QTreeWidgetItem *osloItem = new QTreeWidgetItem(cities);
 osloItem->setText(0, tr("Oslo"));
 osloItem->setText(1, tr("Yes"));
 QTreeWidgetItem *planets = new QTreeWidgetItem(treeWidget, cities);

    树形部件处理顶层项的方法跟处理其它层次的项的方法稍有不同。树形组件的顶层项可以通过调用树形部件的takeTopLevelItem()函数来进行删除,而其它低层次的项要通过调用它们父项的 takeChild()函数来进行删除。顶层项的插入用insertTopLevelItem()函数,而其它层次的项要用父项的insertChild()函数来插入。

    在树型部件的顶层项及其它层级的项之间移动是很容易的。我们只需检测这些项是否是顶层项,这个信息由每个项的parent()函数提供。比如,我们可以删除树形部件里的当前项,而不用去管它的具体位置是什么:

QTreeWidgetItem *parent = currentItem->parent();
  intindex;
  if(parent) {
     index = parent->indexOfChild(treeWidget->currentItem());
     delete parent->takeChild(index);
  } else{
     index =treeWidget->indexOfTopLevelItem(treeWidget->currentItem());
     delete treeWidget->takeTopLevelItem(index);
     }

    把项插入到树形部件的某个位置也是使用一样的模式:

QTreeWidgetItem *parent = currentItem->parent();
  QTreeWidgetItem *newItem;
  if(parent)
     newItem = new QTreeWidgetItem(parent,treeWidget->currentItem());
  else
     newItem = new QTreeWidgetItem(treeWidget, treeWidget->currentItem());

3、表格组件

    表格部件跟电子表单程序里用到的相似,都是用QTableWidget和 QTableWidgetItem构造。它们提供一个带表头的可滚动表格,而把项置于其中来使用。
    在构建表格部件时可以指定行数和列数,未定义大小的表格部件也可以在需要的时候再加上行数和列数。

QTableWidget *tableWidget;
  tableWidget = new QTableWidget(12, 3, this);

项可以在添加到表格的指定位置之前,在表格之外单独构造:

QTableWidgetItem *newItem = new QTableWidgetItem(tr("%1").arg(pow(row, column+1)));
 
 tableWidget->setItem(row, column, newItem);

通过在表格外构造项,并把它们作为表头使用,就可以添加表格的水平和垂直表头:

QTableWidgetItem *valuesHeaderItem = newQTableWidgetItem(tr("Values"));
  tableWidget->setHorizontalHeaderItem(0, valuesHeaderItem);

注意,表格的行号和列号是从0开始的。

4、共有特性

    有几个基于项的特性是每个项视图简便类共有的,它们都可以通过每个类的相同接口来进行使用。接下来的章节中我们将通过使用不同组件的几个例子来说明这些特性。对于所使用到得函数,可以通过查看 Model/View Classes列表中的每个部件来获得更详细的内容。

A、隐藏项

    有时需要在一个项视图组件中隐藏项是有用的,而不是删除项。以上所有部件的项都可以隐藏和重现。可以通过调用isItemHidden()函数知道一个项是否被隐藏,同时用setItemHidden()函数可以将项隐藏。
因为这个操作是基于项的,所以相同的函数适用于所有便利类。

B、选择

    项的选择方式由部件的选择模式(QAbstractItemView::SelectionMode)控制。这个属性控制用户是否可以选择一个或多个项,如果是多项选择,选择是否必须是一个连续的项范围。以上所有组件的选择模式的工作方式都是一样的。

    单项选择:当用户需要在一个部件中选择一个单一项时,默认的SingleSelection模式是最适合的。这种模式的当前项和选中项是一样的。

wKiom1hCYWHzbOWhAAAwNlcvuoA129.png

    多项选择:使用这种模式,用户可以在不改变当前选择的情况下切换这个部件中任意项的选择状态,跟非排他性的复选框单独切换的方式很相似的。

wKiom1hCYXzBUZhGAABLqJWUNME095.png

    扩展选择:需要选择许多相邻项的部件,如电子制表,就要使用到扩展选择模式ExtendedSelection。使用这种模式,部件里连续范围的项可以同时用鼠标和键盘进行选择。如果使用修改键,也可以创建复杂的选择,如涉及到部件里跟其它选中的的项不相邻的多个项的选择。如果用户在选择一个项时没有使用修改键,那原来的选择就会被清除。

wKiom1hCYZ7xmX2kAABEOsBa5Fo256.png

    部件中被选中的项可以用 selectedItems()函数来读取,它提供一个可以遍历的相关项的列表。例如,我们可以用下面的代码找出一个选择项列表中所有数值的总和:

QList<QTableWidgetItem*> selected = tableWidget->selectedItems();
  QTableWidgetItem *item;
  intnumber = 0;
  doubletotal = 0;
  foreach(item, selected)

{
    bool ok;
    double value = item->text().toDouble(&ok);
    if(ok && !item->text().isEmpty())

   {
      total += value;
      number++;
   }
 }

    对于单选模式,当前项是被选中的。而对于多选模式和扩展模式,当前项不一定在选择范围内,这取决于用户选择的方式。

 

C、搜索

    无论是作为一个开发人员还是作为一项面向用户的服务,能够在一个项视图中找出项是很有帮助的。所有3种项视图便利类提供了一个通用的findItems()函数,使得这个功能可以尽可能的一致和简单。
    项是以Qt::MatchFlags中的一个值所指定的规则,按照项的文字进行搜索的。我们可以用findItems()函数得到一个符合要求的项的列表。

QTreeWidgetItem *item;
  QList<QTreeWidgetItem *> found = treeWidget->findItems(
        itemText, Qt::MatchWildcard);
  foreach(item, found) {
     treeWidget->setItemSelected(item, true);
     [i][color=#8b0000]// Showthe item->text(0) for each item.[/color][/i]
  }

    上面的代码得到的结果是,树形部件中的项如果包含搜索字符串中的文字就会被选中。这个模式同样可以应用于列表部件和表格部件。

八、项视图的拖放

    模型/视图框架完全支持Qt的拖放基础。列表、表格、树形部件中的项可以在视图间拖动,数据可以以MIME类型的格式进行导入和导出。

    标准视图自动支持内部的拖放,他们的项可以被移动以改变他们的显示顺序。默认情况下,这些视图是不能进行拖放操作的,因为他们被设定成最简单最常用的用法。如果要拖动项,则要开启视图的一些属性,并且项本身也必须是允许拖动的。

    相比那些完全支持拖放的模型,只允许项从一个视图中导出而不允许数据放入到其中的模型是很少的。
    更多关于在新模型中启用拖放支持的内容,请看子类化模型参考这一章节。

1便利类视图的使用

    QListWidgetQTableWidget和 QTreeWidget的每一种类型的项都默认配置有一套不同的标识。比如,QListWidgetItem或 QTreeWidgetItem的初始标识是启用的enabled, 可复选的checkable,可选择的selectable,同时可以作为拖放操作的数据源;QTableWidgetItem可以进行编辑操作,并且可以作为拖放操作的目标。

    虽然所有的标准项都有一或两个标识用于拖放操作,但是你还是要在视图本身设置不同的属性,利用内置的拖放支持。

    要启用项的拖动,就要把视图的dragEnabled 属性设定为true

    要允许用户将内部或外部项拖放到视图中,则要把视图的viewport()的 acceptDrops属性设定为true。

    要显示当前拖动的项将被放在什么地方,则要设定视图的showDropIndicator属性。它会提供关于项在视图中的放置位置的持续更新信息。

    例如,我们可以用以下代码在列表部件中启用拖放功能。

QListWidget*listWidget = new QListWidget(this);
listWidget->setSelectionMode(QAbstractItemView::SingleSelection);
listWidget->setDragEnabled(true);
listWidget->viewport()->setAcceptDrops(true);
listWidget->setDropIndicatorShown(true);

    结果就得到一个可以让项在视图里进行复制的列表部件,甚至可以让用户在包含相同类型数据的视图间拖动项。这两种情况,项是被复制而不是移动。

    如果用户要在视图间移动项,那我们就要设定列表部件的拖放模式dragDropMode。

listWidget->setDragDropMode(QAbstractItemView::InternalMove);

2Model/View类使用

    建立一个可以拖放的基于模型的视图跟简便类视图是一样的模式。例如,可以用建立QListWidget一样的方法建立一个QListView:

QListView *listView = newQListView(this);
listView->setSelectionMode(QAbstractItemView::ExtendedSelection);
listView->setDragEnabled(true);
listView->setAcceptDrops(true);
listView->setDropIndicatorShown(true);

    因为视图所显示的数据的存取是由模型控制的,所以模型也要提供对拖放操作的支持。模型支持的动作可以通过重新实现QAbstractItemModel::supportedDropActions()函数来指定。下面的代码实现复制和移动的操作:

Qt::DropActionsDragDropListModel::supportedDropActions() const
{
     return Qt::CopyAction |Qt::MoveAction;
}

    虽然可以指定一个Qt::DropActions里的值的任意组合,但是还是要写模型来支持他们。例如,为了让一个列表模型能正确地支持 Qt::MoveAction动作,模型必须重新实现QAbstractItemModel::removeRows()函数,不管是直接还是间接从他的 基类继承实现。

3、开启项的拖放

    通过重新实现QAbstractItemModel::flags()函数来提供合适的标识,模型指示视图哪些项可以拖动,哪些项接受放置。

    例如:通过确保返回的标识包含Qt::ItemIsDragEnabled 和 Qt::ItemIsDropEnabled,一个基于QAbstractListModel的简单列表模型就可以使每个项都可以拖放:

Qt::ItemFlagsDragDropListModel::flags(const QModelIndex &index) const
{
     Qt::ItemFlags defaultFlags= QStringListModel::flags(index);
     if (index.isValid())
         returnQt::ItemIsDragEnabled | Qt::ItemIsDropEnabled | defaultFlags;
     else
         returnQt::ItemIsDropEnabled | defaultFlags;
}

    注意,项可以被放置在模型的顶层,而拖动操作只对合法项有效。

    在以上的代码中,因为这个模型是从QStringListModel中衍生出来的,所以我们要调用它的flags()函数实现以包含一套默认的标识。

4导出数据的编码

    在拖放操作中,当项的数据从一个模型中导出时,它们会被编码成对应一个或多个MIME类型的适当的格式。模型通过重新实现返回一个标准MIME类型的列表QAbstractItemModel::mimeTypes()函数,来声明它们可以供项使用的MIME类型。
例如,一个只提供纯文本的模型要提供以下的实现:

QStringListDragDropListModel::mimeTypes() const
{
     QStringList types;
     types <<"application/vnd.text.list";
     return types;
}

    模型同时还要提供对公开格式的数据进行编码的代码。这个可以通过重新实现QAbstractItemModel::mimeData()函数提供一个QMimeData对象来实现,就像在其它的拖放操作里一样

    以下代码的功能是索引参数相关的项数据将被编码为纯文本并被存储在QMimeData对象

QMimeData*DragDropListModel::mimeData(const QModelIndexList &indexes) const
{
     QMimeData *mimeData = new QMimeData();
     QByteArray encodedData;
     QDataStream stream(&encodedData, QIODevice::WriteOnly);
     foreach (QModelIndex index, indexes) {
         if (index.isValid()){
             QString text =data(index, Qt::DisplayRole).toString();
             stream <<text;
         }
     }
    mimeData->setData("application/vnd.text.list",encodedData);
     return mimeData;
}

    由于向函数提供了一个模型索引的链表,所以在层次结构和非层次结构中使用这种方法一般来说是足够了的。
    注意,自定义的数据类型必须声明为meta objects,并且要为它们实现流操作。详细内容请看QMetaObject类里的描述。

5、向模型中插入释放的数据

    任何指定模型处理释放数据的方法要看它的类型(列表,表格或树形)以及它向用户显示其内容的方法而定。通常,累积释放数据的方法是最适合模型底层数据存储的方法。

    不同类型的模型会用不同的方法处理释放的数据。列表和表格模型只提供一个存储项的平面结构。因此,当数据被释放到视图中的一个现有的项上面时,它们可以插入新的行(和列),或者使用提供的数据覆盖掉模型里项的内容。树形模型一般是向他们的底层数据增加包含新数据的子项,因此它的行为会比用户所能想到的更有可预见性。

    释放数据的处理通过重新实现模型的QAbstractItemModel::dropMimeData()函数来实现。例如,一个处理简单字符串列表的模型可以提供一个实现来分别处理放置于现有项之上的数据以及放置于模型顶层的数据(例如,放置到一个无效的项上面)。

    模型首先要确保操作应该作用于的数据是以可用的格式提供的,并且它在模型里的目标是有效的:

bool DragDropListModel::dropMimeData(const QMimeData *data,
     Qt::DropAction action,int row, int column, const QModelIndex &parent)
{
     if (action ==Qt::IgnoreAction)
         return true;
     if (!data->hasFormat("application/vnd.text.list"))[/font]
         return false;
     if (column > 0)
         return false;

    如果提供的数据不是纯文本,或给出的用于放下的列号是无效的,则这个简单的单列字符串列表模型可以将此操作标志为失败。

    根据数据是否被放置在一个现有的项上面作为判断,插入模型的数据将作不同的处理。在这个简单例子中,我们允许把数据放在现有项之间,列表第一个项之前,和最后一个项之后。

    当一个放下操作发生时,如果父项相对应的模型索引是有效的,意味着放下操作发生在一个项上面,如果是无效的,则意味着放下操作发生在视图中对应于模型顶层的某个位置。

int beginRow;
     if (row != -1)
         beginRow = row;

    我们先检查指定的行号看它是否可以用来将项插入到模型中,不管父项的索引是否有效:

else if (parent.isValid())
         beginRow =parent.row();

    如果父项索引是有效德尔,则放下操作发生在一个项上。在这个简单的列表模型中,我们找出项的行号,并用这个值把放下的项插入到模型的顶层。

else
    beginRow =rowCount(QModelIndex());

    当放下动作发生在视图的某个位置,同时行号又是不可使用的,那我们就把项添加在模型的顶层项。
    在层次结构模型中,当放下动作发生在一个项上时,把要插入的项作为该项的子项插入到模型中会更好。在这里讲的简单例子中,模型只要一层,因此这个方法是不适合的。

6、解码导入数据

    每个dropMimeData()的实现同时也必须对数据进行解码, 并把它插入到模型的底层数据结构中。
    对应一个简单的字符串列表模型,编码后的项可以被解码并汇入到 QStringList::

  QByteArray encodedData =data->data("application/vnd.text.list");
     QDataStreamstream(&encodedData, QIODevice::ReadOnly);
     QStringList newItems;
     int rows = 0;
     while (!stream.atEnd()) {
         QString text;
         stream >> text;
         newItems <<text;
         ++rows;
     }

    字符串就可以插入到底层数据。为了保持一致性,可以通过模型自己的接口实现:

insertRows(beginRow,rows, QModelIndex());
     foreach (QString text,newItems) {
         QModelIndex idx =index(beginRow, 0, QModelIndex());
         setData(idx, text);
         beginRow++;
     }
     return true;
}

    注意,模型通常要提供 QAbstractItemModel::insertRows()函数和 QAbstractItemModel::setData()函数的实现。

九、代理模型

    在模型/视图框架中,单个模型提供的数据项可以共享于任意多个视图中,并且每个视图都可能以完全不同的方式显示相同的信息。自定义视图和委托是为相同数据提供根本不同的呈现方式的有效方法。然而,应用程序经常要提供常规的视图用到相同数据的不同处理版本上,如一个列表项的不同排序视图。

    虽然看上去好像适合作为视图的内部参数来执行排序和筛选操作,但是这种方法不允许多个视图共用这种潜在的代价高昂的操作所得的结果。另外一种方法,涉及在模型本身内部的排序,同样会导致相似的问题:每一个视图都必须显示根据最新的处理操作所组织的数据项。

    为了解决这个问题,模型/视图框架使用代理模型来管理单独模型和视图之间的信息。从视图角度看,代理模型是跟普通模型表现一样的组件,可以存取代表该视图的源模型的数据。不管有多少个代理模型放在视图和源模型之间,模型/视图框架使用的信号和槽会确保每一个视图都能及时的更新。

1、代理模型使用

    代理模型可以安插在一个现有的模型和任意数量的视图之间。Qt提供了一个标准的代理模型,QSortFilterProxyModel,它通常被实例化就可直接使用,但它也可以子类化以提供自定义的筛选和排序行为。QSortFilterProxyModel可以以下面的方式使用:

  QSortFilterProxyModel *filterModel = new QSortFilterProxyModel(parent);
     filterModel->setSourceModel(stringListModel);
     QListView *filteredView = new QListView;
     filteredView->setModel(filterModel);

    由于代理模型是从QAbstractItemModel继承而来的,所以它可以连接到各种视图,并共用于视图间。他们也可以通过一个管道途径的安排,用来处理从其它代理模型得到的信息。
    QSortFilterProxyModel类被设计为可实例化并直接在程序中使用。子类化这个类并实现所要求的比较操作,可以创建更多自定义的代理模型。

2、自定义代理模型

    通常情况下,代理模型所使用的处理形式涉及将每个项的数据从他的源模型原始位置映射到代理模型中的任意一个不同的位置。在有些模型中,一些项可能在代理模 型中没有对应的位置;这些模型就是筛选代理模型。视图使用代理模型提供的模型索引存取项,以及那些在这个模型中没有包含源模型信息或源项位置的项。

    QSortFilterProxyModel 使得源模型的数据在提供给视图之前可以被筛选,同时允许源模型的数据以排好序的数据提供给视图。

3、自定义筛选模型

    QSortFilterProxyModel类提供了一个相当通用多变的筛选模型,可以用于各种常见得情况。对于高级使用者,可以子类化QSortFilterProxyModel来提供一个能够执行自定义筛选的机制。

    子类化QSortFilterProxyModel可以重新实现两个虚函数,当请求或使用代理模型的模型索引时要调用这两个函数:

filterAcceptsColumn()函数用于筛选源模型中指定的列

filterAcceptsRow()函数用于筛选源模型中指定的行

    以上两个QSortFilterProxyModel函数的默认实现返回true,以确保所有的项都可以传递给视图;重新实现这些函数应该返回false以筛选出单独的行和列。

4、自定义排序模型

    QSortFilterProxyModel 实例使用Qt内建的qStableSort()函数来建立源模型项和代理模型项之间的映射,在不改变源模型结构的情况下将一个排序后的项显示到视图上。为了提供自定义的排序行为,就要重新实现 lessThan()函数以执行自定义的比较。

5、自定义数据模型

    QIdentityProxyModel实例不会排序和过滤源模型的结构,但提供了一个数据代理的基类。在QFileSystemModel外,这对于不同文件的背景角色提供不同的颜色可能是有用的。

十、模型的子类化参照

模型子类化需要提供QAbstractItemModel基类定义的多个虚函数的实现。需要实现的函数数量取决于模型的类型,比如是否提供简单列表、表格、复杂层次项的视图。继承自QAbstractListModelQAbstractTableModel的模型能够利用这两个基类默认的函数实现。以类树形结构显示数据项的模型必须提供QAbstractItemModel的多个函数实现。

在子类模型中需要实现的函数分为三组:

A、项数据处理:需要实现函数的所有模型需要使视图和委托能够查询模型的维度、检查项和获取数据

B、导航和索引创建:多层次模型需要提供视图能够导航显示树形结构的函数,并且获取项的模型索引

C、拖放支持和MIME类型处理:模型需要继承内外拖放操作控制方式的函数。这些函数允许其他组件和应用程序能够理解的MIME方式进行描述数据。

1、项数据处理

模型能够提供多种层次的数据访问方式,比如简单的只读组件、重绘操作支持、可编辑。

A、只读访问

    为了提供对模型数据的只读访问,必须对模型子类的以下函数重新实现。

    flags()用于其他组件获取模型的每个项信息。在大多数模型中,需要使用Qt::ItemIsEnabledQt::ItemIsSelectable的组合。

    data()用于提供视图和委托项数据。通常,模型只需要提供Qt::DisplayRole和任何应用程序用户指定的角色的数据,但提供Qt::ToolTipRoleQt::AccessibleTextRoleQt::AccessibleDescriptionRole角色的数据也是很好的练习。关于每个角色关联的类型信息在文档中查阅Qt::ItemDataRole枚举类型。

    headerData()为了显示表头,提供带信息的视图。这些信息只能由可以显示表头的视图获取。

    rowCount()提供模型中要显示的数据的行数。

    这四个函数必须在所有模型类型中实现,包括QAbstractListModel的子类列表模型和QAbstractTableModel的子类表格模型。

    此外,以下函数必须在QAbstractTableModelQAbstractItemModel的直接子类中实现。

    columnCount()提供模型要显示的数据的列数。列表模型由于已经在QAbstractListModel中实现了,所以不需要提供这个函数。

2、可编辑项

可编辑模型允许数据项可以修改,提供插入和删除行和列的函数。为了开启可编辑,下面的函数需要正确实现。

    flags()必须返回每个项相近的标识组合。函数的返回值必须包含Qt::ItemIsEditable标识。

    setData()用于修改由模型索引指定的数据项。为了接受用户输入,这个函数需要处理和Qt::EditRole有关的数据。函数实现需要接受由Qt::ItemDataRole指定的多种角色关联的数据。在数据项改变后,模型必须发送dataChanged()信号通知变化的其他组件。

    setHeaderData()用于修改水平和垂直的表头信息。数据项改变后,模型必须发送headerDataChanged()通知变化的其他组件。

3、重绘模型

所有模型支持插入和删除行,表格模型和层次模型支持插入和删除列。在模型的维度变化前后通知其他组件是重要的。以下函数的实现是为了允许模型重绘。但函数实现必须确保合适的函数被调用通知相关的视图和委托。

    insertRows()用于在所有的模型中插入行和数据项。函数实现在插入新行到任何底层数据结构前必须调用beginInsertRows()函数,在插入行后必须立即调用endInsertRows()函数。

    removeRows()用于删除所有模型中包含的行和数据项。函数实现中在插入新的列到底层数据结构前需要调用beginRemoveRows()函数,插入新列后必须立即调用endRemoveRows()函数。

    insertColumns()用于在表格模型和层次模型中添加新的行和数据项。函数实现中在从任何底层数据结构中删除行之前需要先调用beginInsertColumns()函数,删除后必须立即调用endInsertColumns()函数。

    removeColumns()用于删除表格模型和层次模型中的列和数据项。函数实现中在从任何底层数据结构中删除列前必须调用beginRemoveColumns()函数,删除后必须立即调用endRemoveColumns()函数。

    通常,如果操作成功,这些函数应该返回true。但是,有一些操作部分成功的情况,比如插入的行数少于指定的插入行数。在这样的情况下,模型应该返回alse指示失败,使其他相关组件处理这种情况。

    在调用重绘API函数中发送的信号会给相关组件在数据不可用前采取措施的机会。开始和结束函数中对插入和删除的封装使模型能够正确管理永久模型索引。

    通常,开始和结束函数能通知其他组件有关模型底层结构的变化。对于模型结构的复杂变化,可能会涉及到内部数据的重组或排序,发送layoutChanged()信号引起所有相关的视图更新是必须的。

4、模型数据的惰性填充

    模型数据的惰性填充允许模型的信息请求延缓到实际视图需要时。

    有些模型需要从远程数据源获取数据,或是必须执行耗时的操作为了获取数据组织方式的信息。由于视图通常要求尽可能多的信息为了精确显示模型数据,为了减少不必要的重复数据请求,限制返回信息的数量是有用的。

    在层次模型找到所给项的孩子的数量是代价昂贵的操作,确保模型的rowCount()实现只在需要时调用是有用的。本例中,hasChildren()函数需要一种不昂贵的方式重新实现,QTreeView,为父项绘制适合的外观。

    hasChildren()函数重新实现是返回true还是false,为了弄清有多少个孩子显示而调用rowCount()函数对于视图来说并不需要。例如,如果父项没有扩展显示子项,QTreeView不必清楚有多少子项。

    如果知道有多少项有子项,重新实现hasChildren()函数中无条件返回true有时是可用的方法。当尽可能快地做模型数据的填充时,这可以确保每个项随后可以检查子项。缺点是在用户试图显示不存在的子项时,没有子项的项在有些视图中不能正确显示。

5、导航和模型索引创建

    为了导航显示的树形结构和获取项的模型索引,层次模型需要提供视图能够调用的函数。

    显示到视图的结构由底层数据结构决定,每个模型子类由通过提供以下函数的实现创建自身的模型索引决定。

    index() 通过父项的模型索引,函数允许视图和委托访问项的子项。如果没有合法的子项能被找到,函数必须返回QModelIndex()无效的模型索引。

    parent()提供给定子项的父项的模型索引,如果模型索引指定的是模型中的顶层项,或是模型中没有合法的父项,函数必须返回一个无效的模型索引。

6、拖放支持和MIME类型处理

    model/view类支持拖放操作,对于很多程序来说是提供有效的默认行为。然而,也可能是定义在拖放操作间项被编码的方式,他们默认是被拷贝还是移动,还是如何插入一个存在的模型中。

0 0