Qt Model/View 模型视图框架

1. Model/View基本原理

Qt包含一组项视图类,这些类使用Model/View体系结构来管理数据之间的关系以及数据呈现给用户的方式。此体系结构引入的功能分离为开发人员提供了更大的灵活性来定制item的表示,并提供了标准的模型接口,以允许与现有项视图一起使用广泛的数据源。
GUI应用程序的一个很重要的功能是由用户在界面上编辑和修改数据,典型的如数据库应用程序,数据库应用程序中,用户在界面上执行各种操作,实际上是修改了界面组件所关联的数据库内的数据。
将界面组件与所编辑的数据分离开来,又通过数据源的方式连接起来,是处理界面与数据的一种较好的方式。Qt 使用 Model/View 结构来处理这种关系,Model/View的基本结构如图所示。
中文版
其中各部分的功能如下:

  • 数据(Data)是实际的数据,如数据库的一个数据表或 SQL查询结果,内存中的一个StringList,或磁盘文件结构等。
  • 视图或视图组件(View)是屏幕上的界面组件,视图从数据模型获得每个数据项的模型索引(model index),通过模型索引获取数据,然后为界面组件提供显示数据,Qt提供一些现成的数据视图组件,如QListView、QTreeView 和QTableView等。
  • 模型或数据模型(Model)与实际数据通信,并为视图组件提供数据接口。它从原始数据提取需要的内容,用于视图组件进行显示和编辑。Qt中有一些预定义的数据模型,如QStringListModel
    可作为StringList的数据模型,QSqlTableModel 可以作为数据库中一个数据表的数据模型。

模型(Model)与数据源(Data)通信,为体系结构中的其他组件提供接口。通信的性质取决于数据源的类型和模型的实现方式。
视图(View)从模型(Model)中获取模型索引;这些是对数据项的引用。通过向模型提供模型索引,视图可以从数据源检索数据项。
在标准视图中,代理(delegate)渲染数据项。编辑项时,代理将直接使用模型索引(model index)与模型通信。

由于数据源与显示界面通过Model/View结构分离开来,因此可以将一个数据模型在不同的视图中显示,也可以在不修改数据模型的情况下,设计特殊的视图组件。

在Model/View结构中,还提供了代理(Delegate)功能,代理功能可以让用户定制数据的界面显示和编辑方式。在标准的视图组件中,代理功能显示一个数据,当数据被编辑时,代理通过模型索引与数据模型通信,并为编辑数据提供一个编辑器,一般是一个QLineEdit组件。
模型、视图和代理之间使用信号和槽通信。当源数据发生变化时,数据模型发射信号通知视图组件:当用户在界面上操作数据时,视图组件发射信号表示这些操作信息;当编辑数据时,代理发射信号告知数据模型和视图组件编辑器的状态。

2. 数据模型-Model

所有的基于项数据(item data)的数据模型(Model)都基于QAbstractltemModel类,此类定义了视图和代理访问数据的接口。数据本身不必存储在模型中;它可以保存在由单独的类、文件、数据库或其他应用程序组件提供的数据结构或存储库中。

QabstracteModel提供了一个数据接口,该接口足够灵活,可以处理以table、list和tree的形式表示数据的视图。但是,在为list和类似table的数据结构实现新模型时,QAbstractListModel和QAbstractTableModel类是更好的基类选择,因为它们提供了公共函数的适当默认实现。这些类中的每一个都可以被子类化,以提供支持特殊类型的list和table的模型。

Qt提供了一些现成的模型,可用于处理数据项:

  • QStringListModel 用于处理QString列表的数据模型类
  • QStandardItemModel 标准的基于项数据的数据模型类,管理更复杂的树结构,每个项数据内都可以包含任意类型的数据
  • QFileSystemModel 提供有关本地文件系统中的文件和目录的信息,文件系统模型类
  • QSqlQueryModel 用于数据库sql查询结果的数据模型类
  • QSqlTableModel 用于数据库中一个数据表的数据模型类
  • QSqlRelationalTableModel 用于关系型数据表的数据模型类
  • QSortFilterProxyModel 与其他数据模型结合,提供排序和过滤功能的数据模型类

如果这些标准模型不符合您的要求,您可以将QAbstractItemModel、QAbstractListModel或QAbstractTableModel子类化,以创建自己的自定义模型。

Qt中与数据模型相关的几个主要的类的层次结构如图所示:
在这里插入图片描述

3. 视图-View

视图(View)就是显示数据模型的数据的界面组件,Qt提供的视图组件如下:

  • QListView:用于显示单列的列表数据,适用于一维数据的操作。
  • QTreeView:用于显示树状结构数据,适用于树状结构数据的操作。
  • QTableView: 用于显示表格状数据,适用于二维表格型数据的操作。
  • QColumnView:用多个 QListView 显示树状层次结构,树状结构的一层用一个QListView 显示。
  • QHeaderView:提供行表头或列表头的视图组件,如QTableView的行表头和列表头。

视图组件在显示数据时,只需调用视图类的setModel()函数,为视图组件设置一个数据模型
就可以实现视图与数据模型之间的关联,在视图上的修改将自动保存到关联的数据模型
里,一个数据模型可以同时在多个视图中显示数据。
还有QListWidget,QTreeWidget 和 QtableWidget 3个可用于数据编辑的组件。这3个类称为便利类(convenience classes),它们分别是QListView、QTreeView、QTableView3个视图类的子类,
用于Model/View结构的几个视图类直接从QAbstractltemView继承而来,而便利类则从相应的视图类继承而来。
视图类的数据采用单独的数据模型,视图不存储数据。便利类则为视图的每个节点或单元格创建一个项(item),用项存储数据、格式设置等。所以,便利类没有数据模型,它实际上是用项的方式集成了数据模型的功能,这样就将界面与数据绑定了。
所以,便利类缺乏对大型数据源进行灵活处理的能力,适用于小型数据的显示和编辑。

4. 代理-Delegate

QAbstractItemDelegate类用于显示和编辑模型中的数据项。
QAbstracteMDelegate为Model/View体系结构中的代理提供接口和通用功能。代理在视图中显示各个项,并处理模型数据的编辑。
代理(Delegate)就是在视图组件上为编辑数据提供编辑器,如在表格组件中编辑某个单元格的数据时,缺省是使用一个QLineEdit编辑框,代理负责从数据模型获取相应的数据,然后显示在编辑器里,修改数据后,又将其保存到数据模型中。
QAbstractltemDelegate 是所有代理类的抽象基类,它不能直接使用。默认代理实现由QStyledItemDelegate提供,Qt的标准视图将其用作默认代理。QStyledItemDelegate和QItemDelegate的区别在于QStyledItemDelegate使用当前样式绘制项(item)。因此,我们建议在实现自定义委托或使用Qt样式表时使用QStyledItemDelegate作为基类。
对于一些特殊的数据编辑需求,例如只允许输入整型数,使用一个 QSpinBox 作为代理组件更恰当,从列表中选择数据时使用一个 QComhoBox 作为代理组件更好。这时,就可以从QStyledltemDelegate继承创建自定义代理类。

给出一个在item中绘制进度条的示例,创建WidgetDelegate类,它继承自QStyledItemDelegate。在paint()函数中进行绘图:

void WidgetDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option,
                             const QModelIndex &index) const
  {
      if (index.column() == 1) {
          int progress = index.data().toInt();

          QStyleOptionProgressBar progressBarOption;
          progressBarOption.rect = option.rect;
          progressBarOption.minimum = 0;
          progressBarOption.maximum = 100;
          progressBarOption.progress = progress;
          progressBarOption.text = QString::number(progress) + "%";
          progressBarOption.textVisible = true;

          QApplication::style()->drawControl(QStyle::CE_ProgressBar,
                                             &progressBarOption, painter);
      } else
          QStyledItemDelegate::paint(painter, option, index);

请注意,我们使用QStyleOptionProgressBar并初始化其成员。然后我们可以使用当前的QStyle来绘制它。
要提供自定义编辑,可以使用两种方法。第一种方法是创建一个编辑器小部件,并将其直接显示在项目顶部。为此,必须重新实现createEditor()以提供编辑器小部件,setEditorData()以使用模型中的数据填充编辑器,以及setModelData(),以便代理可以使用编辑器中的数据更新数据模型。
第二种方法是通过重新实现editorEvent()直接处理用户事件。

5. 基本概念

5.1 Model/View的基本结构

在 Model/View 结构中,数据模型为视图组件和代理提供存取数据的标准接口。在Qt中,所有的数据模型类都从 QAbstractltemModel 继承而来,不管底层的数据结构是如何组织数据的,QAbstracttemModel 的子类都以表格的层次结构表示数据,视图组件通过这种规则来存取模型中的数据,但它们向用户显示数据的方式不受限制。
不管数据模型的表现形式是怎么样的,数据模型中存储数据的基本单元都是项(item),每个项有一个行号、一个列号,还有一个父项(parent item)。
在列表和表格模式下,所有的项都有一个相同的顶层项(root item);在树状结构中,行号、列号、
父项稍微复杂一点,但是由这3个参数完全可以定义一个项的位置,从而存取项的数据。
在这里插入图片描述

5.2 模型索引-model index

为了确保数据的表示与访问方式分开,引入了模型索引的概念。可以通过model获得的每一条信息都由模型索引表示。视图和代理使用这些索引请求要显示的数据项。

因此,只有模型需要知道如何获取数据,并且可以相当普遍地定义模型管理的数据类型。模型索引包含指向创建它们的模型的指针,这可以防止在处理多个模型时出现混淆。

模型索引提供对信息片段的临时引用,并可用于通过模型检索或修改数据。由于模型可能会不时地重新组织其内部结构,因此模型索引可能会变得无效,不应存储。如果需要对一条信息进行长期引用,则必须创建持久模型索引。这提供了对模型保持最新的信息的引用。临时模型索引由QModelIndex类提供,持久模型索引由QPersistentModelIndex类提供。

要获取与数据项对应的模型索引,必须为模型指定三个属性:行号、列号和父项的模型索引。
以下各节详细描述和解释这些属性。

5.2.1 Rows and columns

在其最基本的形式中,模型可以作为一个简单的表来访问,其中的项通过它们的行号和列号来定位。这并不意味着底层数据段存储在数组结构中;使用行号和列号只是允许组件相互通信的约定。我们可以通过向模型指定任何给定项的行号和列号来检索有关该项的信息,并收到一个表示该项的索引

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()作为其父项来引用。

5.2.2 Parents of items

当在table或list中使用数据时,模型为item data 提供的类表格接口是理想的;行和列编号可以精确映射到视图显示的项。但是,treeview等结构要求模型向其中的item提供更灵活的接口。因此,每个项也可以是另一个项的父项,这与树视图中的顶层项可以包含另一个项列表的方式非常相似。
当请求模型项的索引时,我们必须提供有关该项父项的一些信息。在模型之外,引用项的唯一方法是通过模型索引,因此还必须提供父模型索引:

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);
5.2.3 Item roles

模型中的项可以为其他组件表示各种角色,允许为不同情况提供不同类型的数据。例如,Qt::DisplayRole用于访问可在视图中显示为文本的字符串。通常,项包含许多不同角色的数据,标准角色由Qt::ItemDataRole定义。
我们可以通过向模型传递与项对应的模型索引,并通过指定角色来获取所需的数据类型,向模型请求项的数据:

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

在这里插入图片描述

6. 总结

  • 模型索引以独立于任何底层数据结构的方式提供项的视图和委托信息。
  • 项通过其行和列编号以及其父项的模型索引进行引用。
  • 模型索引由模型根据其他组件(如视图和代理)的请求构建。
  • 如果在使用index()请求索引时为父项指定了有效的模型索引,则返回的索引将引用模型中该父项下的项。获得的索引引用该项的子项。
  • 如果在使用index()请求索引时为父项指定了无效的模型索引,则返回的索引将引用模型中的顶级项。
  • 角色区分与项目关联的不同类型的数据。

来源:Qt Model/View官方文档

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值