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

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

    本文翻译自QT官方文档QT 4.8 Model/View Programming数据库

1、Model/View框架简介

    Qt4推出了一组新的项视图类,使用Model/View框架来管理数据与表示层的关系。Model/View框架带来的功能上的分离给了开发人员更大的弹性来定制数据项的表示,而且提供一个标准的model接口,使得更多的数据源能够被项视图类使用。本文简要介绍了Model/View架构,对涉及的概念作了简单的概述,阐述了项视图系统。架构中的每个组件都将一一做出解释,同时将用实例对如何使用这些类进行说明。编程

一、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之间经过信号-槽机制来进行通信:app

    从Model发出的信号通知View关于数据源中的数据发生的改变。
    从View发出的信号提供了有关被显示的数据项与用户交互的信息。
    从Delegate发射的信号被用于在编辑时通知Model和View关于当前编辑器的状态信息。
框架

    Model/View框架中,全部模型类具备共同的抽象基类QAbstractItemModel,全部视图类具备共同的抽象基类QAbstractItemView,全部委托类具备共同的抽象基类QabstractItemDelegate。less

二、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等搭配使用。

七、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

2、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

二、模型索引

    为了确保数据显示与数据访问分离,引入了模型索引的概念。经过Model获取的数据项能够经过模型索引显示。Views和Delegates都使用索引来访问数据项,而后显示出来。所以,只有Model须要了解如何获取数据,而且Model管理的数据类型能够定义地很是普遍。模型索引包含一个指向建立它们的Model的指针,这样能够避免使用多个Model时引发混淆
 QAbstractItemModel *model = index.model();

    模型索引为信息块提供了临时参照,经过它能够用来提取或修改Model中的数据。因为Model常常会从新组织内部的结构,使得模型索引失效,所以不该保存模型索引。若是须要一个对信息块的长期参照,必须建立一个永久的模型索引。这样会为不断更新的Model信息提供一个参照。临时模型索引由QModelIndex类提供,而永久模型索引则由QPersistentModelIndex类提供。

    为了获取相应数据项的模型索引,必须指定Model的三个属性:行数,列数,父项的模型索引。

三、行数和列数

    在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()函数来做为他们的父项参照

四、父项

    当在一个表格或列表视图中使用数据时,模型提供的表格型数据接口是较为理想的。行列号系统正好映射到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);

五、数据项的角色

    Model中的项能够为其余部件扮演不一样的角色,容许为不一样的情形提供各类不一样的数据。例如,Qt::DisplayRole就是用于存取一个能够在视图中以文字显示的字符串。一般状况下,项包含各类不一样的角色的数据,标准的角色由Qt::ItemDataRole定义。经过传递对应项的模型索引给Model,并指定一个角色以取得咱们想要的数据的类型,咱们就能够从Model中取得项的数据:

QVariant value = model->data(index, role);

wKioL1hCX8yhWbt9AACX8lStoK4767.png

    角色为Model指明哪种数据被参照。视图以不一样的方式显示角色,因此为每一种角色提供合适的信息是很重要的。Qt::ItemDataRole里定义的标准角色涵盖了项数据最经常使用的用途。经过为每一个角色提供合适的项数据,Model就能够为视图和委托提供关于怎样向用户显示项的提示。各类不一样的视图都有根据须要中断或忽略这些信息的自由,同时也能够为特定的程序要求定义另外的角色。

六、总结

    模型索引以一种独立于任何底层数据结构以外的方法,为视图和委托提供关于模型中项的定位信息。

    项以他们的行号和列号,以及他们的父项的模型索引做为参照

    模型索引应其余部件如视图和委托的要求由模型来构造。

    当使用index()函数请求一个索引时,若是指定一个有效的模型索引做为父项索引,则返回的索引指向模型里这个父项下面的一个项。这个索引得到该项的子项的一个参照。

    当使用index()函数请求一个索引时,若是指定一个无效的模型索引为父项索引,则返回的索引指向模型里的一个顶级项。

    角色识别一个项中的各类不一样类型的相关数据。

七、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提供的标准接口能够建立新的模型。

八、自定义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()

返回表头信息。

3、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的默认行为足以应付大多的应用程序,它们提供了基本的编辑功能,也能够定制特殊的需求。

三、单个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

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

4、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这个例子演示了如何创建一个专门用于表格视图的自定义委托。

二、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,他们跟程序的整合性就会下降,所起的做用也很小。

5、项视图的选择处理

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

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

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

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

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

    当前项与选中项

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

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

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

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

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

    在处理选择时,考虑使用QItemSelectionModel来记录一个项模型中全部项的选择状态是颇有帮助的。只要创建一个选择模型,项的集合就能够选择,撤销选择,或者反向选择,而不须要知道哪些项已经被选择。任什么时候候均可以提取全部被选择项的索引,同时经过信号和槽机制,其余的部件也能够知道选择 模型的变更。

二、选择模型的使用

    标准视图类提供了大多数应用程序均可以使用的默认选择模型。一个视图所使用的选择模型能够经过视图的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() 函数能够肯定一个指定的项是不是另外一层次项的父项。

6、自定义Model

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

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

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

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

一、模型设计

    当为一个现有的数据类型建立一个新的模型时,考虑哪种类型的模型适合于为数据提供接口是很重要的。若是数据结构能够用一个列表或表格的项来表示,能够子类化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的要求,同时为那些必须实现的虚函数提供指引,以便在不一样类型的模型中赋予各类特性。

7、项视图便利类

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

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

一、列表组件

    单层次列表的项用一个 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);

二、树形组件

    树形或层次结构型的列表项由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());

三、表格组件

    表格部件跟电子表单程序里用到的类似,都是用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]
  }

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

8、项视图的拖放

    模型/视图框架彻底支持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()函数的实现。

9、代理模型

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

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

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

一、代理模型使用

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

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

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

二、自定义代理模型

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

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

3、自定义筛选模型

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

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

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

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

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

4、自定义排序模型

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

5、自定义数据模型

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

10、模型的子类化参照

模型子类化须要提供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中实现了,因此不须要提供这个函数。

二、可编辑项

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

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

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

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

三、重绘模型

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

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

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

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

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

    一般,若是操做成功,这些函数应该返回true。可是,有一些操做部分红功的状况,好比插入的行数少于指定的插入行数。在这样的状况下,模型应该返回alse指示失败,使其余相关组件处理这种状况。

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

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

四、模型数据的惰性填充

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

    有些模型须要从远程数据源获取数据,或是必须执行耗时的操做为了获取数据组织方式的信息。因为视图一般要求尽量多的信息为了精确显示模型数据,为了减小没必要要的重复数据请求,限制返回信息的数量是有用的。

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

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

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

五、导航和模型索引建立

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

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

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

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

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

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

相关文章
相关标签/搜索