文章目录
- Qt 模型视图(一):概述
- 1、模型/视图结构基本原理
- 2、模型
- 3、视图
- 4、代理
- 5、简单实例
Qt 模型视图(一):概述
模型/视图结构是一种将数据存储和界面展示分离的编程方法。模型存储数据,视图组件显示模型中的数据,在视图组件里修改的数据会被自动保存到模型里。模型的数据来源可以是内存中的字符串列表或二维表格型数据,也可以是数据库中的数据表,一种模型可以用不同的视图组件来显示数据,所以模型/视图结构是一种高效、灵活的编程结构。
1、模型/视图结构基本原理
GUI 程序的主要功能是可由用户在界面上编辑和修改数据,典型的如数据库应用程序。在数据库应用程序中,界面上的数据来源于数据库,用户在界面上修改数据,修改后的数据又保存到数据库。
将界面与原始数据分离,又通过模型将界面和原始数据关联起来,从而实现界面与原始数据的交互操作,这是处理界面与数据的一种较好的方式。
Qt 使用模型/视图结构来处理这种关系,模型/视图的基本结构如图 1 所示,它包括以下几个部分。
图 1 模型/视图基本结构 |
---|
- 源数据(data)是原始数据,如数据库的一个数据表或
SQL
查询结果、内存中的一个字符串列表或磁盘文件系统结构等。 - 视图(view)也称为视图组件,是界面组件,视图从模型获得数据然后将其显示在界面上。Qt 提供一些常用的视图组件,如
QListView
、QTreeView
和QTableView
等。 - 模型(model)也称为数据模型,与源数据通信,并为视图组件提供数据接口。它从源数据提取需要的数据,用于视图组件进行显示和编辑。Qt 中有一些预定义的模型类,如
QStringListModel
是字符串列表的模型类,QSqlTableModel
是数据库中数据表的模型类。 - 代理(delegate)在视图与模型之间交互操作时提供的临时编辑器。模型向视图提供数据是单向的,一般仅用于显示。当需要在视图上编辑数据时,代理会为编辑数据提供一个编辑器,这个编辑器获取模型的数据、接受用户编辑的数据后又将其提交给模型。例如在
QTableView
组件上双击一个单元格来编辑数据时,在单元格里就会出现一个QLineEdit
组件,这个编辑框就是代理提供的临时编辑器。
由于通过模型/视图结构将源数据与显示和编辑界面分离,我们可以将一个模型在不同的视图中显示,也可以为一些特殊源数据设计自定义模型,或者在不修改模型的情况下设计特殊的视图组件。所以,模型/视图结构是一种高效、灵活的编程结构。
模型、视图和代理使用信号和槽进行通信。当源数据发生变化时,模型发射信号通知视图组件;当用户在界面上操作数据时,视图组件发射信号表示操作信息;在编辑数据时,代理会发射信号告知模型和视图组件编辑器的状态。
2、模型
所有基于项(item)的模型类都是基于 QAbstractItemModel
类的,这个类定义了视图组件和代理存取数据的接口。模型只是在内存中临时存储数据,模型的数据来源可以是其他类、文件、数据库或任何数据源。
Qt 中几个主要的模型类的继承关系如图 2 所示。QAbstractItemModel
的父类是 QObject
,所以模型类支持 Qt 的元对象系统。
图 2 模型类的继承关系 |
---|
抽象模型类 QAbstractItemModel
不能直接用于创建实例对象,常用的几个模型类如表 1 所示。
表 1 常用的模型类 |
---|
模型类 | 功能 |
---|---|
QFileSystemModel | 用于表示计算机上文件系统的模型类 |
QStringListModel | 用于表示字符串列表数据的模型类 |
QStandardItemModel | 标准的基于项的模型类,每个项是一个 QStandardItem 对象 |
QSqlQueryModel | 用于表示数据库 SQL 查询结果的模型类 |
QSqlTableModel | 用于表示数据库的一个数据表的模型类 |
3、视图
视图就是用于显示模型中的数据的界面组件,Qt 提供的视图组件主要有以下几个。
表 2 常用的视图类 |
---|
视图组件 | 用于显示单列的列表数据,适用于一维数据的操作 |
---|---|
QListView | 用于显示单列的列表数据,适用于一维数据的操作 |
QTreeView | 用于显示树状结构数据,适用于树状结构数据的操作 |
QTableView | 用于显示表格数据,适用于二维表格数据的操作 |
QColumnView | 用多个 QListView 显示树状结构数据,树状结构的一层用一个QListView 显示 |
QUndoView | 用于显示 undo 指令栈内数据的视图组件,是 QListView 的子类 |
QListWidget
、QTreeWidget
和 QTableWidget
这 3 个用于处理项数据的组件。这3 个类分别是 3 个视图类的子类,称为视图类的便利类(convenience class)。这些类的继承关系如图 3 所示。
图 3 视图类的继承关系 |
---|
只需调用视图类的 setModel()
函数为视图组件设置一个模型,模型的数据就可以显示在视图组件上。在视图组件上修改数据后,数据可以自动保存到模型里。视图组件的数据来源于模型,视图组件不存储数据。便利类则为组件的每个节点或单元格创建一个项,用项存储数据,例如对于 QTableWidget
类这个便利类,表格的每个单元格关联一个QTableWidgetItem
对象。便利类没有模型,它实际上是用项的方式替代了模型的功能,将界面与数据绑定。因此,便利类缺乏对大型数据源进行灵活处理的能力,只适用于小型数据的显示和编辑,而视图组件则会根据模型的数据内容自动显示,有助于减少编程工作量,使用起来也更灵活。
4、代理
代理就是在视图组件上为编辑数据提供的临时编辑器,例如在 QTableView
组件上编辑一个单元格的数据时,默认会提供一个 QLineEdit
编辑框。代理负责从模型获取相应的数据,然后将其显示在编辑器里,修改数据后又将编辑器里的数据保存到模型中。
QAbstractItemDelegate
是所有代理类的基类。它有两个子类, QItemDelegate 和QStyledItemDelegate
,这两个类的功能基本相同,而QStyledItemDelegate
能使用 Qt 样式表定义的当前样式绘制代理组件,所以,QStyledItemDelegate
是视图组件使用的默认的代理类。
对于一些特殊的数据编辑需求,例如只允许输入整数时使用 QSpinBox
作为代理组件更合适,需要从列表中选择数据时则使用 QComboBox
作为代理组件更好,这时就可以从 QStyledItemDelegate
继承创建自定义代理类。
5、简单实例
展示了模型/视图类使用的简单实例 main.cpp
#include <QApplication>
#include <QSplitter>
#include <QFileSystemModel>
#include <QTreeView>
#include <QListView>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();
}
实例运行展示如下图