我逐文件、逐行 做超详细注释+讲解,兼顾语法、Qt知识点、运行逻辑,按文件顺序梳理。
先重申项目整体:文件读写 + Qt Model/View + 自定义分页+搜索过滤 ,对应你之前了解的 Paged 分页概念。
一、PagedMode.pro 项目配置文件
.pro 是Qt的工程文件,用来告诉编译器要加载哪些模块、编译哪些文件。
qmake
# 引入Qt核心模块、图形基础模块
QT += core gui
# 如果Qt主版本号 > 4(Qt5/Qt6),额外引入widgets控件模块
# Qt4控件在gui里,Qt5/6控件单独拆到widgets
greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
# 启用C++11语法标准,支持现代C++特性
CONFIG += c++11
# 编译时,对Qt废弃的接口给出警告提示
DEFINES += QT_DEPRECATED_WARNINGS
# 下面注释行:开启后,直接禁止使用Qt6之前被废弃的API
#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000
# 源文件列表:所有.cpp文件,参与编译
SOURCES += \
data.cpp \
filter.cpp \
main.cpp \
mainwindow.cpp
# 头文件列表:所有.h文件
HEADERS += \
data.h \
filter.h \
mainwindow.h
# UI界面文件:Qt设计师绘制的界面
FORMS += \
mainwindow.ui
# 以下是不同系统的部署路径(发布程序用,日常开发不用管)
# QNX系统:程序输出到 /tmp/程序名/bin
qnx: target.path = /tmp/$${TARGET}/bin
# Linux/Unix非安卓系统:输出到 /opt/程序名/bin
else: unix:!android: target.path = /opt/$${TARGET}/bin
# 把目标文件加入安装规则
!isEmpty(target.path): INSTALLS += target
二、data.h 头文件(数据读写类声明)
负责生成测试文件、读取文件到表格模型
cpp
// 头文件保护:防止同一个头文件被重复包含(C/C++标准写法)
#ifndef DATA_H
#define DATA_H
// 引入Qt基础对象类,所有Qt信号槽类必须继承它
#include <QObject>
// Qt调试输出,等价于控制台打印
#include <QDebug>
// 文件操作类
#include <QFile>
// IO设备基类,文件、串口等都继承它
#include <QIODevice>
// 文本流,用来读写文本文件
#include <QTextStream>
// 标准表格模型,存储表格数据
#include <QStandardItemModel>
// 自定义Data类,继承QObject(为了使用Qt内存管理)
class Data : public QObject
{
// Qt宏:启用信号槽、元对象机制,继承QObject的类必须加
Q_OBJECT
public:
// 构造函数:parent = nullptr 表示无父对象,Qt父子树管理内存
explicit Data(QObject *parent = nullptr);
// 函数:创建测试文件
// path:文件路径 rows:文件行数 返回值:true成功/false失败
bool writeTest(QString path, int rows);
// 函数:读取文件,把数据加载到表格模型model中
bool loadTable(QString path, QStandardItemModel *model);
// 信号区:本类暂时没有自定义信号
signals:
};
// 结束头文件保护
#endif // DATA_H
三、data.cpp 源文件(Data类实现)
cpp
// 包含对应头文件
#include "data.h"
// 构造函数实现:调用父类QObject构造,传入父对象
Data::Data(QObject *parent) : QObject(parent)
{
// 暂无初始化逻辑,空函数体
}
// 实现:生成测试文本文件
bool Data::writeTest(QString path, int rows)
{
// 注释:向文件写入大量测试数据
// 创建QFile对象,绑定指定路径的文件
QFile file(path);
// 如果文件已经存在,直接返回true(不重复创建)
if(file.exists()) return true;
// 以【只写模式】打开文件;打开失败返回false
if(!file.open(QIODevice::WriteOnly))
{
// qCritical:错误级别的调试输出
qCritical() << "could not create: " << path;
// 输出文件打开失败的具体原因
qCritical() << file.errorString();
return false;
}
// 文本流:绑定file对象,用来按行读写文本
QTextStream stream(&file);
// 循环 rows 次,写入 rows 行数据
for(int i = 0; i < rows; i++)
{
// 把数字i转为字符串
QString data = QString::number(i);
// 写入字符串 + 换行符 \r\n(Windows标准换行)
stream << data << "\r\n";
}
// 刷新缓冲区:强制把内存数据写入磁盘
stream.flush();
// 关闭文件(必须手动关闭,释放资源)
file.close();
// 调试输出:文件创建成功
qDebug() << "Created: " << path;
return true;
}
// 实现:读取文件内容,加载到表格模型
bool Data::loadTable(QString path, QStandardItemModel *model)
{
// 注释:将文件内容加载到表格模型
// 清空模型原有数据
model->clear();
// 定义表头字符串列表
QStringList headers;
// 添加表头名称 "Item"
headers.append("Item");
// 设置表格列数为 1
model->setColumnCount(1);
// 设置表格顶部表头
model->setHorizontalHeaderLabels(headers);
// 新建文件对象,绑定文件路径
QFile file(path);
// 以【只读模式】打开文件,失败则报错返回
if(!file.open(QIODevice::ReadOnly))
{
qCritical() << "could not create: " << path;
qCritical() << file.errorString();
return false;
}
// 文本流绑定文件,用于逐行读取
QTextStream stream(&file);
// 循环:文件未读到末尾时,持续读取
while(!stream.atEnd()) {
// 读取一行文本(自动忽略换行符)
QString line = stream.readLine();
// 向表格模型添加一行数据:新建单元格项QStandardItem
model->appendRow(new QStandardItem(line));
}
// 关闭文件
file.close();
// 调试:加载完成 + 表格总行数
qDebug() << "Loaded: " << path;
qDebug() << "Model Row count: " << model->rowCount();
return true;
}
四、filter.h 头文件(分页+过滤核心类)
继承 QSortFilterProxyModel(Qt代理模型,专门做数据过滤、排序、分页)
cpp
#ifndef FILTER_H
#define FILTER_H
#include <QObject>
#include <QDebug>
// Qt代理模型基类:过滤、排序数据专用
#include <QSortFilterProxyModel>
// 线程类(本项目未实际用到,只是引入)
#include <QThread>
// 自定义Filter类,继承代理模型
class Filter : public QSortFilterProxyModel
{
Q_OBJECT
public:
// 构造函数
explicit Filter(QObject *parent = nullptr);
// ===== 分页开关 读写接口 =====
// 获取:是否开启分页
bool pageData();
// 设置:是否开启分页 value=true开启分页
void setPageData(bool value);
// ===== 每页条数 读写接口 =====
// 获取:每页数据条数
int pageSize();
// 设置:每页数据条数
void setPageSize(int value);
// 获取总页数
int pageCount();
// 执行搜索过滤:pattern 是搜索关键词
void filterPaged(QString pattern);
// ===== 当前页码 读写接口 =====
// 获取当前页码
int currentPage();
// 设置当前页码
void setCurrentPage(int value);
// 信号:对外发送通知(界面接收)
signals:
// 开始搜索/分页时触发
void started() const;
// 搜索/分页结束时触发
void finished() const;
// 槽函数:可被信号触发执行
public slots:
// 上一页
void back();
// 下一页
void next();
// 重写父类虚函数:Qt过滤核心函数(每一行都会进入这个函数判断是否显示)
protected:
// source_row:原始模型行号 source_parent:父索引
bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) const override;
// 私有成员变量(分页、过滤核心参数)
private:
bool pagedata; // 标记:是否开启分页模式
int pagesize; // 每页最多显示多少条数据
int currentpage; // 当前所在页码
QString regxpattern; // 搜索正则/关键词
mutable int total_added; // mutable:const函数也能修改;当前页已展示条数
mutable int total_passed; // 过滤后,符合条件的总条数
mutable int total_processed;// 已经遍历处理过的原始数据总行数
mutable int total_pages; // 计算得出的总页数
int min; // 当前页数据的起始下标
int max; // 当前页数据的结束下标
// 私有成员函数
void search(); // 触发一次搜索+分页刷新
void calcRange(); // 计算当前页上下界(本项目未实现)
// 计数辅助函数:统计行数、页数、状态
bool process(bool allowed, bool countonly, QString reason) const;
};
#endif // FILTER_H
五、filter.cpp 源文件(Filter 核心逻辑实现)
cpp
#include "filter.h"
// 构造函数:初始化所有分页、计数变量
Filter::Filter(QObject *parent) : QSortFilterProxyModel(parent)
{
total_added = 0; // 当前页条数清零
total_passed = 0; // 过滤通过总数清零
total_processed = 0; // 已处理行数清零
total_pages = 0; // 总页数清零
regxpattern = ""; // 搜索关键词置空
setCurrentPage(0); // 默认第0页(首页)
setPageData(false); // 默认关闭分页
setPageSize(100); // 默认每页100条
}
// 获取分页开关状态
bool Filter::pageData()
{
return pagedata;
}
// 设置分页开关
void Filter::setPageData(bool value)
{
pagedata = value;
qDebug() << "Page data: " << pagedata;
}
// 获取每页条数
int Filter::pageSize()
{
return pagesize;
}
// 设置每页条数
void Filter::setPageSize(int value)
{
pagesize = value;
qDebug() << "Page size: " << pagesize;
}
// 获取总页数;未开启分页返回-1
int Filter::pageCount()
{
if(!pagedata) return -1;
return total_pages;
}
// 外部调用:传入搜索关键词,启动搜索
void Filter::filterPaged(QString pattern)
{
regxpattern = pattern; // 保存搜索关键词
search(); // 执行搜索刷新
}
// 获取当前页码
int Filter::currentPage()
{
return currentpage;
}
// 设置当前页码,并刷新分页
void Filter::setCurrentPage(int value)
{
currentpage = value;
search();
}
// 槽函数:上一页
void Filter::back()
{
currentpage--; // 页码-1
if(currentpage < 0) currentpage = 0; // 边界保护:不能小于0
search(); // 刷新页面
}
// 槽函数:下一页
void Filter::next()
{
currentpage++; // 页码+1
if(currentpage > total_pages) currentpage = total_pages; // 边界保护
search(); // 刷新页面
}
// 核心:每次切换页码/搜索关键词,都会执行此函数
void Filter::search()
{
// 所有计数变量重置
total_added = 0;
total_passed = 0;
total_processed = 0;
total_pages = 0;
// 计算当前页的起始、结束下标
min = currentpage * pagesize;
max = min + pagesize;
emit started(); // 发出【开始搜索】信号,通知UI
// 设置过滤正则,Qt自动调用 filterAcceptsRow 逐行过滤
setFilterRegularExpression(regxpattern);
}
// 辅助函数:统计行数、页数,返回当前行是否允许显示
bool Filter::process(bool allowed, bool countonly, QString reason) const
{
total_processed++; // 已处理行数 +1
// 行允许显示 且 不是仅计数 → 当前页条数+1
if(allowed == true && countonly == false) total_added++;
// 需要计数统计 → 过滤通过总数+1,并计算总页数
if(countonly)
{
total_passed++;
total_pages = total_passed / pagesize; // 总条数 / 每页条数 = 总页数
}
qDebug() << "Allowed: " << allowed << reason;
// 所有原始行处理完毕 → 发出【搜索结束】信号
if(total_processed >= sourceModel()->rowCount()) emit finished();
return allowed; // 返回当前行是否显示
}
// 【最核心】逐行过滤判断:返回true=显示该行,false=隐藏该行
bool Filter::filterAcceptsRow(int source_row, const QModelIndex &source_parent) const
{
// 获取原始模型中当前行、当前列的索引
QModelIndex index = sourceModel()->index(source_row,filterKeyColumn(),source_parent);
// 1. 判断:当前行数据 是否 匹配搜索关键词
if(!sourceModel()->data(index).toString().contains(filterRegularExpression()))
return process(false,false,"Failed filter"); // 不匹配 → 隐藏
// 2. 如果未开启分页 → 全部匹配行都显示
if(!pagedata) return process(true,true,"Not paging");
// 走到这里:已开启分页
// 3. 当前页数据已满 → 隐藏该行
if(total_added >= pagesize) return process(false, true,"Not in page range");
// 4. 判断该行是否在【当前页下标范围】内 → 在范围则显示
if(total_passed >= min && total_passed < max) return process(true, true,"In page range");
// 其余情况:隐藏
return process(false, true,"Default");
}
六、main.cpp 程序入口文件
Qt程序唯一入口,所有Qt程序都从这里启动
cpp
// 引入主窗口头文件
#include "mainwindow.h"
// Qt应用程序核心类
#include <QApplication>
// C++程序标准入口函数
int main(int argc, char *argv[])
{
// 1. 创建Qt应用实例,接收命令行参数
QApplication a(argc, argv);
// 2. 创建主窗口对象
MainWindow w;
// 3. 显示主窗口
w.show();
// 4. 启动Qt事件循环(死循环),监听按钮点击、输入等操作
// 窗口关闭后,a.exec()返回,程序结束
return a.exec();
}
七、mainwindow.h 主窗口头文件
界面窗口类,关联UI、按钮、菜单、数据模型
cpp
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
// 主窗口基类
#include <QMainWindow>
// 文件选择对话框
#include <QFileDialog>
// 数字输入对话框
#include <QInputDialog>
// 弹窗提示框
#include <QMessageBox>
// 原始数据模型
#include <QStandardItemModel>
// 代理模型(父类,此处仅引入)
#include <QSortFilterProxyModel>
// 引入自定义类
#include "data.h"
#include "filter.h"
// Qt命名空间:UI自动生成代码放在这个命名空间里
QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACE
// 主窗口类,继承QMainWindow
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
// 构造、析构
MainWindow(QWidget *parent = nullptr);
~MainWindow();
// 槽函数:对应UI按钮、菜单的点击事件
private slots:
// 搜索按钮点击
void on_btnSearch_clicked();
// 上一页按钮点击
void on_btnBack_clicked();
// 下一页按钮点击
void on_btnNext_clicked();
// 菜单:创建测试文件 点击
void on_actionCreate_Test_File_triggered();
// 接收Filter的信号:搜索开始/结束
void started();
void finished();
// 私有成员
private:
Ui::MainWindow *ui; // UI界面对象,操作所有控件
QStandardItemModel model; // 原始数据模型(存放全部数据)
Filter filter; // 分页过滤代理模型
// 私有函数:自动生成默认测试数据
void makeTestData();
// 私有函数:加载指定路径文件到模型
void loadFile(QString path);
};
#endif // MAINWINDOW_H
八、mainwindow.cpp 主窗口实现
界面逻辑、按钮事件、信号槽连接全部在这里
cpp
#include "mainwindow.h"
// UI自动生成的头文件
#include "ui_mainwindow.h"
// 主窗口构造函数
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow) // 实例化UI对象
{
// 加载UI设计师绘制的界面(按钮、输入框、表格等)
ui->setupUi(this);
// 初始化分页配置:开启分页
filter.setPageData(true);
// 设置每页 10 条数据
filter.setPageSize(10);
// 初始页码 0
filter.setCurrentPage(0);
// 信号槽连接:Filter发出开始/结束信号 → 触发当前类对应槽函数
connect(&filter,&Filter::started,this,&MainWindow::started);
connect(&filter,&Filter::finished,this,&MainWindow::finished);
// 设置【原始模型】为代理模型的数据源
filter.setSourceModel(&model);
// 表格控件 使用代理模型(分页/过滤后的数据)
ui->tableView->setModel(&filter);
// 程序启动时,自动生成并加载默认测试文件
makeTestData();
}
// 主窗口析构函数:释放UI内存
MainWindow::~MainWindow()
{
delete ui;
}
// 【搜索按钮】点击事件
void MainWindow::on_btnSearch_clicked()
{
// 获取输入框文本,传给过滤类执行搜索
filter.filterPaged(ui->lineEdit->text());
}
// 【上一页】按钮点击
void MainWindow::on_btnBack_clicked()
{
filter.back();
}
// 【下一页】按钮点击
void MainWindow::on_btnNext_clicked()
{
filter.next();
}
// 【菜单:创建测试文件】点击事件
void MainWindow::on_actionCreate_Test_File_triggered()
{
QFileDialog dlg;
// 弹出保存文件对话框,让用户选择路径
QString path = dlg.getSaveFileName(this,"File to create");
// 文件信息类,判断文件是否存在
QFileInfo fi(path);
// 用户取消选择(路径为空),直接返回
if(path.isEmpty()) return;
// 判断文件已存在
if(fi.exists()) {
// 弹出询问框:是否覆盖文件
QMessageBox::StandardButton btn = QMessageBox::question(
this,"File Exists","File exists!\r\nOverwrite the file?",
QMessageBox::Yes | QMessageBox::No);
// 选择Yes:重新执行本函数(覆盖)
if(btn == QMessageBox::Yes) on_actionCreate_Test_File_triggered();
return;
}
// 弹出数字输入框:让用户输入数据行数
int value = QInputDialog::getInt(this,"Max Items","How many items to create?");
Data data;
// 生成测试文件,失败则弹出错误框
if(!data.writeTest(path,value))
{
QMessageBox::critical(this,"Error","Could not create test data!");
return;
}
// 生成成功,加载文件到表格
loadFile(path);
}
// 接收Filter::started信号:搜索开始
void MainWindow::started()
{
qDebug() << "STARTED SEARCHING";
}
// 接收Filter::finished信号:搜索结束,更新页码显示
void MainWindow::finished()
{
qDebug() << "FINISHED SEARCHING";
// 拼接 "当前页 / 总页数"
QString current = QString::number(filter.currentPage());
QString total = QString::number(filter.pageCount());
// 把文本显示到界面标签
ui->lblCount->setText(current + " of " + total);
}
// 自动创建默认测试文件(程序启动调用)
void MainWindow::makeTestData()
{
Data data;
// 获取程序运行所在目录路径
QString path = QGuiApplication::applicationDirPath();
// 拼接文件名:程序目录下的 test.txt
path.append("/test.txt");
// 创建1000行数据的测试文件,失败弹窗
if(!data.writeTest(path,1000))
{
QMessageBox::critical(this,"Error","Could not create test data!");
return;
}
// 加载文件
loadFile(path);
}
// 加载指定文件到表格模型
void MainWindow::loadFile(QString path)
{
Data data;
// 状态栏显示提示
ui->statusbar->showMessage("No file");
// 读取文件到model,失败弹窗
if(!data.loadTable(path,&model))
{
QMessageBox::critical(this,"Error","Could not load file!");
return;
}
// 状态栏显示当前加载的文件路径
ui->statusbar->showMessage(path);
// 隐藏表格左侧行号表头
ui->tableView->verticalHeader()->setVisible(false);
}
补充:整体运行流程极简梳理(帮你串起来)
- 运行程序 →
main()创建应用+主窗口 - 主窗口构造 → 加载UI、初始化分页、绑定模型
- 自动调用
makeTestData→ 生成test.txt(1000行数字) loadFile把文件读入model(原始数据)- 表格
tableView通过filter代理模型分页展示(每页10条) - 输入关键词点搜索 →
filter过滤数据 - 点上/下一页 → 切换页码,重新过滤分页
- 顶部菜单可手动新建自定义行数的测试文件
该项目的 Paged 逻辑,和你之前学的 Modbus Poll 分页读取 思想完全一致:数据量过大时,拆分成分页分批展示/读取。