Qt中包含了一系列的项视图类,它们使用model/view的架构去管理数据之间的关系以及它们被展示给用户的方式。这种由这种架构引进的功能分离特性给了开发者很大的灵活性去自定义自己的展示方式,并且提供了一个编制的模型接口以使很多种的数据源都可以和现存的视图相结合。那么,今天我们就来简单的看下这种model/view范例,相关的概念,并简要描述一下项视图系统的架构。
模型/视图 架构
Model-View-Controller(MVC)是一种起源于Smalltalk编程语言的设计模式,主要被用来构建用户界面。对于这种设计模式,Gamma et al 写道:
MVC由3中对象组成。模型(Model)是应用程序的数据,视图(View)是它的屏幕展示,控制器(Controller)则定义了用户界面响应用户输入的方式。在MVC之前,用户界面的设 计倾向于将这些东西杂揉到一起。MVC则通过对它们进行解耦来提高开发的灵活性和组件的复用性。
如果视图和控制器被组合在了一起,结果就是model/view架构了。这仍然区分了数据的存储方式和它被展示的方式,不过是基于相同的原则提供了一个更简单的框架。这种分离使我们有可能用多种不同的视图来展示同一种数据,并且可以在不改变底层数据结构的情况下实现新的视图类型。而为了灵活的处理用户输入,我们又引入了代理(Delegate)的概念。在model/view框架中引入代理的好处是允许数据项的渲染和编辑可以被自定义。所以,传统的MVC在Qt中就变成了MVD,其工作原理图如下:
其中,模型和数据源进行交互,为架构中的其他组件提供一个接口。当然,交互的本质取决于数据源的类型和模型被实现的方式;视图从模型中获得模型下标(model indexes),这些下标是对数据项的引用。通过为模型应用模型下标,视图就可以从数据源中获得数据项。在标志视图下,会有一个代理来渲染这些数据项。当数据项被编辑时,代理又会使用模型下标直接和模型进行交互。
通常情况下,model/view相关的类可以分为三组:模型,视图和代理。这些组件中的每一个都有相关的抽象类来定义,以此来提供一些通用的接口,并在某些情况下,提供一些特性的默认实现。这些抽象类可以被子类化以为其他组件提供完全的功能支持,也可以借此实现一些特定的组件。
模型,视图和代理彼此使用信号和槽进行通信:
- 来自模型的信号会通知视图关于数据源中数据的改变
- 来自视图的信号提供了用户和视图项发生交互的信息
- 来自代理的信号被用于在编辑过程中告知模型和视图当前编辑器的状态。
- QStringListModel:被用来存储一个QString项的简单列表。
- QStanderItemModel:可以用来管理更复杂的树型数据结构,每一个数据项可以包含任意数据。
- QFileSystemModel:提供本地文件系统中文件和目录的信息。
- QSqlQueryModel,QSqlTabelModel和QSqlRelationalTableModel 被用作访问数据库的方便方法。
int main(int argc, char *argv[])
{
QApplication app(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 app.exec();
}
我们实例化了一个QFileSystemModel以便使用,并创建了相应的视图来展示一个目录下的内容。这是使用模型的简单的方式。该模型被初始化为使用一个文件系统的数据。调用setRootPath()告诉模型要将文件系统上的哪一个驱动器展示到视图中。我们在此创建了两个视图以便于用两种方式测试存储在模型中的数据。
QAbstractItemModel *model = index.model();
模型下标提供了对信息片段的临时引用,可以被用来通过模型去获取或修改数据。因为模型随时都有可能重新组织它们的内部结构,模型下标可能会失效,所以不应该存储它们到一个变量中。如果需要对某种信息的长期引用,必须创建一个持久性的模型下标。这种下标会提供一个对模型信息的引用,并保持更新。临时模型下标由QModelIndex类表示,持久性的模型下标由QPersistentModelIndex类表示。
QModelIndex index = model->index(row, column, ...);
对于那些为简单、单级数据结构如列表和表格提供接口的模型来说,在访问一个数据项时,除了行号和列号,不需要提供其他的信息。但是,像上面的代码显示的,我们需要提供其他的信息才能获得一个模型下标。
上图表示了一个基本的表格模型,其中的每一项由一个行和列来确定。我们通过传递相对于该模型的行和列来得到一个引用某个数据项的模型下标。
QModelIndex indexA = model->index(0, 0, QModelIndex());
QModelIndex indexB = model->index(1, 1, QModelIndex());
QModelIndex indexC = model->index(2, 1, QModelIndex());
对应模型中的顶层项来说,我们总是传入一个QModelIndex()来作为它们的父项。我们会在后面的部分继续讨论这个问题。
QModelIndex index = model->index(row, column, parent);
上图展示了一个树型视图,其中的每一项都由一个父项,一个行号,一个列号来确定。
QModelIndex indexA = model->index(0, 0, QModelIndex());
QModelIndex indexC = model->index(2, 1, QModelIndex());
项"A"有一系列的孩子。其中,"B"的模型下标可以用下面的方式获得:
QModelIndex indexB = model->index(1, 0, indexA);
QVariant value = model->data(index, role);
对于大部分常见用途的项数据,Qt::ItemDataRole类型的定义中均已包括。通过为每一种角色提供合适的项数据,模型可以为视图和代理提供提示该如果向用户展示该项。不同种类的视图均可以接受或忽略这些需要的信息。当然,也可以为特定的应用程序目的定义特定的角色。
自此,对于模型类的使用,我们可以进行一下总结:
- 模型下标以独立于底层数据结构的方式向视图和代理提供了模型中的数据项的信息。
- 项是由它们在模型中的行号和列号,以及它们的父项所指定的。
- 模型下标在其他组件,如视图和代理,发出请求时被模型创建出来。
- 当我们使用index() 函数请求一个下标时,若传递了一个有效的模型下标做为父项,那么返回的下标引用了一个在模型中位于父项下面的某项。这个下标引用着那个项的一个孩子。
- 若使用index() 函数时,传入了一个无效的模型下标做为父项,那么返回的下标引用着模型中的一个顶层项
- 角色区分了一个项所关联的不同数据。
QFileSystemModel *model = new QFileSystemModel;
QModelIndex parentIndex = model->index(QDir::current