Qt Model/View编程介绍

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相关的类可以分为三组:模型,视图和代理。这些组件中的每一个都有相关的抽象类来定义,以此来提供一些通用的接口,并在某些情况下,提供一些特性的默认实现。这些抽象类可以被子类化以为其他组件提供完全的功能支持,也可以借此实现一些特定的组件。

模型,视图和代理彼此使用信号和槽进行通信:

  • 来自模型的信号会通知视图关于数据源中数据的改变
  • 来自视图的信号提供了用户和视图项发生交互的信息
  • 来自代理的信号被用于在编辑过程中告知模型和视图当前编辑器的状态。
模型
Qt中所有的模型都是基于QAbstractItemModel类的。这个类为视图和代理访问数据定义了一个接口。数据本身并不比存储在模型中;数据可以存储在一个数据结构中,或由其他类所提供的一个仓库中,文件中,数据库中,或其他应用程序组件。
QAbstractItemModel提供了一个对于数据的足够灵活的接口,可以应对各种形式的视图,比如表格,列表和树状列表。当时,当为列表或类表格数据结构实现新的模型时,QAbstractListModel和QAbstractTableModel类是一个很好的开始点,因为它们为大部分基础功能提供了默认实现。这些类中的每一个都可以被实例化来为特殊种类的列表和表格提供模型。
另外,Qt中还提供了一些现成的模型供我们使用:
  • QStringListModel:被用来存储一个QString项的简单列表。
  • QStanderItemModel:可以用来管理更复杂的树型数据结构,每一个数据项可以包含任意数据。
  • QFileSystemModel:提供本地文件系统中文件和目录的信息。
  • QSqlQueryModel,QSqlTabelModel和QSqlRelationalTableModel 被用作访问数据库的方便方法。
如果这些标准的模型不能满足我们实际的需求,我们还可以子类化QAbstractItemModel,QAbstractListModel和QAbstractTableModel来创建我们自己的模型。

视图
Qt中为不同种类的视图都提供了完整的实现:QListView显示一个数据项的列表,QTableView在一个表格中展示模型中的数据,QTreeView在一个有层级的列表中展示模型中的数据项。这些类都是基于QAbstractItemView抽象类。虽然这些类都是Qt提供的可以直接使用的类,但我们也可以子类化它们来实现一些自定义视图。

代理
QAbstractItemDelegate是模型/视图框架中的代理的基类。默认的代理实现由QStyledItemDelegate类提供,并且它被用作Qt的标准视图的代理。但是,对于视图中的项的绘制和编辑,QStyledItemDelegate 和 QItemDelegate是两个独立的可选方案。而它们之间的区别是QStyleItemDelegate类使用当前应用程序的样式来绘制每一项。因此,当实现自定义代理或和Qt的样式表配合使用时,我们推荐使用QStyledItemDelegate类作为我们的基类。

排序
在模型/视图中有两种排序的方式,至于选择哪一种取决于你的底层模型。
如果你的模型是可排序的,也就是说,如果它实现了QAbstractItemModel::sort()函数,那么QTableView和QTreeView都提供了相应的API允许你以编程的方式来对模型数据进行排序。此外,你还可以连接QHeaderView::sortIndicatorChanged()信号到QTableView::sortByColumn()槽函数上,或QTreeView::sortByColumn()槽函数上,来实现交互式的排序,即允许用户通过点击视图的表头还对模型数据进行排序。
如果你的模型没有需要的接口或者如果你想使用一个列表视图来展示你的数据,一个可选的方案是使用一个代理模型在视图展示数据之前对数据结构进行一个转换。关于这个方案,我们会在代理视图中详细讲解。

方便使用的类
其实,Qt库中还为我们提供了现成的控件类,这些类也是从Qt的标准视图类派生下来的,以供那些依赖模型视图的应用程序使用。但这些类一般不应该被子类化。
这些类包括 QListWidget,QTreeWidget和QTableWidget。
但这些类没有视图类灵活,也不能应用于任意的模型。所以,我们推荐你使用模型/视图的方式去处理基于项的数据.。除非你确实需要基于项的控件类。
如果你希望利用模型/视图这种方式的优点,又想使用基于项的控件类,可以考虑使用视图类,比如QListView,QTableView,QTreeView,结合QStandardItemModel。

下面我们就来详细看一看怎么在Qt中进行模型/视图编程。
Qt中的两种模型
QStandardItemModel和QFileSystemModel是Qt中提供的两种标准模型。QStandardItemModel是一个多用途的模型,可以被list,table和tree视图用来展示多种多样的数据结构。这个模型也持有数据项。QFileSystemModel模型主要是处理一个目录的人呢信息。因此,它不持有数据项本身,只是简单的展示本地文件系统中的文件和目录。
QFileSystemModel提供了一个现成的模型,并且经过简单的配置就可以使用现成的数据。使用这个模型,我们可以说明怎样为一个现成的视图设置模型,怎么使用模型下标操作模型中的数据。
QListView和QTreeView类是最适合使用QFileSystemModel模型的视图。下面的例子展示了在一个树型视图和一个列表视图中显示一个目录的内容。并且,这两个视图共享用户的选择操作,所以,每一个被选中的项会在两个视图中高亮显示。

其实现代码如下:
  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()告诉模型要将文件系统上的哪一个驱动器展示到视图中。我们在此创建了两个视图以便于用两种方式测试存储在模型中的数据。
由上面的代码可以看出,视图的创建方式和其他控件一样。要设置一个视图要显示的模型只需用要使用的模型作为参数调用视图类的setModel()。我们使用每一个视图的setRootIndex()来过滤模型提供的数据,在此我们为该函数传入了一个代表当前目录的的模型下标。上面所使用index()函数对应QFileSystemModel来说是唯一的;我们为它传入一个目录,它为我们返回一个模型下标。
至于这么处理视图中项的选择操作,我们会在后面的部分讲解。

模型类
在处理选择操作之前,我们先来看下模型/视图中的一些概念。
基本概念
在模型/视图架构中,模型为视图和代理提供了访问数据的标准接口。在Qt中,这些标准接口在QAbstractItemModel类中被定义。无论数据项存储在什么数据结构中,所有的QAbstractItemModel的子类都会把数据渲染为一个包含项目表的层级结构。视图使用这个约定的方式访问模型中的数据项,但它们也可以使用其他的方式来访问数据,并不局限于用和它们向用户展示数据的相同的方式。


同时,模型还会使用信号和槽向关联的视图通知数据的改变。

模型下标
为了确保数据的展示和数据的访问分开,便引入了模型下标的概念。通过模型可以获得的任何信息都是由一个模型下标表示的。视图和代理使用这些下标去请求要显示的数据项。
这样一来,只有模型需要知道怎么获取数据,并且被模型管理的数据的类型可以被定义的非常广泛。模型下标中包含一个指向创建它们的模型的指针,这在使用多个模型时可以避免混淆。
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);



上图展示了一个树型视图,其中的每一项都由一个父项,一个行号,一个列号来确定。
项 "A" 和 "C" 是顶层兄弟项,可以用下面的方式获得:
  QModelIndex indexA = model->index(0, 0, QModelIndex());
  QModelIndex indexC = model->index(2, 1, QModelIndex());
项"A"有一系列的孩子。其中,"B"的模型下标可以用下面的方式获得:
QModelIndex indexB = model->index(1, 0, indexA);

项的角色
在模型中的每一项对其他组件来说都可能扮演多种角色,以允许不同种类的数据被应用于不同的情况。例如,Qt::DisplayRole是用来访问一个可以在视图中按文本显示的字符串。典型的,数据项包含了不同角色的数据,标准的角色由Qt::ItemDataRole类型来定义。
我们可以向模型请求某个项的数据,通过向它传递该项的模型下标,以及我们想要的数据类型的角色:
 QVariant value = model->data(index, role);
对于大部分常见用途的项数据,Qt::ItemDataRole类型的定义中均已包括。通过为每一种角色提供合适的项数据,模型可以为视图和代理提供提示该如果向用户展示该项。不同种类的视图均可以接受或忽略这些需要的信息。当然,也可以为特定的应用程序目的定义特定的角色。

自此,对于模型类的使用,我们可以进行一下总结:
  • 模型下标以独立于底层数据结构的方式向视图和代理提供了模型中的数据项的信息。
  • 项是由它们在模型中的行号和列号,以及它们的父项所指定的。
  • 模型下标在其他组件,如视图和代理,发出请求时被模型创建出来。
  • 当我们使用index() 函数请求一个下标时,若传递了一个有效的模型下标做为父项,那么返回的下标引用了一个在模型中位于父项下面的某项。这个下标引用着那个项的一个孩子。
  • 若使用index() 函数时,传入了一个无效的模型下标做为父项,那么返回的下标引用着模型中的一个顶层项
  • 角色区分了一个项所关联的不同数据。
使用模型下标
为了说明如何使用模型下标从一个模型中获取数据,我们初始化了一个QFileSystemModel,但不使用相关的视图,而是把文件和目录的名字显示到一个控件上。虽然,这不是一个使用模型的常规方式,但它确实说明了模型处理模型下标时的一些约定。
我们以下面这种方式来构建一个文件系统模型:
      QFileSystemModel *model = new QFileSystemModel;
      QModelIndex parentIndex = model->index(QDir::current
  • 10
    点赞
  • 36
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值