qt模型/视图结构

目录

(一). 模型/视图结构概述

一. 模型/视图结构基本原理

二. 模型

三. 视图

四. 代理

五. 模型/视图结构的一些概念

1.模型的基本结构

2.模型索引

3.行号和列号

4.父项

5.项的角色

六. QAbstractItemModel 类

1.行数和列数

2.插入或删除行

3.插入或删除列

4.移动行或列

5.数据排序

6.设置和读取项的数据

7.清除一个项的数据

七. QAbstractItemView 类

1.关联数据模型和选择模型

2.常用属性

3.常用接口函数

4.常用信号

(二). QStringListModel 和 QListView

一. QStringListModel 类

二. 示例程序功能实现

1.窗口界面可视化设计

2.模型/视图结构初始化

3.数据模型的操作

4.视图组件相关的代码

5.在文本框中显示数据模型的内容

(三). QStandardItemModel 和 QTableView

一. QTableView 类

二. QStandardItemModel 类

1.设置行数和列数

2.设置项

3.获取项

4.添加行或列

5.插入行或列

6.移除行、列或项

7.水平表头和垂直表头

8.函数 clear()

9.QStandardItemModel 的信号

三. QStandardItem 类

1.特性读写函数

2.用户自定义数据

3.管理子项的函数

四. QItemSelectionModel 类

1.主要接口函数

2.信号

五. 示例程序功能实现

1.界面设计

2.窗口类定义和初始化

3.从文本文件导入数据

4.数据修改

5.项的格式设置

6.遍历数据模型

(四). 自定义代理

一. 自定义代理的功能

二. QStyledItemDelegate 类

1.函数 createEditor()

2.函数 setEditorData()

3.函数 setModelData()

三. 设计自定义代理类

1.设计代理类 TFloatSpinDelegate

2.设计代理类 TComboBoxDelegate

四. 使用自定义代理类

(六). QFileSystemModel 和 QTreeView

一. QFileSystemModel 类

1.设置根目录

2.设置模型选项

3.获取目录和文件的信息

4.操作目录和文件

二. QTreeView 类

三. 示例程序功能实现

1.模型/视图结构初始化

2.显示节点信息

3.模型设置


模型/视图(model/view)结构是进行数据存储和界面展示的一种编程结构。在这种结构里,模型存储数据,界面上的视图组件显示模型中的数据,在视图组件里修改的数据会被自动保存到模型里。模型/视图结构将数据存储和界面展示分离,分别用不同的类实现。

(一). 模型/视图结构概述

模型/视图结构是一种将数据存储和界面展示分离的编程方法。模型存储数据,视图组件显示模型中的数据,在视图组件里修改的数据会被自动保存到模型里。模型的数据来源可以是内存中的字符串列表或二维表格型数据,也可以是数据库中的数据表,一种模型可以用不同的视图组件来显示数据,所以模型/视图结构是一种高效、灵活的编程结构。

一. 模型/视图结构基本原理

GUI 程序的主要功能是可由用户在界面上编辑和修改数据,典型的如数据库应用程序。在数据库应用程序中,界面上的数据来源于 数据库,用户在界面上修改数据,修改后的数据又保存到数据。

将界面与原始数据分离,又通过模型将界面和原始数据关联起来,从而实现界面与原始数据的交互操作,这是处理界面与数据的一 种较好的方式。Qt使用模型/视图结构来处理这种关系,模型/视图的基本结构如图所示,它包括以下几个部分。

 • 源数据(data)是原始数据,如数据库的一个数据表或 SQL 查询结果、内存中的一个字符串列表或磁盘文件系统结构等。

• 视图(view)也称为视图组件,是界面组件,视图从模型获得数据然后将其显示在界面上。Qt 提供一些常用的视图组件,如 QListView、QTreeView 和 QTableView 等。

• 模型(model)也称为数据模型,与源数据通信,并为视图组件提供数据接口。它从源数据提取需要的数据,用于视图组件进行显示和编辑。Qt 中有一些预定义的模型类,如 QStringListModel 是字符串列表的模型类,QSqlTableModel 是数据库中数据表的模型类。

• 代理(delegate)在视图与模型之间交互操作时提供的临时编辑器。模型向视图提供数据是单向的,一般仅用于显示。当需要在视图上编辑数据时,代理会为编辑数据提供一个编辑器,这个编辑器获取模型的数据、接受用户编辑的数据后又将其提交给模型。例如在 QTableView 组件上双击一个单元格来编辑数据时,在单元格里就会出现一个 QLineEdit 组件,这个编辑框就是代理提供的临时编辑器。

由于通过模型/视图结构将源数据与显示和编辑界面分离,我们可以将一个模型在不同的视图中显示,也可以为一些特殊源数据设计自定义模型,或者在不修改模型的情况下设计特殊的视图组件。所以,模型/视图结构是一种高效、灵活的编程结构。

模型、视图和代理使用信号和槽进行通信。当源数据发生变化时,模型发射信号通知视图组件;当用户在界面上操作数据时,视图组件发射信号表示操作信息;在编辑数据时,代理会发射信号告知模型和视图组件编辑器的状态。

二. 模型

所有基于项(item)的模型类都是基于 QAbstractItemModel 类的,这个类定义了视图组件和代理存取数据的接口。模型只是在内存中临时存储数据,模型的数据来源可以是其他类、文件、数据库或任何数据源。Qt 中几个主要的模型类的继承关系如图所示。QAbstractItemModel 的父类是 QObject,所以模型类支持 Qt 的元对象系统。

抽象模型类 QAbstractItemModel 不能直接用于创建实例对象,常用的几个模型类如表所示。

常用的模型类
模型类功能
QFileSystemModel用于表示计算机上文件系统的模型类
QStringListModel用于表示字符串列表数据的模型类
QStandardItemModel标准的基于项的模型类,每个项是一个 QStandardItem 对象
QSqlQueryModel用于表示数据库 SQL 查询结果的模型类
QSqlTableModel用于表示数据库的一个数据表的模型类

三. 视图

视图就是用于显示模型中的数据的界面组件,Qt 提供的视图组件主要有以下几个。

• QListView:用于显示单列的列表数据,适用于一维数据的操作。

• QTreeView:用于显示树状结构数据,适用于树状结构数据的操作。

• QTableView:用于显示表格数据,适用于二维表格数据的操作。

• QColumnView:用多个 QListView 显示树状结构数据,树状结构的一层用一个 QListView 显示。

• QUndoView:用于显示 undo 指令栈内数据的视图组件,是 QListView 的子类。

QListWidget、QTreeWidget 和 QTableWidget 这 3 个用于处理项数据的组件分别是 3 个视图类的子类,称为视图类的便利类(convenience class)。

只需调用视图类的 setModel()函数为视图组件设置一个模型,模型的数据就可以显示在视图组件上。在视图组件上修改数据后,数据可以自动保存到模型里。

视图组件的数据来源于模型,视图组件不存储数据。便利类则为组件的每个节点或单元格创建一个项,用项存储数据,例如对于 QTableWidget 类这个便利类,表格的每个单元格关联一个QTableWidgetItem 对象。便利类没有模型,它实际上是用项的方式替代了模型的功能,将界面与数据绑定。因此,便利类缺乏对大型数据源进行灵活处理的能力,只适用于小型数据的显示和编辑, 而视图组件则会根据模型的数据内容自动显示,有助于减少编程工作量,使用起来也更灵活。

四. 代理

代理就是在视图组件上为编辑数据提供的临时编辑器,例如在 QTableView 组件上编辑一个单元格的数据时,默认会提供一个 QLineEdit 编辑框。代理负责从模型获取相应的数据,然后将其显示在编辑器里,修改数据后又将编辑器里的数据保存到模型中。

QAbstractItemDelegate 是所有代理类的基类,作为抽象类,它不能直接用于创建对象。它有两个子类,即 QItemDelegate 和 QStyledItemDelegate ,这两个类的功能基本相同,而 QStyledItemDelegate 能使用 Qt 样式表定义的当前样式绘制代理组件, 所以, QStyledItemDelegate 是视图组件使用的默认的代理类。

对于一些特殊的数据编辑需求,例如只允许输入整数时使用 QSpinBox 作为代理组件更合适, 需要从列表中选择数据时则使用QComboBox作为代理组件更好, 这时就可以从QStyledItemDelegate 继承创建自定义代理类。

五. 模型/视图结构的一些概念

1.模型的基本结构

在模型/视图结构中,模型为视图组件和代理提供存取数据的标准接口。QAbstractItemModel 是所有模型类的基类,不管底层的数据结构是如何组织数据的,QAbstractItemModel 的子类都以表格的层次结构展示数据,视图组件按照这种规则来存取模型中的数据,但是展示给用户的形式不一样。

上图所示的是模型的 3 种常见展示形式,分别是列表模型(list model)、表格模型(table model) 和树状模型(tree model)。不管模型的表现形式是怎样的, 模型中存储数据的基本单元都是项(item), 每个项有一个行号和一个列号,还有一个父项(parent item)。3 个模型都有一个隐藏的根项(root item), 列表模型的存储结构就是一列, 表格模型的存储结构是规则的二维数组, 树状模型的项可以有子项,结构复杂一点。

2.模型索引

为了确保数据的展示与数据存取方式分离,模型中引入了模型索引(model index)的概念。通过模型能访问的每个项都有一个模型索引,视图组件和代理都通过模型索引来获取数据。 QModelIndex 是表示模型索引的类。模型索引提供访问数据的临时指针,用于通过模型提取或修改数据。因为模型内部组织数据的结构可能随时改变,所以模型索引是临时的,例如对于一个 QTreeView 组件,获得一个节点的模型索引后又修改了模型的数据,那么前面获得的那个模型索引可能就不再指向原来那个节点了。

3.行号和列号

模型的基本形式是用行和列定义的表格数据,但这并不意味着底层的数据是用二维数组存储的,使用行和列只是为了组件之间交互方便。一个模型索引包含行号和列号。

要获得一个模型索引,必须提供 3 个参数:行号、列号、父项的模型索引。例如,对于图中的表格模型中的 3 个项 A、B、C,获取其模型索引的示意代码如下:

QModelIndex indexA = model->index(0, 0, QModelIndex()); 
QModelIndex indexB = model->index(1, 1, QModelIndex()); 
QModelIndex indexC = model->index(2, 1, QModelIndex()); 

其中,indexA、indexB、indexC 都是 QModelIndex 类型的变量,model 是数据模型。在创建模型索引的函数中需要传递行号、列号和父项的模型索引。对于列表模型和表格模型,顶层节点总是用 QModelIndex()表示。

4.父项

当模型为列表或表格结构时,使用行号、列号访问数据比较直观,所有项的父项就是顶层项。当模型为树状结构时情况比较复杂(树状结构中,项一般称为节点),一个节点有父节点,其也可以是其他节点的父节点,在构造节点的模型索引时,必须指定正确的行号、列号和父节点。

对于图中的树状模型,节点 A 和节点 C 的父节点是顶层节点,获取模型索引的代码是:

QModelIndex indexA = model->index(0, 0, QModelIndex()); 
QModelIndex indexC = model->index(2, 1, QModelIndex()); 

但是,节点 B 的父节点是节点 A,节点 B 的模型索引由下面的代码生成:

QModelIndex indexB = model->index(1, 0, indexA); 

5.项的角色

在为模型的一个项设置数据时,可以为项设置不同角色的数据。QAbstractItemModel 类定义了设置项的数据的函数 setData(),其函数原型定义如下:

bool QAbstractItemModel::setData(const QModelIndex &index, const QVariant &value,  int role = Qt::EditRole) 

其中,index 是项的模型索引,value 是需要设置的数据,role 是设置数据的角色。

可以为一个项设置不同角色的数据,角色参数 role 用枚举类型 Qt::ItemDataRole 的枚举值表示。枚举类型 Qt::ItemDataRole 常用的一些枚举值及其含义如下表所示,表中的角色数据类型是指用于相应角色的数据的类型,例如,Qt::DisplayRole 角色的数据一般是 QString 类型的字符串。

枚举类型 Qt::ItemDataRole 常用的一些枚举值及其含义
枚举值角色数据类型含义
Qt::DisplayRoleQString界面上显示的字符串,例如单元格显示的文字
Qt::DecorationRoleQIcon、QColor在界面上起装饰作用的数据,如图标
Qt::EditRoleQString界面上适合在编辑器中显示的数据,一般是文字
Qt::ToolTipRoleQString项的 toolTip 字符串
Qt::StatusTipRoleQString项的 statusTip 字符串
Qt::FontRoleQFont项的字体,如单元格内文字的字体
Qt::TextAlignmentRoleQt::Alignment项的对齐方式,如单元格内文字的对齐方式
Qt::BackgroundRoleQBrush项的背景色,如单元格的背景色
Qt::ForegroundRoleQBrush项的前景色,如单元格的文字颜色
Qt::CheckStateRoleQt::CheckState项的复选状态
Qt::UserRoleQVariant自定义的用户数据

在获取一个项的数据时也需要指定角色,以获取不同角色的数据。QAbstractItemModel 定义了函数 data(),可返回一个项的不同角色的数据,其函数原型定义如下:

QVariant QAbstractItemModel::data(const QModelIndex &index, int role = Qt::DisplayRole)

通过为一个项的不同角色定义数据,可以告知视图组 件和代理如何展示数据。例如在下图中,项的 DisplayRole 角色数据是显示的字符串,DecorationRole 角色数据是用于装饰显示的元素(如图标),ToolTipRole 角色数据是就地显 示的提示信息。不同的视图组件对各种角色数据的解释和展示可能不一样,也可能会忽略某些角色的数据。

六. QAbstractItemModel 类

QAbstractItemModel 是所有模型类的直接或间接父类, 它定义了模型的通用接口函数,例如用于插入行、删除行、设置数据的函数。 QAbstractItemModel 是抽象类,不能直接用于创建对象实例,各个具体的模型类实现了这些接口函数。

注意,因为 QAbstractItemModel 是抽象类,所以它的很多函数都是虚函数, 也就是函数名称前面有关键字 virtual,下面在显示函数原型时都省略了这个关键字。

1.行数和列数

函数 rowCount()返回行数,columnCount()返回列数,两个函数的原型定义如下:

int rowCount(const QModelIndex &parent = QModelIndex()) 
int columnCount(const QModelIndex &parent = QModelIndex()) 

这两个函数中都需要传递一个参数 parent,这是父项的模型索引。对于列表模型和表格模型,  parent 使用默认的参数 QModelIndex()即可,得到的行数和列数就是模型的行数和列数。对于树状模型,parent 需要设置为父节点的模型索引,函数返回的是父节点下的节点的行数和列数。

2.插入或删除行

可以用以下函数在模型中插入或删除一个或多个数据行。

bool insertRow(int row, const QModelIndex &parent = QModelIndex()) 
bool insertRows(int row, int count, const QModelIndex &parent = QModelIndex()) 
bool removeRow(int row, const QModelIndex &parent = QModelIndex()) 
bool removeRows(int row, int count, const QModelIndex &parent = QModelIndex()) 

参数 parent 是父项的模型索引。对于列表模型和表格模型, parent 使用默认的参数 QModelIndex() 即可。对于树状模型,parent 需要设置为父节点的模型索引。

在使用函数 insertRow()时,如果参数 row 的值超过了模型的行数,新增的行就添加到模型的末尾。

3.插入或删除列

可以用以下函数在模型中插入或删除一个或多个数据列。

bool insertColumn(int column, const QModelIndex &parent = QModelIndex()) 
bool insertColumns(int column, int count, const QModelIndex &parent = QModelIndex()) 
bool removeColumn(int column, const QModelIndex &parent = QModelIndex()) 
bool removeColumns(int column, int count, const QModelIndex &parent = QModelIndex()) 

4.移动行或列

函数 moveRow()可以移动一个行,函数 moveColumn()可以移动一个列。使用这两个函数可以实现表格的行或列的移动以及目录树中的节点移动等操作。

bool moveRow(const QModelIndex &sourceParent, int sourceRow, 
 const QModelIndex &destinationParent, int destinationChild) 
bool moveColumn(const QModelIndex &sourceParent, int sourceColumn, 
 const QModelIndex &destinationParent, int destinationChild) 

5.数据排序

函数 sort()将数据按某一列排序,可指定排序方式,默认是升序方式。

void sort(int column, Qt::SortOrder order = Qt::AscendingOrder)

6.设置和读取项的数据

函数 setData()为一个项设置数据,函数 data()返回一个项的数据,其函数原型定义如下:

 bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) 
 QVariant data(const QModelIndex &index, int role = Qt::DisplayRole)

这两个函数中都用模型索引定位项,都需要指定数据的角色。

7.清除一个项的数据

函数 clearItemData()用于清除一个项的所有角色的数据,函数定义如下:

 bool clearItemData(const QModelIndex &index) 

QAbstractItemModel 的这些函数一般都是虚函数,子类会重新实现其需要用到的函数,以符合模型类的具体操作。

七. QAbstractItemView 类

QAbstractItemView 是所有视图组件类的父类,它定义了视图组件类共有的一些接口。

1.关联数据模型和选择模型

需要为视图组件设置数据模型才能构成完整的模型/视图结构,相关函数定义如下:

void setModel(QAbstractItemModel *model) //设置数据模型
QAbstractItemModel *model() //返回关联的数据模型对象指

不同的视图组件使用不同类型的模型,QListView 组件一般用 QStringListModel 对象作为数据模型,用于编辑字符串列表;QTableView 一般用 QStandardItemModel 对象作为数据模型,用于编 辑表格数据。

提示:模型用于表示模型/视图结构中的概念,数据模型一般用于表示模型类的具体对象。

视图组件还可以设置选择模型,在界面上选择的项发生变化时,通过选择模型可以获取所有被选择项的模型索引。例如,QTableView 在允许选择多个单元格时,使用 QItemSelectionModel 类对象作为选择模型就比较有用,可以获得所有被选单元格的模型索引,从而能方便地对所选择的项进行处理。相关函数定义如下:

void setSelectionModel(QItemSelectionModel *selectionModel) //设置选择模型
QItemSelectionModel *selectionModel()  //返回关联的选择模型对象指针

2.常用属性

QAbstractItemView 类定义了一些属性,这些属性是子类 QListView 和 QTableView 共有的。下图展示了在 UI 可视化 时一个 QListView 组件的属性,其中包含 QAbstractItemView 的属性。

(1)editTriggers 属性。表示视图组件是否可以编辑数据,以及进入编辑状态的方式。设置和读取该属性的函数定义如下:

void setEditTriggers(QAbstractItemView::EditTriggers triggers) 
QAbstractItemView::EditTriggers editTriggers() 

该属性值是标志类型 QAbstractItemView::EditTriggers,是枚举类型 QAbstractItemView::EditTrigger 的枚举值的组合。各枚举值的含义如下。

• NoEditTriggers:不允许编辑。

• CurrentChanged:当前项变化时进入编辑状态。

• DoubleClicked:双击一个项时进入编辑状态。

• SelectedClicked:点击一个已选择的项时进入编辑状态。

• EditKeyPressed:当平台的编辑按键被按下时进入编辑状态。

• AnyKeyPressed:任何键被按下时进入编辑状态。

• AllEditTriggers:发生以上任何动作时进入编辑状态。

视图组件类和模型类都没有 readonly 属性,如果要设置数据是只读的,用函数 setEditTriggers()设置视图组件为不允许编辑即可。

(2)alternatingRowColors 属性。这个属性设置各行是否交替使用不同的背景色。如果设置为true,会使用系统默认的一种颜色。如果要自定义背景色,需要用 Qt 样式表。

(3)selectionMode 属性。这个属性表示在视图组件上选择项的操作模式,对于 QTableView 比较有意义。这个属性值是枚举类型 QAbstractItemView::SelectionMode,有以下几种枚举值。

• SingleSelection:单选,只能选择一个项,例如只能选择一个单元格。

• ContiguousSelection:连续选择,例如按住 Shift 键选择多个连续单元格。

• ExtendedSelection:扩展选择,例如可以按住 Ctrl 键选择多个不连续的单元格。

• MultiSelection:多选,例如通过拖动鼠标选择多个单元格。

• NoSelection:不允许选择。

(4)selectionBehavior 属性。这个属性表示点击鼠标时选择操作的行为,对于 QTableView 比较有意义。这个属性值是枚举类型 QAbstractItemView::SelectionBehavior,各枚举值含义如下。

• SelectItems:选择单个项,点击一个单元格时,就是选择这个单元格。

• SelectRows:选择行,点击一个单元格时,选择单元格所在的一整行。

• SelectColumns:选择列,点击一个单元格时,选择单元格所在的一整列。

3.常用接口函数

QAbstractItemView 定义了很多接口函数,下面是常用的几个。

QModelIndex currentIndex() //返回当前项的模型索引,例如当前单元格的模型索引
void setCurrentIndex(const QModelIndex &index) //设置模型索引为 index 的项为当前项
void selectAll() //选择视图中的所有项,例如选择 QTableView 组件中的所有单元格
void clearSelection() //清除所有选择

如果设置为单选,视图组件上就只有一个当前项,函数 currentIndex()返回当前项的模型索引, 通过模型索引就可以从模型中获取项的数据。

4.常用信号

QAbstractItemView 定义了几个信号,常用的几个信号定义如下,信号触发条件见注释。

void clicked(const QModelIndex &index) //点击某个项时
void doubleClicked(const QModelIndex &index) //双击某个项时
void entered(const QModelIndex &index) //鼠标移动到某个项上时
void pressed(const QModelIndex &index) //鼠标左键或右键被按下时

这些信号函数都传递了一个模型索引 index,这是信号触发时的项的模型索引。例如 clicked() 信号中的模型索引 index 表示点击的项的模型索引。

(二). QStringListModel 和 QListView

QStringListModel 是处理字符串列表的模型类,其实例可以作为 QListView 组件的数据模型。结合使用这两个类,就可以在界面上显示和编辑字符串列表。

QStringListModel 内部存储了一个字符串列表,这个字符串列表的内容自动显示在关联的QListView 组件上,在 QListView 组件上双击某一行时,可以通过默认的代理组件(QLineEdit组件)修改这一行字符串的内容,修改后的这行字符串自动保存到数据模型的字符串列表里。

在字符串列表中添加或删除行是通过 QStringListModel 的接口函数实现的,QListView 没有接口函数用于修改数据,它只是用作数据显示和编辑的界面组件。通过QStringListModel 的接口函数修改字符串列表的内容后,关联的 QListView 组件会自动更新显示内容。

示例项目采用 QStringListModel 作为数据模型,QListView 作为视图组件,构成模型/视图结构, 编辑字符串列表。示例运行时界面如图所示。界面左侧实现了对 数据模型的显示和操作,右侧的 QPlainTextEdit 组件显示数据模型中的字符串列表,以方便查看数据模型的内容是否与界面左侧QListView组件显示的内容一致。

一. QStringListModel 类

QStringListModel 是字符串列表数据的模型类,与 QListView 组件搭配组成模型/视图结构, 适合处理字符串列表数据。QStringListModel 有两种参数形式的构造函数,定义如下:

QStringListModel(const QStringList &strings, QObject *parent = nullptr) 
QStringListModel(QObject *parent = nullptr) 

作为字符串列表数据的模型,QStringListModel 对象内部有一个字符串列表,对模型数据的修改就是对 QStringListModel 对象内部字符串列表的修改。可以在创建 QStringListModel 对象时传递一个 QStringList 对象初始化其内部字符串列表数据。

QStringListModel 新定义的函数只有两个,定义如下:

void setStringList(const QStringList &strings) //设置字符串列表,初始化模型数据
QStringList stringList() //返回模型内部的字符串列表

函数 setStringList()传递一个字符串列表 strings,用于初始化模型内部的字符串列表的数据。函数 stringList()则返回模型内部的字符串列表。

QStringListModel 重新实现了 QAbstractItemModel 的一些函数,使其适用于对字符串列表数据的处理。

二. 示例程序功能实现

1.窗口界面可视化设计

本示例窗口基类是 QMainWindow,在 UI 可视化设计时删除了主窗口上的菜单栏和工具栏, 保留了状态栏。界面采用水平分割布局,左侧的分组框里是一些按钮和一个 QListView 组件,右侧的分组框里是两个按钮和一个QPlainTextEdit组件。

2.模型/视图结构初始化

窗口类 MainWindow 中自定义的内容只有两个私有变量,定义如下:

QStringList m_strList; //保存初始字符串列表内容
QStringListModel *m_model; //数据模型

下面是 MainWindow 类构造函数的代码。

MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWindow) 
{ 
 ui->setupUi(this); 
 //初始化一个字符串列表的内容
 m_strList<<"北京"<<"上海"<<"天津"<<"河北"<<"山东"<<"四川"<<"重庆"<<"广东"<<"河南"; 
 m_model= new QStringListModel(this); //创建数据模型
 m_model->setStringList(m_strList); //初始化数据
 ui->listView->setModel(m_model); //设置数据模型
 ui->chkEditable->setChecked(true); 
 ui->listView->setEditTriggers(QAbstractItemView::DoubleClicked 
 | QAbstractItemView::SelectedClicked); 
} 

我们在构造函数中创建了数据模型 m_model 并初始化其字符串列表数据,再将m_model 设置为界面上的 QListView 组件 listView 的数据模型,构造模型/视图结构。

程序运行后,界面上的 listView 里就会显示初始化的字符串列表的内容。

3.数据模型的操作

界面左侧分组框里的一些按钮用于对数据模型进行操作,例如添加项、删除项、移动项等,这些按钮的槽函数代码如下:

void MainWindow::on_btnIniList_clicked() 
{//“恢复列表”按钮
 m_model->setStringList(m_strList); //重新载入
} 
void MainWindow::on_btnListClear_clicked() 
{//“清除列表”按钮
 m_model->removeRows(0,m_model->rowCount()); //清除数据模型的所有项
} 
void MainWindow::on_btnListAppend_clicked() 
{ //“添加项”按钮
 m_model->insertRow(m_model->rowCount()); //在末尾插入一个项
 QModelIndex index= m_model->index(m_model->rowCount()-1,0); //获取刚插入项的模型索引
 m_model->setData(index,"new item",Qt::DisplayRole); 
 ui->listView->setCurrentIndex(index); 
} 
void MainWindow::on_btnListInsert_clicked() 
{//“插入项”按钮
 QModelIndex index= ui->listView->currentIndex(); //当前项的模型索引
 m_model->insertRow(index.row()); 
 m_model->setData(index,"inserted item",Qt::DisplayRole); 
 ui->listView->setCurrentIndex(index); 
} 
void MainWindow::on_btnListDelete_clicked() 
{//“删除项”按钮
 QModelIndex index= ui->listView->currentIndex(); //获取当前项的模型索引
 m_model->removeRow(index.row()); 
} 
void MainWindow::on_btnListMoveUp_clicked() 
{//“上移”按钮
 int curRow= ui->listView->currentIndex().row(); //当前行的行号
 QModelIndex index= QModelIndex(); 
 m_model->moveRow(index,curRow,index,curRow-1);
} 
void MainWindow::on_btnListMoveDown_clicked() 
{//“下移”按钮
 int curRow= ui->listView->currentIndex().row(); //当前行的行号
 QModelIndex index= QModelIndex(); 
 m_model->moveRow(index,curRow,index,curRow+2); 
} 
void MainWindow::on_btnListSort_clicked(bool checked) 
{//“排序”按钮
 if (checked) 
 m_model->sort(0,Qt::AscendingOrder); //升序
 else 
 m_model->sort(0,Qt::DescendingOrder); //降序
} 

从上述代码可以看出,对数据的操作都是通过数据模型的接口函数实现的。在数据模型 m_model 中添加或删除项后,界面组件 listView 中会立刻自动将其显示出来。

4.视图组件相关的代码

界面上“允许编辑”复选框设置组件 listView 的 editTriggers 属性,我们还为 listView 的 clicked() 信号编写了槽函数,代码如下:

void MainWindow::on_chkEditable_clicked(bool checked) 
{//“允许编辑”复选框
 if (checked) //可编辑
 ui->listView->setEditTriggers(QAbstractItemView::DoubleClicked 
 | QAbstractItemView::SelectedClicked); 
 else 
 ui->listView->setEditTriggers(QAbstractItemView::NoEditTriggers); 
} 
void MainWindow::on_listView_clicked(const QModelIndex &index) 
{//组件 listView 的 clicked()信号的槽函数
 QString str1= QString::asprintf("模型索引:row=%d, column=%d;", 
 index.row(),index.column()); 
 QVariant var= m_model->data(index, Qt::DisplayRole); //获取模型数据
 QString str2= var.toString(); 
 ui->statusbar->showMessage(str1+str2); 
} 

在槽函数 on_listView_clicked()中,参数 index 是点击的项的模型索引。一个模型索引有行号和列号,通过模型索引可以获取数据模型中项的数据,代码如下:

QVariant var= m_model->data(index,Qt::DisplayRole);

这里获取的是项的 DisplayRole 角色的数据,也就是显示的文字。函数 data()返回的是 QVariant 类型的数据,其还需要转换为 QString 类型。

5.在文本框中显示数据模型的内容

在对数据模型进行插入、添加、删除项操作后,内容会立即在 listView 上显示出来,这是数据模型与视图组件之间信号与槽的作用的结果,当数据模型的内容发生改变时,通知视图组件更新显示。在 listView 上双击一行进入编辑状态,修改一行的文字后,修改的文字也会保存到数据模型里。

数据模型内保存着最新的数据内容,对 QStringListModel 模型来说,通过函数stringList()可以得到其最新的数据副本。窗口上有一个标题为“显示数据模型的 StringList”的按钮,它的功能就是通过 QStringListModel::stringList()函数返回数据模型内部的字符串列表,并将其在组件 plainTextEdit 里逐行显示出来,以检验数据模型内的数据是否与 listView 上显示的完全一样。以下是这个按钮的槽函数代码。

void MainWindow::on_btnTextImport_clicked() 
{//“显示数据模型的 StringList”按钮
 QStringList tmpList= m_model->stringList(); 
 ui->plainTextEdit->clear(); 
 for (int i=0; i<tmpList.size(); i++) 
 ui->plainTextEdit->appendPlainText(tmpList.at(i)); 
} 

在这个示例中,QStringListModel 和 QListView 结合组成了模型/视图结构, 对字符串列表数据进行编辑。在测试过程中发现不能为 listView 显示的列表显示图标和复选框,不能设置文字对齐方式,即使用函数 setData()设置了 DecorationRole、TextAlignmentRole 等角色的数据。这是因为 QStringListModel 内部仅保存字符串列表,并没有数据结构保存其他角色的数据。

如果只是需要编辑字符串列表的内容, 使用模型/视图结构比较方便。如果需要每一行带复选框的列表框,还是要使用 QListWidget。

(三). QStandardItemModel 和 QTableView

QStandardItemModel 是基于项的模型类,每个项是一个QStandardItem对象, 可以存储各种数据。QStandardItemModel 通常与 QTableView 组成模型/视图结构,实现二维数据的管理。设计一个示例项目主要涉及如下 3 个类的使用方法。

• QStandardItemModel: 基于项的模型类。它维护一个二维的项数组, 每个项是一个QStandardItem对象,用于存储文字、字体、对齐方式等各种角色的数据。

• QTableView:二维表格视图组件类,基本显示单元是单元格。通过函数 setModel()设置一个 QStandardItemModel 类的数据模型之后,一个单元格显示数据模型中的一个项。

• QItemSelectionModel:项选择模型类。它是用于跟踪视图组件的单元格选择状态的类,需要指定一个 QStandardItemModel 类的数据模型。当在 QTableView 组件上选择一个或多个单元格时,通过项选择模型可以获得选中单元格的模型索引。

示例运行时界面如上图所示,该示例具有如下一些功能。

• 打开一个纯文本文件,该文件是规范的二维数据文件,通过字符串处理获取表头和各行各列的数据,并导入到一个 QStandardItemModel 数据模型。

• 编辑、修改模型的数据,可以插入行、添加行、删除行。

• 可以设置数据模型中某个项的不同角色的数据,例如设置文字对齐方式、文字是否为粗体等。

• 通过项选择模型获取视图组件上的当前单元格,以及选择单元格的范围,对选择的单元格进行操作,例如设置选中单元格的文字对齐方式。

• 遍历数据模型的所有项,将数据模型的内容显示到 QPlainTextEdit 组件里。

一. QTableView 类

QTableView 继承自 QAbstractItemView 类, QTableView 新定义的属性主要用于控制显示效果,在 UI 可视化设计时就可以设置 QTableView 组件的各种属性。

QTableView 组件有水平表头和垂直表头,都是 QHeaderView 对象,可以设置和返回表头对象, 相关函数定义如下:

void QTableView::setHorizontalHeader(QHeaderView *header) //设置水平表头
void QTableView::setVerticalHeader(QHeaderView *header) //设置垂直表头
QHeaderView *QTableView::horizontalHeader() //返回水平表头对象指针
QHeaderView *QTableView::verticalHeader() //返回垂直表头对象指针

当 QTableView 组件使用一个 QStandardItemModel 对象作为数据模型时,它会自动创建表头对象,垂直表头一般显示行号,水平表头一般显示列的标题。

二. QStandardItemModel 类

QStandardItemModel 是以项为基本数据单元的模型类,每个项是一个 QStandardItem 对象。 项可以存储各种角色的数据,如文字、字体、对齐方式、图标、复选状态等。QStandardItemModel 如果以多行多列的二维数组形式存储项,就是表格模 型;如果表格模型只有一列, 就是列表模型;如果在存储项时为项指定父项,就可以构成树状模型。

如果一个 QStandardItemModel 对象是表格模型, 将它设置为 QTableView 组件的数据模型后, 视图组件就用表格的形式显示模型的数据,并且根据每个单元格的项定义的各种角色数据控制显示效果。

对数据的操作是通过 QStandardItemModel 类的接口函数实现的。QStandardItemModel 的父类是 QAbstractItemModel, QStandardItemModel 新定义了一些接口函数,下面分组介绍常用的一些接口函数。

1.设置行数和列数

QStandardItemModel 以二维数组的形式存储项数据,所以可以设置行数和列数。

void setRowCount(int rows) //设置数据模型的行数
void setColumnCount(int columns) //设置数据模型的列数

如果设置的列数大于 1,模型就是表格模型;如果设置的列数为 1,模型就可以看作列表模型。

2.设置项

设置了模型的行数和列数后,就相当于设置了模型的表格大小,还需要用函数 setItem()为表格的每个单元设置一个 QStandardItem 对象。函数 setItem()有两种参数形式,定义如下:

void setItem(int row, int column, QStandardItem *item) //用于表格模型
void setItem(int row, QStandardItem *item) //用于列表模型

如果模型只有一列,就看作列表模型,函数 setItem()里不需要传递列号。

3.获取项

函数 item()根据行号和列号返回模型中某个单元的项, 函数 itemFromIndex()根据模型索引返回某个单元的项,这两个函数定义如下:

QStandardItem *item(int row, int column = 0) //根据行号和列号返回项
QStandardItem *itemFromIndex(const QModelIndex &index) //根据模型索引返回项

函数 indexFromItem()根据项返回其模型索引,其定义如下:

QModelIndex indexFromItem(const QStandardItem *item) 

4.添加行或列

函数 appendRow()用于在模型最后添加一行, 并且为添加行的每个单元设置QStandardItem 对象。

void appendRow(const QList<QStandardItem *> &items) //用于表格模型
void appendRow(QStandardItem *item) //用于列表模型

如果是表格模型,一行有多列,appendRow()函数里需要传递一个 QStandardItem 对象列表;如果是列表模型,只需传递一个 QStandardItem 对象。

函数 appendColumn()用于在模型中添加一列,一般只用于表格模型,其定义如下:

void appendColumn(const QList<QStandardItem *> &items) //在表格模型中添加列

5.插入行或列

函数 insertRow()用于在模型中插入一行,有 3 种参数形式,其中的 2 种定义如下:

void insertRow(int row, const QList<QStandardItem *> &items) //用于表格模型
void insertRow(int row, QStandardItem *item) //用于列表模型

上面这两种参数形式的函数分别用于表格模型和列表模型,会为新增的单元设置项。

下面这种参数形式的函数适用于树状模型,参数 parent 是父节点的模型索引,row 是插入位置的行号。这种参数形式的函数的功能是在父节点下面插入一个节点,但是没有为节点设置QStandardItem 对象,需要再调用函数 setItem()为新插入的节点设置 QStandardItem 对象。

bool insertRow(int row, const QModelIndex &parent = QModelIndex()) //用于树状模型

函数 insertColumn()用于在模型中插入列,只有表格模型或树状模型才需要插入列。

void insertColumn(int column, const QList<QStandardItem *> &items) //用于表格模型
bool insertColumn(int column, const QModelIndex &parent = QModelIndex()) //用于树状模型

6.移除行、列或项

可以从表格模型中移除一行或一列,模型的行数或列数就会相应减 1,但是移除的 QStandardItem对象不会被删除,需要单独用 delete 删除。移除行或列的两个函数定义如下,返回值是被移除的 QStandardItem 对象列表。

QList<QStandardItem *> takeRow(int row) //移除一行,适用于表格模型
QList<QStandardItem *> takeColumn(int column) //移除一列,适用于表格模型

函数 takeItem()用于移除一个项,它适用于列表模型。

QStandardItem *takeItem(int row, int column = 0) //移除一个项,适用于列表模型

7.水平表头和垂直表头

数据模型有水平表头,水平表头的列数等于表格模型的列数,表头的每个单元也是 QStandardItem 对象,可以用函数 setHorizontalHeaderItem()为表头的某一列设置项。

void setHorizontalHeaderItem(int column, QStandardItem *item) //为表头某列设置项

如果只是设置表头各列的文字,可以使用函数 setHorizontalHeaderLabels(),它用一个字符串列表的内容设置表头各列的文字。

void setHorizontalHeaderLabels(const QStringList &labels)//用字符串列表的内容设置水平表头各列的文字

还有函数可以用于返回表头某列的项,或移除表头某列的项,函数定义如下:

QStandardItem *horizontalHeaderItem(int column) //返回水平表头中的一个项
QStandardItem *takeHorizontalHeaderItem(int column) //移除水平表头中的一个项

同样,还有操作垂直表头的函数,定义如下:

void setVerticalHeaderItem(int row, QStandardItem *item) 
void setVerticalHeaderLabels(const QStringList &labels) 
QStandardItem *verticalHeaderItem(int row) 
QStandardItem *takeVerticalHeaderItem(int row)

表格数据的垂直表头默认以行号作为文字,一般不需要处理。

8.函数 clear()

QStandardItemModel 有一个接口函数 clear(),它用于清除模型内的所有项,行数和列数都会变为 0。

9.QStandardItemModel 的信号

QStandardItemModel 新定义了一个信号 itemChanged(),在任何一个项的数据发生变化时,此信号就会被发射。信号函数定义如下,其中的参数 item 是数据发生了变化的项。

void itemChanged(QStandardItem *item) 

三. QStandardItem 类

QStandardItemModel 数据模型中的每个项是一个 QStandardItem 对象。QStandardItem 存储了一个项的各种特性参数,还可以存储用户自定义数据。一个项可以添加子项,子项也是 QStandardItem 类型的对象,所以,QStandardItem 也可以作为树状模型的项。

1.特性读写函数

QStandardItem 没有父类,它存储项的各种特性参数和设置内容,有一些成对的读写函数分别用于读取和设置各种特性。常用的一些函数如表所示,表中列出了函数名,并且以表格数据模型为例说明设置函数的功能。

QStandardItem 的常用接口函数
读取函数设置函数设置函数的功能
text()setText()设置项的显示文字,如单元格的文字
toolTip()setToolTip()设置 toolTip 文字,是鼠标处显示的提示信息
statusTip()setStatusTip()设置 statusTip 文字,是在状态栏上显示的提示信息
icon()setIcon()设置项的图标,如单元格内显示的图标
font()setFont()设置显示文字的字体,如单元格内文字的字体
textAlignment()setTextAlignment()设置显示文字的对齐方式
foreground()setForeground()设置项的前景色,如单元格中文字的颜色
background()setBackground()设置项的背景色,如单元格的背景色
isEnabled()setEnabled()设置项是否使能,若设置为 true,用户就可以交互操作这个项
isEditable()setEditable()设置项是否可以编辑
isSelectable()setSelectable()设置项是否可以被选择
isCheckable()setCheckable()设置项是否可复选,若设置为 true,会出现复选框
checkState()setCheckState()设置项的复选状态
isAutoTristate()setAutoTristate()设置是否自动改变 3 种复选状态,对于树状模型的节点比较有用
isUserTristate()setUserTristate()设置是否由用户决定 3 种复选状态
flags()setFlags()设置项的标志
row()返回自身在父项的所有子项中的行号,对于表格模型,就是表格行号
column()返回自身在父项的所有子项中的列号,对于表格模型,就是表格列号

表中一般的函数功能都很简单,不用过多解释。函数 setFlags()用于设置项的标志,其函数原型定义如下:

void QStandardItem::setFlags(Qt::ItemFlags flags) 

参数 flags 是标志类型,是枚举类型 Qt::ItemFlag 的枚举值的组合。QTreeWidgetItem 类的 setFlags()函数也用到这个枚举类型。

2.用户自定义数据

可以用 QStandardItem::setData()函数设置各种角色的数据,函数定义如下:

void QStandardItem::setData(const QVariant &value, int role = Qt::UserRole + 1) 

其中,参数 value 是需要设置的数据,role 是设置数据的角色,默认值是 Qt::UserRole + 1。

QStandardItem 的一些单独的函数相当于设置了项的一些固定角色的数据,例如函数 setText() 设置了 Qt::DisplayRole 角色的数据,函数 setToolTip()设置了 Qt::ToolTipRole 角色的数据。所以, setData()一般用于设置用户自定义数据,可以设置多个用户数据,从 Qt::UserRole 递增角色数值即可。

函数 data()返回指定角色的数据,函数 clearData()清除用 setData()函数设置的所有角色的数据。

QVariant QStandardItem::data(int role = Qt::UserRole + 1) 
void QStandardItem::clearData() 

3.管理子项的函数

一个 QStandardItem 对象可以添加子项, 子项也是 QStandardItem 对象, 这样就可以构造不限层级的树状结构。QStandardItem以二维表格的形式管理子项, 可以添加多行多列的子项QStandardItem管理子项的函数有如下这些。

void appendRow(const QList<QStandardItem *> &items) //添加一行多个项
void appendRow(QStandardItem *item) //添加一行,只有一个项
void appendColumn(const QList<QStandardItem *> &items) //添加一列多个项
void insertRow(int row, const QList<QStandardItem *> &items) //插入一行多个项
void insertRow(int row, QStandardItem *item) //插入一行,只有一个项
void insertRows(int row, int count) //在 row 行插入 count 个空行,未设置 item 
void insertColumn(int column, const QList<QStandardItem *> &items) //插入一列多个项
void insertColumns(int column, int count) //在column列插入count个空列,未设置item 
void removeColumn(int column) //删除序号为 column 的列
void removeColumns(int column, int count) //从 column 列开始,删除 count 个列
void removeRow(int row) //删除序号为 row 的行,存储的项也被删除
void removeRows(int row, int count) //从 row 行开始,删除 count 个行
int rowCount() //返回子项的行数
int columnCount() //返回子项的列数
bool hasChildren() //这个项是否有子项
QStandardItem *child(int row, int column = 0) //根据行号和列号返回子项

QStandardItem 的很多函数与 QStandardItemModel 中的函数名称相同,功能也相同,只是操作对象不同。QStandardItemModel 管理的是模型的顶层项,如果是列表模型或表格模型,各个项没有子项,QStandardItemModel就直接管理模型中的所有项。如果是树状模型, 那么 QStandardItemModel 管理的就是所有顶层项(不是根项),也就是目录树中的一级节点,而各个一级节点的直接子节点则通过 QStandardItem 类来管理,依次递推下去,就可以形成不限层级的树状模型。

四. QItemSelectionModel 类

一个视图组件需要设置一个数据模型,还可以设置一个选择模型。QItemSelectionModel 是选择模型类,它的功能是跟踪视图组件上的选择操作,给出选择范围。例如,给 QTableView 组件设置一个选择模型后,在 QTableView 组件上选择多个单元格时,通过选择模型就可以得到所有被选单元格的模型索引。

1.主要接口函数

需要用函数 setModel()为选择模型设置数据模型,函数定义如下:

void QItemSelectionModel::setModel(QAbstractItemModel *model) //为选择模型设置数据模型

将数据模型、选择模型、视图组件这 3 种对象做好关联设置后,在视图组件上进行选择操作时,选择模型就可以跟踪视图组件上的选择操作。QItemSelectionModel 有一些接口函数可用于给出选择的项的模型索引等信息。

bool hasSelection() //是否有被选择的项,例如被选择的单元格
QModelIndex currentIndex() //返回当前项的模型索引,例如当前单元格
bool isSelected(const QModelIndex &index) //模型索引为 index 的项是否被选中
QModelIndexList selectedIndexes() //返回所有被选择项的模型索引列表,列表未排序
QModelIndexList selectedRows(int column = 0) //返回 column 列所有被选择项的模型索引列表
QModelIndexList selectedColumns(int row = 0) //返回 row 行所有被选择项的模型索引列表

其中,QModelIndexList 就是 QList,是模型索引列表。

QItemSelectionModel 有几个函数用于清除选择,例如取消选择 QTableView 表格中被选择的单元格,各函数的功能和触发的信号见注释。

void clear() //清除选择模型,会触发 selectionChanged()和 currentChanged()信号
void clearCurrentIndex() //清除当前索引,会触发 currentChanged()信号
void clearSelection() //清除所有选择,会触发 selectionChanged()信号

2.信号

QItemSelectionModel 定义了几个信号。选择的当前项发生变化时会触发 currentChanged()信号,信号函数定义如下:

void currentChanged(const QModelIndex &current, const QModelIndex &previous) 

其中,current 是当前项的模型索引,previous 是之前项的模型索引。

选择发生变化时,例如在 QTableView 视图组件上选择多个单元格,或取消选择一些单元格, 都会触发 selectionChanged()信号。信号函数定义如下:

void selectionChanged(const QItemSelection &selected, const QItemSelection &deselected) 

其中,selected 是被选择的项,deselected 是被取消选择的项,它们都是 QItemSelection 类型。 QItemSelection 类的 indexes()返回一个模型索引列表。

五. 示例程序功能实现

1.界面设计

本示例的窗口从 QMainWindow 继承而 来,工作区的 QTableView 和 QPlainTextEdit 组件采用水平分割布局。在 Action 编辑器中 创建下图所示的一些 Action,用 Action 创建工具栏上的按钮。状态栏上的几个 QLabel 组件是在窗口的构造函数里创建的,用于显 示当前文件名称、当前单元格行号和列号, 以及当前单元格的内容。

将窗口上的 QTableView 组件命名为 tableView,将 alternatingRowColors 属性设置为 true,其他属性都用默认值,即允许编辑、允许扩展选择多个单元格。

2.窗口类定义和初始化

为了实现本示例的功能,我们在窗口类 MainWindow 里新增了一些变量和函数的定义,窗口类 MainWindow 的定义代码如下:

#define FixedColumnCount 6 //文件固定为 6 列
class MainWindow : public QMainWindow 
{ 
 Q_OBJECT 
private: 
 //用于状态栏的信息显示
 QLabel *labCurFile; //当前文件
 QLabel *labCellPos; //当前单元格行列号
 QLabel *labCellText; //当前单元格内容
 QStandardItemModel *m_model; //数据模型
 QItemSelectionModel *m_selection; //选择模型
 void iniModelData(QStringList &aFileContent); //从StringList初始化数据模型内容
public: 
 MainWindow(QWidget *parent = 0); 
private slots: 
 //自定义槽函数,与 QItemSelectionModel 的 currentChanged()信号连接
 void do_currentChanged(const QModelIndex &current, const QModelIndex &previous); 
private: 
 Ui::MainWindow *ui; 
}; 

MainWindow 中定义了数据模型变量 m_model 和选择模型变量 m_selection。

函数 iniModelData()用于在打开文件时,从一个 QStringList 变量的内容初始化数据模型的内容。 自定义槽函数 do_currentChanged()将会与选择模型 m_selection 的 currentChanged()信号关联, 用于在视图组件上选择单元格发生变化时更新状态栏的信息。

MainWindow 类的构造函数代码如下:

MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWindow) 
{ 
 ui->setupUi(this); 
 m_model = new QStandardItemModel(2,FixedColumnCount,this); //创建数据模型
 m_selection = new QItemSelectionModel(m_model,this); //创建选择模型,并设置数据模型
 //选择当前单元格变化时的信号与槽
 connect(m_selection,&QItemSelectionModel::currentChanged, 
 this,&MainWindow::do_currentChanged); 
 //为 tableView 设置数据模型和选择模型
 ui->tableView->setModel(m_model); //设置数据模型
 ui->tableView->setSelectionModel(m_selection); //设置选择模型
 ui->tableView->setSelectionMode(QAbstractItemView::ExtendedSelection); 
 ui->tableView->setSelectionBehavior(QAbstractItemView::SelectItems); 
 setCentralWidget(ui->splitter); 
 //创建状态栏组件
 labCurFile = new QLabel("当前文件:",this); 
 labCurFile->setMinimumWidth(200); 
 labCellPos = new QLabel("当前单元格:",this); 
 labCellPos->setMinimumWidth(180); 
 labCellPos->setAlignment(Qt::AlignHCenter); 
 labCellText = new QLabel("单元格内容:",this); 
 labCellText->setMinimumWidth(150); 
 ui->statusBar->addWidget(labCurFile); 
 ui->statusBar->addWidget(labCellPos); 
 ui->statusBar->addWidget(labCellText); 
}

构造函数首先创建数据模型 m_model,创建选择模型时需要传递一个数据模型变量作为其参数。这样,选择模型 m_selection 就与数据模型 m_model 关联,用于跟踪数据模型的项选择操作。

创建数据模型和选择模型后,为界面上的视图组件 tableView 设置数据模型和选择模型:

ui->tableView->setModel(m_model); //设置数据模型
ui->tableView->setSelectionModel(m_selection); //设置选择模型

构造函数里还将自定义槽函数 do_currentChanged()与 m_selection 的 currentChanged()信号关联, 在视图组件 tableView 的当前单元格发生变化时,在状态栏上显示单元格的信息, 槽函数代码如下:

void MainWindow::do_currentChanged(const QModelIndex &current, const QModelIndex &previous) 
{ 
 Q_UNUSED(previous); 
 if (current.isValid()) 
 { 
 labCellPos->setText(QString::asprintf("当前单元格:%d 行,%d 列",current.row(),current.column())); 
 QStandardItem *aItem= m_model->itemFromIndex(current); 
 //通过模型索引获得项的 QStandardItem 对象指针
 labCellText->setText("单元格内容:" +aItem->text()); 
 QFont font= aItem->font(); 
 ui->actFontBold->setChecked(font.bold()); 
 } 
} 
 

在这个槽函数里,参数 current 是当前单元格的模型索引,通过这个模型索引,可以从数据模型中获取项的 QStandardItem 对象指针:

QStandardItem *aItem =m_model->itemFromIndex(current);

获得项的对象指针后,就可以通过 QStandardItem 的接口函数访问其各种属性,如文字、字体、对齐方式等。

3.从文本文件导入数据

QStandardItemModel 的数据可以是程序生成的内存中的数据,也可以来源于文件。例如,在实际数据处理中,有些数据经常是以纯文本格式保存的,它们有固定的列数, 每一列是一项数据, 实际构成一个二维数据表。如图所示的是本示例程序要打开的一个纯文本文件的内容,文件的第一行是数据列的文字标题,相当于数据表的表头,以行存储数据,以制表符分隔每列数据。

在示例程序运行时,点击窗口工具栏上的“打开文件”按钮,需要选择一个这样的文件,将数据导入数据模型,然后就可以在界面上显示和编辑了。图中的数据有 6 列,第一列是整数,第二到四列是浮点数,第五列是文字,第六列是逻辑型变量(“1”表示 true)。

下面是“打开文件”按钮关联的槽函数代码:

void MainWindow::on_actOpen_triggered() 
{ 
 QString curPath=QCoreApplication::applicationDirPath(); //获取应用程序的路径
 QString aFileName=QFileDialog::getOpenFileName(this,"打开一个文件",curPath, 
 "数据文件(*.txt);;所有文件(*.*)"); 
 if (aFileName.isEmpty()) 
 return; 
 QStringList aFileContent; 
 QFile aFile(aFileName); 
 if (aFile.open(QIODevice::ReadOnly | QIODevice::Text)) //以只读文本方式打开文件
 { 
 QTextStream aStream(&aFile); //用文本流读取文件
 ui->plainTextEdit->clear(); 
 while (!aStream.atEnd()) 
 { 
 QString str=aStream.readLine(); //读取文件的一行
 ui->plainTextEdit->appendPlainText(str); 
 aFileContent.append(str); 
 } 
 aFile.close(); 
 labCurFile->setText("当前文件:"+aFileName); //状态栏显示
 ui->actAppend->setEnabled(true); 
 ui->actInsert->setEnabled(true); 
 ui->actDelete->setEnabled(true); 
 ui->actSave->setEnabled(true); 
 iniModelData(aFileContent); //用字符串列表内容初始化数据模型的数据
 } 
} 

这段程序在打开一个文件后,逐行读取文件的内容,并将其添加到一个临时的 QStringList 类型的变量 aFileContent 里。然后调用自定义函数 iniModelData(),用 aFileContent 的内容初始化数据模型的数据。下面是函数 iniModelData()的代码。

void MainWindow:: iniModelData(QStringList &aFileContent) 
{ 
 int rowCnt= aFileContent.size(); //文本行数,第一行是标题
 m_model->setRowCount(rowCnt-1); //实际数据行数
 QString header= aFileContent.at(0); //第一行是表头文字
 //以一个或多个空格、制表符等分隔符隔开的字符串,分割为一个 StringList 
 QStringList headerList= header.split(QRegularExpression("\\s+"),Qt::SkipEmptyParts); 
 m_model->setHorizontalHeaderLabels(headerList); //设置表头文字
 //设置表格数据
 int j; 
 QStandardItem *aItem; 
 for (int i=1;i<rowCnt;i++) 
 { 
 QString aLineText= aFileContent.at(i); //获取数据区的一行
 //以一个或多个空格、制表符等分隔符隔开的字符串,分割为一个 StringList 
 QStringList tmpList= aLineText.split(QRegularExpression("\\s+"), 
 Qt::SkipEmptyParts); 
 for (j=0; j<FixedColumnCount-1; j++) 
 { //不包含最后一列
 aItem= new QStandardItem(tmpList.at(j)); 
 m_model->setItem(i-1,j,aItem); 
 } 
 aItem= new QStandardItem(headerList.at(j)); //最后一列
 aItem->setCheckable(true); //设置为 Checkable 
 aItem->setBackground(QBrush(Qt::yellow)); 
 if (tmpList.at(j) == "0") 
 aItem->setCheckState(Qt::Unchecked); 
 else 
 aItem->setCheckState(Qt::Checked); 
 m_model->setItem(i-1,j,aItem); 
 } 
} 

传递来的参数 aFileContent 是文本文件所有行构成的字符串列表,文件的每一行是 aFileContent 的一行字符串,第一行是表头文字,数据从第二行开始。

获取字符串列表 aFileContent 的第一行文字,将其赋给字符串变量 header,然后用 QString::split() 函数将 header 的字符串分割成一个 QStringList 对象 headerList,再将其设置为数据模型的表头。

函数 QString::split()根据某个特定的符号将字符串进行分割,例如,header 是数据列的标题,每个标题之间通过一个或多个制表符分隔,其内容是:

测深(m)垂深(m)方位(°)总位移(m)固井质量测井取样

通过上面的 split()函数的操作,得到一个字符串列表 headerList,它有 6 行,就是这 6 个列标题。然后就可以用函数 setHorizontalHeaderLabels()将这个字符串列表设置为水平表头的内容。

同样,在逐行获取字符串后,也采用函数 split()进行分割,为每个数据创建一个 QStandardItem 类型对象,并将其赋给数据模型作为某行某列的项。

数据模型 m_model 以二维表格的形式存储项,每个项对应视图组件 tableView 的一个单元格。不仅可以设置项的显示文字,还可以设置图标、背景色等特性。用 QStandardItem::setData()函数还 可以为项设置用户数据。

数据文件的最后一列是逻辑型数据,对应的项用 QStandardItem::setCheckable()函数设置为可复用的,那么在 tableView 上显示时,单元格里就会出现复选框。

4.数据修改

当视图组件 tableView 设置为可编辑时,双击一个单元格就可以修改其内容,对于带有复选框的单元格,改变复选框的勾选状态就可以修改单元格关联项的复选状态。

在示例主窗口工具栏上有“添加行”“插入行”“删除行”按钮,可实现相应的编辑操作,这些操作都是直接针对数据模型的,数据模型被修改后,数据会直接在 tableView 上显示出来。

在数据模型的最后添加一行,其实现代码如下:

void MainWindow::on_actAppend_triggered() 
{ //“添加行”按钮
 QList<QStandardItem*> aItemList; 
 QStandardItem *aItem; 
 for(int i=0; i<FixedColumnCount-1; i++) //不包含最后一列
 { 
 aItem= new QStandardItem("0"); 
 aItemList<<aItem; //添加到列表
 } 
 //获取最后一列的表头文字
 QString str= m_model->headerData(m_model->columnCount()-1, Qt::Horizontal, 
 Qt::DisplayRole).toString(); 
 aItem= new QStandardItem(str); 
 aItem->setCheckable(true); 
 aItemList<<aItem; //添加到列表
 
 m_model->insertRow(m_model->rowCount(),aItemList); //插入一行
 QModelIndex curIndex=m_model->index(m_model->rowCount()-1,0); //获取模型索引
 m_selection->clearSelection(); 
 m_selection->setCurrentIndex(curIndex,QItemSelectionModel::Select); 
}

使用 QStandardItemModel::insertRow()函数插入一行,其函数原型是:

void insertRow(int row, const QList<QStandardItem *> &items) 

这表示在 row 行之前插入一行,若 row 值等于或大于总行数,则在最后添加一行。

“插入行”按钮的功能是在当前行的前面插入一行,实现代码与“添加行”的类似,就不展示了。

“删除行”按钮的功能是删除当前行,其槽函数代码如下:

void MainWindow::on_actDelete_triggered() 
{//“删除行”按钮
 QModelIndex curIndex= m_selection->currentIndex(); //获取当前单元格的模型索引
 if (curIndex.row() == m_model->rowCount()-1) //最后一行
 m_model->removeRow(curIndex.row()); //删除最后一行
 else 
 {
   m_model->removeRow(curIndex.row()); //删除一行,并重新设置当前选择项
   m_selection->setCurrentIndex(curIndex,QItemSelectionModel::Select); 
 } 
} 

程序是从选择模型中获取当前单元格的模型索引,然后从模型索引中获取行号,再调用removeRow()删除指定的行。

5.项的格式设置

工具栏上有 3 个设置单元格文字对齐方式的按钮,还有一个设置粗体的按钮。当在 tableView 中选择多个单元格时,可以同时设置多个单元格的格式, 实质上设置的是单元格关联的项的特性。例如,“居左”按钮的代码如下:

void MainWindow::on_actAlignLeft_triggered() 
{//设置文字居左对齐
 if (!m_selection->hasSelection()) //没有选择的项
 return; 
//获取选择的单元格的模型索引列表,可以多选
 QModelIndexList IndexList= m_selection->selectedIndexes(); 
 for (int i=0; i<IndexList.count(); i++) 
 { 
 QModelIndex aIndex= IndexList.at(i); //获取一个模型索引
 QStandardItem* aItem= m_model->itemFromIndex(aIndex); //获取一个单元格的项
 aItem->setTextAlignment(Qt::AlignLeft | Qt::AlignVCenter); 
 } 
}

函数 QItemSelectionModel::selectedIndexes()返回选择单元格的模型索引列表,通过此列表可以获 取每个选择的单元格的模型索引,通过模型索引就可以获取数据模型中的项,然后通过 QStandardItem 的接口函数就可以设置项的特性,例如用 setTextAlignment()函数设置文字对齐方式。

“粗体”按钮用于设置单元格的文字是否为粗体。在选择单元格时,actFontBold 的复选状态会根据当前单元格的字体是否为粗体自动更新。actFontBold 的 triggered(bool)信号的槽函数代码如下,与设置对齐方式的代码类似。

void MainWindow::on_actFontBold_triggered(bool checked) 
{//设置字体为粗体
 if (!m_selection->hasSelection()) 
 return; 
 //获取选择单元格的模型索引列表
 QModelIndexList selectedIndex= m_selection->selectedIndexes(); 
 for (int i=0; i<selectedIndex.count(); i++) 
 { 
 QModelIndex aIndex= selectedIndex.at(i); //获取一个模型索引
 QStandardItem* aItem= m_model->itemFromIndex(aIndex); //获取项
 QFont font= aItem->font(); 
 font.setBold(checked); 
 aItem->setFont(font); 
 } 
} 

6.遍历数据模型

在视图组件上的修改都会自动更新到数据模型里,点击工具栏上的“数据预览”按钮可以遍历数据模型,将模型的数据内容显示到组件 plainTextEdit 里,其实现代码如下:

void MainWindow::on_actModelData_triggered() 
{//模型数据导出到 plainTextEdit 中显示
 ui->plainTextEdit->clear(); 
 QStandardItem *aItem; 
 QString str; 
 //获取表头文字
 for (int i=0; i<m_model->columnCount(); i++) 
 { 
 aItem= m_model->horizontalHeaderItem(i); //获取表头的一个项
 str=str+aItem->text()+"\t"; //用制表符分隔文字
 } 
 ui->plainTextEdit->appendPlainText(str); 
 //获取数据区的每行
 for (int i=0; i<m_model->rowCount(); i++) 
 { 
 str=""; 
 for(int j=0; j<m_model->columnCount()-1; j++) 
 { 
 aItem= m_model->item(i,j); 
 str= str+aItem->text()+QString::asprintf("\t"); //以制表符分隔
 } 
 aItem= m_model->item(i, FixedColumnCount-1); //最后一行是逻辑型数据
 if (aItem->checkState() == Qt::Checked) 
 str= str+"1"; 
 else 
 str= str+"0"; 
 ui->plainTextEdit->appendPlainText(str); 
 } 
} 

本示例将 QStandardItemModel 模型和 QTableView 视图组件结合,用于编辑二维表格数据。如果 QStandardItemModel 存储的是一维数据, 那么它可以和 QListView 组件结合, 用于编辑列表数据,且对于每个列表项可以设置图标和复选框。

从原理上来讲,QStandardItemModel 也可以存储树状结构的项数据,与 QTreeView 结合以目录树形式显示数据,实现起来稍微复杂一点,我们就不设计示例来演示了。

(四). 自定义代理

在模型/视图结构中,代理的作用就是在视图组件进入编辑状态编辑某个项时,提供一个临时的编辑器用于数据编辑,编辑完成后再把数据提交给数据模型。例如,在 QTableView 组件上双击一个单元格时,代理会提供一个临时的编辑器,默认是 QLineEdit 编辑框,在这个编辑框里修改项的文字,按 Enter 键或焦点移动到其他单元格时完成编辑,编辑框内的文字会保存到数据模型。

QTableView 等视图组件的代理默认提供的编辑器是 QLineEdit 编辑框,在编辑框里可以输入任何数据,所以比较通用。但是在有些情况下,我们希望根据数据类型使用不同的编辑器,例如在前面的示例数据中,第一列“测深”列是整数,使用 QSpinBox 组件作为编辑器更合适, “垂深”“方位”“总位移”这几列是浮点数, 使用 QDoubleSpinBox 组件更合适,而“固井质量” 列使用一个 QComboBox 组件从一个列表中选择输入更合适。若要实现这样的功能,就需要设计自定义代理。

一. 自定义代理的功能

若要替换 QTableView 组件提供的默认代理组件,就需要为 QTableView 组件的某列或某个单元格设置自定义代理。自定义代理类需要从 QStyledItemDelegate 类继承,创建自定义代理类的实例后,再将其设置为整个视图组件或视图组件的某行或某列的代理,以替代默认代理的功能。

QAbstractItemView 类定义了设置自定义代理的 3 个函数,函数定义如下:

void setItemDelegate(QAbstractItemDelegate *delegate) 
void setItemDelegateForColumn(int column, QAbstractItemDelegate *delegate) 
void setItemDelegateForRow(int row, QAbstractItemDelegate *delegate) 

其中,delegate 是创建的自定义代理类的实例对象。函数 setItemDelegate()将 delegate 设置为整个视图组件的代理,函数 setItemDelegateForColumn()为视图组件的某一列设置自定义代理, 函数 setItemDelegateForRow()为视图组件的某一行设置自定义代理。

QStyledItemDelegate 是视图组件使用的默认的代理类,自定义代理类需要从 QStyledItemDelegate 类继承。在前面示例的基础上设新示例,为窗口上的 QTableView 组件的前 5 列设置了相应的自定义代理,程序运行时单元格处于编辑状态的效果如图所示。

二. QStyledItemDelegate 类

QStyledItemDelegate 是视图组件使用的默认代理类,一般使用 QStyledItemDelegate 作为自定义代理类的父类。要自定义一个代理类,必须重新实现 QStyledItemDelegate 中定义的 4个虚函数。这 4个函数是由模型/视图系统自动调用的。假设为 QTableView 组件的某一列设置了一个自定义代理,我们以此为例说明这 4 个函数的功能。

1.函数 createEditor()

函数 createEditor()可创建用于编辑模型数据的界面组件,称为代理编辑器,例如 QSpinBox 组件,或 QComboBox 组件。其函数原型定义如下:

QWidget *QStyledItemDelegate::createEditor(QWidget *parent, 
 const QStyleOptionViewItem &option, const QModelIndex &index) 

其中,parent 是要创建的组件的父组件,一般就是窗口对象;option 是项的一些显示选项,是 QStyleOptionViewItem 类型的,包含字体、对齐方式、背景色等属性;index 是项在数据模型中的模型索引,通过 index->model()可以获取项所属数据模型的对象指针。

在 QTableView视图组件上双击一个单元格使其进入编辑状态时,系统就会自动调用 createEditor()创建代理编辑器,例如创建 QSpinBox 组件,然后将其显示在单元格里。

2.函数 setEditorData()

函数 setEditorData()的功能是从数据模型获取项的某个角色(一般是 EditRole 角色)的数据,然后将其设置为代理编辑器上显示的数据。其函数原型定义如下:

void QStyledItemDelegate::setEditorData(QWidget *editor, const QModelIndex &index)

参数 editor 就是前面用函数 createEditor()创建的代理编辑器,通过 index->model()可以获取项所属数据模型的对象指针,从而获取项的数据,然后将其显示在代理编辑器上。

3.函数 setModelData()

完成对当前单元格的编辑, 例如输入焦点移到其他单元格时,系统会自动调用函数 setModelData(), 其功能是将代理编辑器里的输入数据保存到数据模型的项里。其函数原型定义如下:

void QStyledItemDelegate::setModelData(QWidget *editor, QAbstractItemModel *model, 
 const QModelIndex &index) 

其中,editor 是代理编辑器,model 是数据模型,index 是所编辑的项在模型中的模型索引。

4.函数 updateEditorGeometry()

视图组件在界面上显示代理编辑器时,需要调用 updateEditorGeometry()函数为组件设置合适的大小,例如在一个单元格里显示一个 QSpinBox 代理编辑器时,一般将其设置为单元格的大小。其函数原型定义如下:

void QStyledItemDelegate::updateEditorGeometry(QWidget *editor, 
 const QStyleOptionViewItem &option, const QModelIndex &index) 

其中,变量 option->rect 是 QRect 类型,表示代理编辑器的建议大小。一般将代理编辑器大小设置为建议大小即可,即用下面的一行代码:

editor->setGeometry(option.rect);

三. 设计自定义代理类

首先将前面()中的示例项目复制为新的项目,我们将在新项目中设计 3 个自定义代理类。

1.设计代理类 TFloatSpinDelegate

打开 New File or Project 对话框,选择新建 C++ Class,会出现新建 C++类对话框。Class name是要新建的类的名称,我们设置为 TFloatSpinDelegate,设置类名称后,下面的 Header file 和 Source file 编辑框里会自动设置好头文件名称和源程序文件名称。Base class 是基类名称,必须设置为 QStyledItemDelegate。在对话框上还有一些复选框,要勾选Add Q_OBJECT 复选框,因为要在 TFloatSpinDelegate 类中插入 Q_OBJECT 宏。

结束创建 C++类的向导后,Qt Creator 就会自动创建类的头文件和源程序文件,并将其添加到项目里。头文件 tfloatspindelegate.h 中有 TFloatSpinDelegate 类的定义,但是只有构造函数,我们在其中再加入必须实现的 4 个函数的定义,TFloatSpinDelegate 类的完整定义如下:

#include <QStyledItemDelegate> 
class TFloatSpinDelegate : public QStyledItemDelegate 
{ 
 Q_OBJECT 
public: 
 explicit TFloatSpinDelegate(QObject *parent = nullptr); 
 //自定义代理必须重新实现以下 4 个函数
 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; 
};

下面是源程序文件 tfloatspindelegate.c 中的实现代码。

QWidget *TFloatSpinDelegate::createEditor(QWidget *parent, 
 const QStyleOptionViewItem &option, const QModelIndex &index) const 
{ 
 Q_UNUSED(option); 
 Q_UNUSED(index); 
 QDoubleSpinBox *editor = new QDoubleSpinBox(parent); //新建代理编辑器
 editor->setFrame(false); //组件的属性设置
 editor->setMinimum(0); 
 editor->setMaximum(20000); 
 editor->setDecimals(2); //显示两位小数
 return editor; 
} 

void TFloatSpinDelegate::setEditorData(QWidget *editor, const QModelIndex &index) const 
{ 
 float value = index.model()->data(index, Qt::EditRole).toFloat(); //从模型获取数据
 QDoubleSpinBox *spinBox = static_cast<QDoubleSpinBox*>(editor); 
 spinBox->setValue(value); //设置为代理编辑器的值
} 

void TFloatSpinDelegate::setModelData(QWidget *editor, 
 QAbstractItemModel *model, const QModelIndex &index) const 
{ 
 QDoubleSpinBox *spinBox = static_cast<QDoubleSpinBox*>(editor); 
 float value = spinBox->value(); 
 QString str = QString::asprintf("%.2f",value); 
 model->setData(index, str, Qt::EditRole); //保存到数据模型
} 

void TFloatSpinDelegate::updateEditorGeometry(QWidget *editor, 
 const QStyleOptionViewItem &option, const QModelIndex &index) const 
{ 
 Q_UNUSED(index); 
 editor->setGeometry(option.rect); //设置组件的大小
} 

函数 createEditor()里创建了一个QDoubleSpinBox 组件作为代理编辑器, 并且设置了其数值范围、小数位数等属性;函数 setEditorData()里获取了数据模型的数据,并将其显示到代理编辑器上;  函数 setModelData()将代理编辑器里的数据保存到数据模型。

用同样的方法再设计自定义代理类 TSpinBoxDelegate,它使用 QSpinBox 组件作为代理编辑器, 用于编辑整数数据。

2.设计代理类 TComboBoxDelegate

再用新建 C++类向导创建一个类TComboBoxDelegate, 在TComboBoxDelegate 类的定义中增加自定义代理类必须实现的 4 个函数。为了使TComboBoxDelegate类更通用, 在 TComboBoxDelegate 类中再定义两个私有变量和一个公有函数。TComboBoxDelegate 类的完整定义如下:

class TComboBoxDelegate : public QStyledItemDelegate 
{ 
 Q_OBJECT 
private: 
 QStringList m_itemList; //选项列表
 bool m_editable; //是否可编辑
public: 
 explicit TComboBoxDelegate(QObject *parent = nullptr); 
 void setItems(QStringList items, bool editable); //初始化设置列表内容,是否可编辑
 //自定义代理必须重新实现以下 4 个函数
 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; 
}; 

自定义函数 setItems()用于设置变量 m_itemList 和 m_editable 的值,在函数 createEditor()里动态 创建QComboBox组件时, 用于设置下拉列表的内容以及是否可以编辑。这样,TComboBoxDelegate 就是比较通用的一个类,文件 tcomboboxdelegate.cpp 中的实现代码如下:

void TComboBoxDelegate::setItems(QStringList items, bool editable) 
{ 
 m_itemList=items; 
 m_editable=editable; 
} 

QWidget *TComboBoxDelegate::createEditor(QWidget *parent, 
 const QStyleOptionViewItem &option, const QModelIndex &index) const 
{ 
 Q_UNUSED(option); 
 Q_UNUSED(index); 
 QComboBox *editor = new QComboBox(parent); 
 editor->setEditable(m_editable); //是否可编辑
 for (int i=0; i<m_itemList.count(); i++) //从字符串列表初始化下拉列表
 editor->addItem(m_itemList.at(i)); 
 return editor; 
} 

void TComboBoxDelegate::setEditorData(QWidget *editor, const QModelIndex &index) const 
{ 
 QString str = index.model()->data(index, Qt::EditRole).toString(); 
 QComboBox *comboBox = static_cast<QComboBox*>(editor); 
 comboBox->setCurrentText(str); 
} 

void TComboBoxDelegate::setModelData(QWidget *editor, QAbstractItemModel *model, 
 const QModelIndex &index) const 
{ 
 QComboBox *comboBox = static_cast<QComboBox*>(editor); 
 QString str = comboBox->currentText(); 
 model->setData(index, str, Qt::EditRole); 
} 

void TComboBoxDelegate::updateEditorGeometry(QWidget *editor, 
 const QStyleOptionViewItem &option,const QModelIndex &index) const 
{ 
 Q_UNUSED(index); 
 editor->setGeometry(option.rect); 
} 

四. 使用自定义代理类

设计好 3 个自定义代理类后,我们就可以在程序中对界面上的 QTableView 组件使用自定义代理。首先要在 MainWindow 类中定义 3 个自定义代理对象变量,定义如下:

private: 
 TSpinBoxDelegate *intSpinDelegate; //用于编辑整数
 TFloatSpinDelegate *floatSpinDelegate; //用于编辑浮点数
 TComboBoxDelegate *comboDelegate; //用于列表选择

在 MainWindow 的构造函数中创建这 3 个自定义代理对象,然后将其设置为 tableView 的某些列的代理。构造函数主要的代码如下,其中省略了创建状态栏上的 QLabel 组件等的一些不重要的代码。

MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWindow) 
{ 
 ui->setupUi(this); 
 m_model = new QStandardItemModel(2,FixedColumnCount,this); //创建数据模型
 m_selection = new QItemSelectionModel(m_model,this); //创建选择模型
 connect(m_selection,&QItemSelectionModel::currentChanged, 
 this,&MainWindow::do_currentChanged); 
 ui->tableView->setModel(m_model); //设置数据模型
 ui->tableView->setSelectionModel(m_selection); //设置选择模型
 ui->tableView->setSelectionMode(QAbstractItemView::ExtendedSelection); 
 ui->tableView->setSelectionBehavior(QAbstractItemView::SelectItems); 
 intSpinDelegate= new TSpinBoxDelegate(this); 
 ui->tableView->setItemDelegateForColumn(0, intSpinDelegate); //测深
 floatSpinDelegate = new TFloatSpinDelegate(this); 
 ui->tableView->setItemDelegateForColumn(1, floatSpinDelegate); //垂深
 ui->tableView->setItemDelegateForColumn(2, floatSpinDelegate); //方位
 ui->tableView->setItemDelegateForColumn(3, floatSpinDelegate); //总位移
 comboDelegate = new TComboBoxDelegate(this); 
 QStringList strList; 
 strList<<"优"<<"良"<<"一般"<<"不合格"; 
 comboDelegate->setItems(strList,false); 
 ui->tableView->setItemDelegateForColumn(4, comboDelegate); //固井质量
} 

这样增加了自定义代理功能后,在编辑“测深”列时,会在单元格的位置出现一个 SpinBox 用于输入整数;在编辑第二到四列的浮点数时,会出现一个 DoubleSpinBox 用于输入浮点数;在编辑“固井质量”列时,会出现一个下拉列表框用于从下拉列表中选择一个字符串。

QTableView 的子类 QTableWidget 中也可以使用自定义代理。

(六). QFileSystemModel 和 QTreeView

QFileSystemModel为本机的文件系统提供一个模型, 结合使用 QFileSystemModel和 QTreeView, 可以以目录树的形式显示本机的文件系统,如同 Windows 的资源管理器一样。使用 QFileSystemModel 提供的接口函数,我们可以创建目录、删除目录、重命名目录,可以获得文件名称、目录名称、文件大小等,还可以获得文件的详细信息。 要通过QFileSystemModel获得本机的文件系统,需要用QFileSystemModel的函数setRootPath() 设置一个根目录,例如:

QFileSystemModel *model = new QFileSystemModel(this); 
model->setRootPath(QDir::currentPath()); //设置应用程序的当前目录为模型的根目录

使用 QFileSystemModel 作为模型以及 QTreeView、QListView、QTableView 作为主要组件而设计的示例运行时界面如图所示。TreeView 以目录树的形式显示本机的文件系统, 在 TreeView 上点击一个目录时,右边的 ListView 和 TableView 显示该目录下的目录和文件。在 TreeView 上点击一个目录或文件节点时,下方的几个标签会显示当前节点的信息。

示例中可以设置 QFileSystemModel 模型的根目录,设置是否只显示目录,还可以对显示的文件根据文件名后缀进行过滤。

一. QFileSystemModel 类

QFileSystemModel 为本机的文件系统提供一个模型,可用于访问本机的文件系统。它提供了一些接口函数,这些接口函数可以设置显示选项,获取目录或文件信息,以及创建或删除文件夹等。

1.设置根目录

函数 setRootPath()用于设置一个根目录,QFileSystemModel 模型就只显示这个根目录下的文件系统。

QModelIndex setRootPath(const QString &newPath)

有两个函数用于返回 QFileSystemModel 的当前根目录。

QDir rootDirectory() //以 QDir 类型返回当前根目录
QString rootPath() //以 QString 类型返回当前根目录

用函数 index()可以根据完整的目录或文件名字符串,返回目录或文件在文件系统模型中的模型索引。

QModelIndex index(const QString &path, int column = 0) //返回目录或文件的模型索引

2.设置模型选项

(1)模型项的过滤器。函数 setFilter()用于设置一个过滤器,以控制 QFileSystemModel 模型包含的文件系统的项的类型,例如是否显示文件,是否显示隐藏文件等。其函数原型定义如下:

void setFilter(QDir::Filters filters) //设置模型数据项过滤器

参数 filters 是标志类型,是枚举类型 QDir::Filter 的枚举值组合。QDir::Filter 的常见枚举值如下:

• QDir::AllDirs:列出所有目录。函数 setFilter()设置的过滤器必须包含这个选项。

• QDir::Files:列出文件。

• QDir::Drives:列出驱动器。

• QDir::NoDotAndDotDot:不列出目录下的“.”和“..”特殊项。

• QDir::Hidden:列出隐藏的文件。

• QDir::System:列出系统文件。

(2)文件名过滤器。如果函数 setFilter()设置的过滤器允许列出文件,就可以用函数 setNameFilters() 设置文件名过滤器,使模型只显示特定类型的文件,例如只显示文件名后缀为.jpg 的文件。

void setNameFilters(const QStringList &filters) //设置文件名过滤器

参数 filters 是允许的文件名字符串列表,一般用通配符表示,例如“*.jpg”“*.h”“*.cpp”等。

函数 setNameFilterDisables()可以设置未通过文件名过滤器过滤的项的显示方式,定义如下:

void setNameFilterDisables(bool enable)

如果参数 enable 设置为 true,未通过文件名过滤器过滤的项只是被设置为禁用;如果参数 enable 设置为 false,未通过文件名过滤器过滤的项就被隐藏。

(3)模型选项。函数 setOption()可以设置模型的一些选项,函数定义如下:

void setOption(QFileSystemModel::Option option, bool on = true) 

参数 option 是枚举类型 QFileSystemModel::Option,各枚举值的含义如下。

• QFileSystemModel::DontWatchForChanges:不监视文件系统的变化,默认是监视。

• QFileSystemModel::DontResolveSymlinks:不解析文件系统的符号连接项,默认是解析。

• QFileSystemModel::DontUseCustomDirectoryIcons:不使用自定义的目录图标,默认是使用系统的图标。

3.获取目录和文件的信息

QFileSystemModel 可以返回模型中某个项的一些信息,有如下几个函数:

QIcon fileIcon(const QModelIndex &index) //返回项的图标
QFileInfo fileInfo(const QModelIndex &index) //返回项的文件信息
QString fileName(const QModelIndex &index) //返回不含路径的文件名或最后一级文件夹名称
QString filePath(const QModelIndex &index) //返回项的路径或包含路径的文件名
QDateTime lastModified(const QModelIndex &index) //返回项的最后修改日期
bool isDir(const QModelIndex &index) //判断项是不是一个文件夹
qint64 size(const QModelIndex &index) //返回文件的大小(字节数),若是文件夹,返回值为0 
QString type(const QModelIndex &index) //返回项的类型描述文字

这些函数都需要一个模型索引 index 作为输入参数,表示模型中的一个项。在 TreeView 中点击一个节点时可以获得当前节点的模型索引。

4.操作目录和文件

通过 QFileSystemModel 的接口函数,可以在文件系统中创建或删除文件夹,还可以删除文件。

QModelIndex mkdir(const QModelIndex &parent, const QString &name) //创建文件夹
bool rmdir(const QModelIndex &index) //删除文件夹
bool remove(const QModelIndex &index) //删除文件

用函数 mkdir()创建一个文件夹时需要指定一个父节点。函数 rmdir()用于删除模型索引 index 表示的文件夹,函数 remove()用于删除模型索引 index 表示的文件。注意,rmdir()和 remove()会从实际的文件系统中删除文件夹和文件。

如果不想修改文件系统,可以用函数 QFileSystemModel::setReadOnly()将模型设置为只读的。

二. QTreeView 类

QTreeView 是用于显示树状模型的视图组件,其与 QFileSystemModel 模型结合就可以显示本机的文件系统。QTreeView 组件的 Go to slot 对话框如图所示。QTreeView 新定义了两个信号, 在一个节点处展开子节点时会触发 expanded()信号,折叠子节点时会触发 collapsed()信号。在 QTreeView 组件上点击时,clicked()信号 会传递当前节点的模型索引,通过这个模型索引就可以用 QFileSystemModel 类的接口函数对这个节点表示的文件夹或文件进行各种操作。

QTreeView 组件的 clicked()信号只能传递当前节点的模型索引, 如果 QTreeView 组件允许多选操作,就需要使用 QItemSelectionModel 选择模型,通过选择模型获取所有被选节点的模型索引。

三. 示例程序功能实现

示例的窗口基类是 QMainWindow,我们删除了窗口上的菜单栏和状态栏,保留了工具栏。我们只设计了两个 Action,分别用于创建工具栏上的“设置根目录”按钮和“退出”按钮。

主窗口界面布局采用了两个分割条的设计,ListView 和 TableView 采用垂直分割布局,然后作为整体和左边的 TreeView 采用水平分割布局。水平分割布局再和下方显示信息的分组框在主窗口工作区中垂直布局。

1.模型/视图结构初始化

在窗口类 MainWindow 中只新定义了一个 QFileSystemModel 类型的私有变量 m_model。

private: 
 QFileSystemModel *m_model; //数据模型变量

在 MainWindow 类的构造函数中创建模型 m_model,并使其与界面上的视图组件关联,构建模型/视图结构。MainWindow 类的构造函数代码如下:

MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWindow) 
{ 
 ui->setupUi(this); 
 //将分割布局的垂直方向尺寸策略设置为扩展的
 ui->splitterMain->setSizePolicy(QSizePolicy::Expanding,QSizePolicy::Expanding); 
 //构建模型/视图结构
 m_model= new QFileSystemModel(this); 
 m_model->setRootPath(QDir::currentPath()); //设置根目录
 ui->treeView->setModel(m_model); //设置数据模型
 ui->listView->setModel(m_model); //设置数据模型
 ui->tableView->setModel(m_model); //设置数据模型
 //信号与槽关联,点击 treeView 的一个节点时,此节点设置为 listView 和 tableView 的根节点
 connect(ui->treeView, SIGNAL(clicked(QModelIndex)), 
 ui->listView, SLOT(setRootIndex(QModelIndex))); 
 connect(ui->treeView, SIGNAL(clicked(QModelIndex)), 
 ui->tableView, SLOT(setRootIndex(QModelIndex))); 
} 

在创建 QFileSystemModel 模型 m_model 后,程序里用静态函数 QDir::currentPath()获取应用程序的当前路径,设置为模型的根目录。如果没有用函数 QDir::setCurrent()设置过当前路径,QDir::currentPath()返回的就是本机的整个文件系统的根目录。

3个视图组件都使用函数setModel()将QFileSystemModel模型m_model设置为自己的数据模型。

函数 connect()设置信号与槽关联,实现的功能是:在点击 treeView 的一个节点时,此节点就设置为 listView 和 tableView 的根节点。因为 treeView 的 clicked(QModelIndex)信号会传递当前节点的模型索引,将此模型索引传递给 listView 和 tableView 的公有槽 setRootIndex(QModelIndex), listView 和 tableView 就会显示此节点下的目录和文件。

2.显示节点信息

为窗口上的 treeView 的 clicked()信号编写槽函数,显示目录树上当前节点的一些信息。

void MainWindow::on_treeView_clicked(const QModelIndex &index) 
{ 
 ui->chkIsDir->setChecked(m_model->isDir(index)); //是不是文件夹
 ui->labPath->setText(m_model->filePath(index)); //完整路径或文件名
 ui->labType->setText(m_model->type(index)); //类型描述文字
 ui->labFileName->setText(m_model->fileName(index)); //文件名或最后一级文件夹名称
 int sz=m_model->size(index)/1024; //目录的大小是 0 
 if (sz<1024) 
 ui->labFileSize->setText(QString("%1 KB").arg(sz)); 
 else 
 ui->labFileSize->setText(QString::asprintf("%.1f MB",sz/1024.0)); 
} 

函数的参数index 是目录树上当前节点的模型索引,通过这个模型索引,就可以用QFileSystemModel 的接口函数获取节点的各种信息。

3.模型设置

窗口工具栏上有一个“设置根目录”按钮,它关联的槽函数代码如下:

void MainWindow::on_actSetRoot_triggered() 
{“设置根目录”按钮
 QString dir = QFileDialog::getExistingDirectory(this, "选择目录", QDir::currentPath()); 
 if (!dir.isEmpty()) 
 { 
 m_model->setRootPath(dir); 
 ui->treeView->setRootIndex(m_model->index(dir)); //设置根目录
 } 
}

界面上还有一些组件可以设置文件系统模型的一些特性,例如设置是否只显示目录,使用文件名过滤。这些组件的相应槽函数代码如下:

void MainWindow::on_radioShowAll_clicked() 
{//“显示目录和文件”单选按钮
 ui->groupBoxFilter->setEnabled(true); 
 m_model->setFilter(QDir::AllDirs | QDir::Files |QDir::NoDotAndDotDot); 
} 

void MainWindow::on_radioShowOnlyDir_clicked() 
{//“只显示目录”单选按钮
 ui->groupBoxFilter->setEnabled(false); 
 m_model->setFilter(QDir::AllDirs | QDir::NoDotAndDotDot); //不列出文件
} 

void MainWindow::on_chkBoxEnableFilter_clicked(bool checked) 
{ //“文件名过滤”复选框
 m_model->setNameFilterDisables(!checked); //文件名过滤
 ui->comboFilters->setEnabled(checked); 
 ui->btnApplyFilters->setEnabled(checked); 
} 

void MainWindow::on_btnApplyFilters_clicked() 
{//“应用”按钮,应用文件名过滤
 QString flts= ui->comboFilters->currentText().trimmed(); 
 QStringList filter= flts.split(";",Qt::SkipEmptyParts); 
 m_model->setNameFilters(filter); 
} 

设置是否显示文件和目录的两个单选按钮的代码中使用了函数 QFileSystemModel::setFilter(),这个函数设置的参数中必须有 QDir::AllDirs。

“应用”按钮的槽函数代码中用到了函数 QString::split(),它将一个字符串按某个分隔符分隔为字符串列表,例如,字符串 flts 的内容是“*.h;*.cpp;*.ui”,分隔出的字符串列表就包含“*.h” “*.cpp”“*.ui” 3 个字符串。使用函数 setNameFilters()就可以使目录树上只显示这些类型的文件。

使用 QFileSystemModel 模型和 QTreeView 等视图组件可以获取和显示本机上的文件系统。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值