C++ Qt开发:StandardItemModel数据模型组件
Qt 是一个跨平台C++图形界面开发库,利用Qt可以快速开发跨平台窗体应用程序,在Qt中我们可以通过拖拽的方式将不同组件放到指定的位置,实现图形化开发极大的方便了开发效率,本章将重点介绍StandardItemModel数据模型组件的常用方法及灵活运用。
QStandardItemModel 是 Qt 中用于存储标准项数据的模型类之一,它继承自 QAbstractItemModel 类。这个模型提供了一种灵活的方式来组织和管理数据,适用于各种视图类(比如 QTreeView、QListView、QTableView 等)。该组件是标准的以项数据为单位的基于M/V模型的一种标准数据管理方式。
Model/View 是Qt中的一种数据编排结构,其中Model代表模型而View则代表视图,视图是显示和编辑数据的界面组件,而模型则是视图与原始数据之间的接口,通常该类结构都是用在数据库中较多,例如模型结构负责读取或写入数据库,视图结构则负责展示数据,其条理清晰,编写代码便于维护。Model/View架构是Qt中数据与界面分离的核心设计模式,为开发者提供了一种清晰而灵活的方式来管理和展示数据。
数据模型组件通常会配合TableView等相关组件一起使用,首先绘制UI界面,界面中包含顶部ToolBar组件,底部是一个TableView视图表格,最下方是一个PlainTextEdit文本框,如下图所示;

如上图所示ToolBar组件中我们绑定了一些快捷键及ICO图标,这些信息通过图形化的方式进行了关联;

1.1 初始化表格
为了能充分展示QStandardItemModel模型组件的使用,我们首先简单的的介绍一下该组件的常用方法与描述,下面是 QStandardItemModel 类的一些常用方法,说明和概述:
| 方法 | 描述 | 
|---|---|
| QStandardItemModel(int rows, int columns, QObject *parent = nullptr) | 构造函数,创建一个具有指定行数和列数的 QStandardItemModel对象。 | 
| virtual ~QStandardItemModel() | 虚析构函数,释放 QStandardItemModel对象及其所有子项。 | 
| int rowCount(const QModelIndex &parent = QModelIndex()) const | 返回指定父项的行数。如果 parent为无效索引,则返回根项的行数。 | 
| int columnCount(const QModelIndex &parent = QModelIndex()) const | 返回指定父项的列数。如果 parent为无效索引,则返回根项的列数。 | 
| QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const | 返回指定行、列和父项的索引。 | 
| QModelIndex parent(const QModelIndex &child) const | 返回指定子项的父项的索引。如果子项没有父项,则返回无效索引。 | 
| Qt::ItemFlags flags(const QModelIndex &index) const | 返回指定索引处项的标志,用于指示该项的状态和行为。 | 
| QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const | 返回指定索引处项的数据。 role参数指定要获取的数据的角色,如Qt::DisplayRole表示显示文本。 | 
| bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) | 设置指定索引处项的数据。如果设置成功,则返回 true,否则返回false。 | 
| bool insertRows(int row, int count, const QModelIndex &parent = QModelIndex()) | 在指定父项下插入行。返回 true表示成功。 | 
| bool removeRows(int row, int count, const QModelIndex &parent = QModelIndex()) | 从指定父项中移除行。返回 true表示成功。 | 
| QModelIndexList match(const QModelIndex &start, int role, const QVariant &value, int hits = 1, Qt::MatchFlags flags = Qt::MatchFlags(Qt::MatchStartsWithQt::MatchWrap)) const | 从模型中匹配指定的字符串等变量。 | 
| bool insertColumns(int column, int count, const QModelIndex &parent = QModelIndex()) | 在指定父项下插入列。返回 true表示成功。 | 
| bool removeColumns(int column, int count, const QModelIndex &parent = QModelIndex()) | 从指定父项中移除列。返回 true表示成功。 | 
| Qt::DropActions supportedDropActions() const | 返回模型支持的拖放操作。 | 
| Qt::DropActions supportedDragActions() const | 返回模型支持的拖动操作。 | 
| QStringList mimeTypes() const | 返回模型支持的 MIME 类型列表。 | 
| QMimeData *mimeData(const QModelIndexList &indexes) const | 返回包含指定索引项数据的 MIME 数据对象。 | 
| bool dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent) | 处理拖放操作中的 MIME 数据。返回 true表示成功。 | 
以上是 QStandardItemModel 类的一些常用方法,通过这些方法,可以对模型进行增删改查等操作,并与视图进行交互。
首先笔者先来演示一下如何将tableView组件与QStandardItemModel组件进行绑定操作,其实绑定很简单只需要调用ui->tableView->setModel即可将tableView组件与model数据集进行绑定,当绑定后,模型中的数据发生变化则会自动刷新到View组件中,我们就无需关心界面中的组件如何显示了,这个现实过程交给Model映射吧。
如下所示的代码片段是一个使用 QStandardItemModel 的例子,演示了如何创建一个带有表头和初始数据的 QTableView。
以下是代码片段的一些说明:
- 创建 QStandardItemModel对象,并设置列数为 3。
- 为表头设置标签,分别是 “账号”、”用户”、”年龄”。
- 将模型设置为 QTableView。
- 设置表头默认对齐方式为左对齐。
- 设置列宽,第一列宽度为 101,第二列宽度为 102。
- 循环添加数据到模型中,包括 “20210506”、”lyshark” 和 “24”。
这样,就创建了一个包含表头和数据的 QTableView,并将其显示在 MainWindow 中。
| // 默认构造函数 | 
运行后读者可观察TableView表格中的变化情况,如下图所示;

接着,我们来看下如何对本项目中UI表格进行初始化,在MainWindow构造函数中,我们首先创建一个QStandardItemModel用于存储表格数据,以及一个QItemSelectionModel用于处理表格中的选择操作,并将它们关联到TableView组件上。在窗口初始化时,除了打开文件的操作外,禁用了其他所有Action选项。创建状态栏组件,包括显示当前文件、当前单元格位置和单元格内容的QLabel组件。
| // 默认构造函数 | 
如上代码中,我们还将选择模型的currentChanged信号连接到了槽函数on_currentChanged上面,这个槽函数主要用于实现,当选择单元格变化时则响应,并将当前单元格变化刷新到底部的StatusBar组件上,代码如下所示;
| // 【选中单元格时响应】:选择单元格变化时的响应,通过在构造函数中绑定信号和槽函数实现触发 | 
读者可自行运行这段程序,当运行后首先会初始化表格的长度及宽度,且页面中禁用了其他按钮,只能选择打开文件选项,如下图所示;

1.2 打开文件
当读者点击打开文件时,首先会触发on_actionOpen_triggered槽函数,在该函数内,通过QCoreApplication::applicationDirPath()获取应用程序的路径,并通过QFileDialog::getOpenFileName()文件对话框让用户选择一个数据文件(*.txt)。如果用户选择了文件,就以只读文本方式打开该文件,读取文件内容到一个字符串列表 fFileContent 中,并显示到 plainTextEdit 文本框中。
当读取结束后,直接关闭文件,并调用 iniModelFromStringList 函数,该函数根据字符串列表的内容初始化数据模型。随即启用工具栏中的其他Action选项,包括保存、查看、追加、删除和插入。并在状态栏显示当前打开的文件路径。
该函数实现了打开文件后的一系列操作,包括读取文件内容、更新UI显示和初始化数据模型。
| // 【打开文件】:当工具栏中打开文件被点击后则触发 | 
在上述槽函数中并没有分析iniModelFromStringList(fFileContent)函数的具体实现细节,该函数用于从传入的字符串列表 aFileContent 中获取数据,并将数据初始化到 TableView 模型中。
具体步骤如下:
- 获取文本行数 rowCnt,第一行是标题。
- 设置模型的行数为实际数据行数 rowCnt-1,因为第一行是标题。
- 获取表头 header,并将其分割成一个字符串列表headerList,作为模型的水平表头标签。
- 循环处理每一行数据,分割每行的文本为一个字符串列表 tmpList。
- 对于每一行,循环处理每一列(不包括最后一列),为模型的某个行列位置设置 QStandardItem。
- 对于每行的最后一列,该列是可检查的,需要创建 QStandardItem,并设置为可检查状态。根据数据判断是否选中,并设置相应的检查状态。
- 将 QStandardItem设置到模型的相应行列位置。
这个函数主要完成了从字符串列表中获取数据并初始化到 TableView 模型的过程,包括表头的设置、数据的提取和状态的处理。
| // 【初始化填充TableView】:从传入的StringList中获取数据,并将数据初始化到TableView模型中 | 
读者可自行运行程序,当程序运行后默认只能点击打开按钮,点击打开按钮后可以选择项目中的data.txt文本文件,此时就可以将文本中的内容映射到组件中,其输出效果如下图所示;

1.3 保存文件
接着我们来看下保存文件与预览TableView视图的实现方法,其实保存文件与预览是一个功能,唯一的区别是保存文件刷新到文件中,而预览则是刷新到了PlainTextEdit文本框内,但其两个本质上是一个功能,此处笔者就以保存文件为例来说明如何实现的。
首先,在代码中同样是获取应用程序路径,同样是打开文件唯一不同的是这里使用了getSaveFileName也标志着是打开一个保存对话框,这里还使用了QFile::Open函数,并设置了QIODevice::ReadWrite写入模式,接着定义了QTextStream文本流,第一次循环将表头先追加到流中,最后model->rowCount()循环表格元素次数,并依次追加文本流到文件。
步骤总结起来如下:
- 获取当前应用程序的路径。
- 弹出保存文件对话框,让用户选择保存文件的路径和文件名。
- 如果用户未选择文件,则直接退出。
- 使用 QFile打开文件,以读写、覆盖原有内容的方式打开文件。
- 使用 QTextStream以文本流的方式读取文件。
- 获取表头文字,以制表符 \t\t分隔,写入文件。
- 获取数据区文字,对于每一行的每一列,以制表符 \t\t分隔,写入文件。最后一列根据选中状态写入1或0。
- 将表头文字和数据区文字分别追加到 plainTextEdit文本框中。
这个函数主要完成了将 TableView 模型中的数据保存到文件的过程,包括文件的选择、打开和写入。
| // 【保存文件】:当保存文件被点击后触发 | 
运行程序后,读者可以点击保存文件按钮,并将其保存到任意位置,此时打开文件,可看到如下图所示的效果;

1.3 插入与删除
首先来解释一下如何添加一行新的行,其实添加与插入原理一致,唯一的区别在于,添加一行新的数据是在行尾加入,这个可以使用model->columnCount()来得到行尾,而插入则是在选中当前selection->currentIndex()行的下方加入行,其他的方式是完全一致的。
如下所示的函数用于在 TableView 中追加一行数据,具体步骤如下:
- 创建一个 QList容器ItemList用于存储一行数据的QStandardItem。
- 循环创建 FixedColumnCount-1列的数据,每列的数据都是 “测试(追加行)”。
- 创建最后一列的数据,这一列是一个可选框(Check Box),其表头通过 model->headerData获取。将该项设置为可选,并添加到ItemList中。
- 使用 model->insertRow插入一行,该行的数据由ItemList决定。
- 获取最后一行的 ModelIndex。
- 清空当前选中项,然后设置当前选中项为最后一行。
这个函数主要用于模拟在 TableView 中追加一行数据,其中包括普通文本和可选框数据。
| // 【添加一行】:为TableView添加一行数据(在文件末尾插入) | 
对于删除来说则更容易实现,只需要通过调用selection->currentIndex()获取当当前单元格模型索引,并通过调用model->removeRow来实现一处即可,此处需要区别一下是不是最后一行,如果是最后一行则直接删除即可,如果不是则需要在删除数据后通过setCurrentIndex将索引设置到前一个或第一个元素上,且核心代码如下所示;
| // 【删除一行】:删除选中行 | 
读者可自行点击添加一行与插入行,观察变化则可以理解两者的区别,如下图所示;

1.4 格式设置
格式设置也是非常常用的功能,例如在Office中就有表格元素居中、表格左对齐、表格右对齐、字体加粗显示等,在Qt中Table表格就默认自带了这些功能的支持,通过直接调用setTextAlignment并传入Qt::AlignHCenter居中、Qt::AlignLeft用于左对齐、Qt::AlignRight用于右对齐、而对于加粗显示只需要通过调用setFont将加粗厚的文本刷新到表格中即可,这些功能具备相似性,如下是完整的代码实现;
| // 设置表格居中对齐 | 
读者可依此点击下图的四个按钮来实习那对不同表格元素的个性化处理,当然如果需要保存这些状态,则还需要单独存储表格中的状态值,在运行程序后依次设置即可;
