文件读写 + Qt Model/View + 自定义分页+搜索过滤

逐文件、逐行 做超详细注释+讲解,兼顾语法、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);
}

补充:整体运行流程极简梳理(帮你串起来)

  1. 运行程序 → main() 创建应用+主窗口
  2. 主窗口构造 → 加载UI、初始化分页、绑定模型
  3. 自动调用 makeTestData → 生成 test.txt(1000行数字)
  4. loadFile 把文件读入 model(原始数据)
  5. 表格 tableView 通过 filter 代理模型分页展示(每页10条)
  6. 输入关键词点搜索 → filter 过滤数据
  7. 点上/下一页 → 切换页码,重新过滤分页
  8. 顶部菜单可手动新建自定义行数的测试文件

该项目的 Paged 逻辑,和你之前学的 Modbus Poll 分页读取 思想完全一致:数据量过大时,拆分成分页分批展示/读取

相关推荐
在繁华处1 小时前
Java从零到熟练(十):JVM基础与性能优化
java·jvm·性能优化
l1t1 小时前
DeepSeek总结的DuckDB-Iceberg 在 v1.5.3 中的新特性
数据库·duckdb
Database_Cool_1 小时前
数据仓库弹性扩缩容怎么实现?阿里云 AnalyticDB MySQL Serverless 弹性架构详解
数据库·人工智能·阿里云
abcy0712131 小时前
django聚合函数
数据库·sqlite
Demon1_Coder1 小时前
Day1-SpringAI-1.0.0版本
java·开发语言·前端
念越1 小时前
数据库系统概论第6版王珊版:第二章关系代数与第三章SQL期末重点整理
数据库·sql·性能优化
TDengine (老段)1 小时前
TDengine 数据保留与 TTL — 多级存储、过期删除与分层迁移
大数据·数据库·物联网·时序数据库·tdengine·涛思数据
老码观察1 小时前
设计模式实战解读(九):责任链模式——流水线上层层把关的艺术
java·设计模式·责任链模式
郝学胜-神的一滴1 小时前
Qt 高级开发 021:零基础吃透 QVBoxLayout 垂直布局
开发语言·c++·qt·程序人生·用户界面