【无标题】QT入门第十二天:数据库编程(下)模型视图与数据展示 | 零基础学QT

QT入门第十二天:数据库编程(下)模型视图与数据展示 | 零基础学QT

前言

昨天我们学习了数据库编程的上篇:SQLite入门、数据库连接、增删改查(CRUD)。

今天我们继续学习数据库编程的下篇,重点是如何把数据库数据显示到界面上,内容包括:

  • QSqlQueryModel:把查询结果显示到表格
  • QSqlTableModel:可编辑的表格模型
  • 数据库与QTableView绑定
  • 数据的排序、筛选
  • 综合实战:完整的学生管理系统(带界面)

学完这两篇,你就能做出真正能用的数据库应用了!

一、模型视图架构回顾

1.1 什么是模型视图

QT用"模型视图"(Model-View)架构来展示数据:

  • 模型(Model):负责管理数据(从哪里来、怎么组织)
  • 视图(View):负责显示数据(怎么显示给用户看)
  • 委托(Delegate):负责编辑数据(怎么编辑)

💡 生活类比:

  • 模型 = 仓库(存放货物)
  • 视图 = 展示柜(把货物摆出来给人看)
  • 委托 = 售货员(帮你处理买卖)

1.2 为什么用模型视图

好处是数据和显示分离

  • 同一份数据可以用不同的视图显示(表格、列表、树)
  • 数据变了,视图自动更新
  • 代码更清晰,好维护

1.3 常用的视图

视图 说明
QTableView 表格视图(行列)
QListView 列表视图
QTreeView 树形视图

数据库数据一般用QTableView(表格)来显示。

二、QSqlQueryModel 只读模型

2.1 什么是QSqlQueryModel

QSqlQueryModel是一个只读的模型,它把SQL查询的结果封装成模型,可以直接显示到视图上。

💡 特点:只能看,不能改。适合用来展示数据。

2.2 基本用法

cpp 复制代码
#include <QSqlQueryModel>
#include <QTableView>

// 创建模型
QSqlQueryModel *model = new QSqlQueryModel(this);

// 执行查询
model->setQuery("SELECT id, name, age, score FROM student");

// 设置表头(可选)
model->setHeaderData(0, Qt::Horizontal, "学号");
model->setHeaderData(1, Qt::Horizontal, "姓名");
model->setHeaderData(2, Qt::Horizontal, "年龄");
model->setHeaderData(3, Qt::Horizontal, "分数");

// 创建视图并设置模型
QTableView *view = new QTableView(this);
view->setModel(model);
view->show();

就这么简单!几行代码,数据库的查询结果就显示到表格里了。

2.3 获取数据

如果你想在代码里获取模型的数据:

cpp 复制代码
// 获取行数
int rows = model->rowCount();

// 获取某个单元格的数据
QModelIndex index = model->index(0, 1);  // 第0行第1列
QString name = model->data(index).toString();

// 或者用记录
QSqlRecord record = model->record(0);  // 第0行的记录
QString name2 = record.value("name").toString();

2.4 刷新数据

数据库变了,重新查询就能刷新:

cpp 复制代码
model->setQuery("SELECT id, name, age, score FROM student");

三、QSqlTableModel 可编辑模型

3.1 什么是QSqlTableModel

QSqlTableModel比QSqlQueryModel更强大,它是可编辑的:

  • 可以直接在表格里修改数据
  • 可以添加、删除行
  • 修改后可以提交到数据库

💡 QSqlTableModel直接对应一张表,可以像操作Excel一样操作数据库。

3.2 基本用法

cpp 复制代码
#include <QSqlTableModel>
#include <QTableView>

// 创建模型
QSqlTableModel *model = new QSqlTableModel(this);

// 设置要操作的表
model->setTable("student");

// 加载数据
model->select();

// 设置表头
model->setHeaderData(1, Qt::Horizontal, "姓名");
model->setHeaderData(2, Qt::Horizontal, "年龄");

// 显示到视图
QTableView *view = new QTableView(this);
view->setModel(model);
view->show();

3.3 编辑策略

QSqlTableModel有三种编辑策略,决定什么时候把修改保存到数据库:

cpp 复制代码
// 1. 修改单元格立即保存(默认)
model->setEditStrategy(QSqlTableModel::OnFieldChange);

// 2. 切换行时保存
model->setEditStrategy(QSqlTableModel::OnRowChange);

// 3. 手动保存(需要调用submitAll())
model->setEditStrategy(QSqlTableModel::OnManualSubmit);

💡 推荐用 OnManualSubmit,这样用户可以修改多处,最后统一保存或取消。

3.4 添加数据

cpp 复制代码
// 方法一:插入空行,然后填数据
int row = model->rowCount();
model->insertRow(row);
model->setData(model->index(row, 1), "新学生");
model->setData(model->index(row, 2), 20);

// 提交(如果是手动提交策略)
model->submitAll();

也可以用QSqlRecord:

cpp 复制代码
QSqlRecord record = model->record();
record.setValue("name", "小明");
record.setValue("age", 18);
model->insertRecord(-1, record);  // -1表示插到最后
model->submitAll();

3.5 删除数据

cpp 复制代码
// 删除第0行
model->removeRow(0);
model->submitAll();

// 删除多行
model->removeRows(0, 3);  // 从第0行开始删3行
model->submitAll();

3.6 修改数据

用户在表格里直接修改后:

cpp 复制代码
// 手动提交策略下,需要调用submitAll保存
if (model->submitAll()) {
    qDebug() << "保存成功";
} else {
    qDebug() << "保存失败:" << model->lastError().text();
    model->revertAll();  // 撤销修改
}

3.7 筛选和排序

cpp 复制代码
// 筛选:只显示一班的学生
model->setFilter("class = '一班'");
model->select();

// 筛选:分数大于80
model->setFilter("score > 80");
model->select();

// 排序:按分数降序
model->setSort(3, Qt::DescendingOrder);  // 第3列,降序
model->select();

// 清除筛选
model->setFilter("");
model->select();

四、视图的美化

4.1 常用设置

cpp 复制代码
QTableView *view = new QTableView(this);
view->setModel(model);

// 隐藏行号(垂直表头)
view->verticalHeader()->setVisible(false);

// 整行选择
view->setSelectionBehavior(QAbstractItemView::SelectRows);

// 单选
view->setSelectionMode(QAbstractItemView::SingleSelection);

// 交替行颜色(斑马纹)
view->setAlternatingRowColors(true);

// 列宽自适应内容
view->resizeColumnsToContents();

// 最后一列拉伸填满
view->horizontalHeader()->setStretchLastSection(true);

// 禁止编辑(如果只想看不想改)
view->setEditTriggers(QAbstractItemView::NoEditTriggers);

4.2 隐藏某列

比如不想显示id列:

cpp 复制代码
view->hideColumn(0);  // 隐藏第0列

五、综合实战:学生管理系统

我们来做一个完整的学生管理系统,带界面,能增删改查。

5.1 完整代码

cpp 复制代码
#include <QMainWindow>
#include <QTableView>
#include <QLineEdit>
#include <QPushButton>
#include <QVBoxLayout>
#include <QHBoxLayout>
#include <QLabel>
#include <QSqlDatabase>
#include <QSqlTableModel>
#include <QSqlQuery>
#include <QSqlError>
#include <QMessageBox>
#include <QHeaderView>

class StudentManager : public QMainWindow
{
    Q_OBJECT
public:
    StudentManager(QWidget *parent = nullptr) : QMainWindow(parent) {
        setWindowTitle("学生管理系统");
        resize(800, 600);
        
        // 初始化数据库
        if (!initDatabase()) {
            QMessageBox::critical(this, "错误", "数据库初始化失败!");
            return;
        }
        
        // 创建界面
        setupUI();
        
        // 初始化模型
        setupModel();
    }

private:
    // 初始化数据库
    bool initDatabase() {
        QSqlDatabase db = QSqlDatabase::addDatabase("QSQLITE");
        db.setDatabaseName("students.db");
        
        if (!db.open()) {
            qDebug() << db.lastError().text();
            return false;
        }
        
        // 创建表
        QSqlQuery query;
        query.exec("CREATE TABLE IF NOT EXISTS student ("
                   "id INTEGER PRIMARY KEY AUTOINCREMENT, "
                   "name TEXT NOT NULL, "
                   "age INTEGER, "
                   "score REAL, "
                   "class TEXT)");
        return true;
    }
    
    // 创建界面
    void setupUI() {
        QWidget *central = new QWidget(this);
        setCentralWidget(central);
        
        QVBoxLayout *mainLayout = new QVBoxLayout(central);
        
        // 搜索栏
        QHBoxLayout *searchLayout = new QHBoxLayout();
        searchLayout->addWidget(new QLabel("搜索姓名:"));
        m_searchEdit = new QLineEdit(this);
        m_searchBtn = new QPushButton("搜索", this);
        m_showAllBtn = new QPushButton("显示全部", this);
        searchLayout->addWidget(m_searchEdit);
        searchLayout->addWidget(m_searchBtn);
        searchLayout->addWidget(m_showAllBtn);
        mainLayout->addLayout(searchLayout);
        
        // 表格视图
        m_view = new QTableView(this);
        mainLayout->addWidget(m_view);
        
        // 按钮栏
        QHBoxLayout *btnLayout = new QHBoxLayout();
        m_addBtn = new QPushButton("添加", this);
        m_deleteBtn = new QPushButton("删除", this);
        m_saveBtn = new QPushButton("保存", this);
        m_revertBtn = new QPushButton("撤销", this);
        btnLayout->addWidget(m_addBtn);
        btnLayout->addWidget(m_deleteBtn);
        btnLayout->addStretch();
        btnLayout->addWidget(m_saveBtn);
        btnLayout->addWidget(m_revertBtn);
        mainLayout->addLayout(btnLayout);
        
        // 连接信号槽
        connect(m_addBtn, &QPushButton::clicked, this, &StudentManager::addStudent);
        connect(m_deleteBtn, &QPushButton::clicked, this, &StudentManager::deleteStudent);
        connect(m_saveBtn, &QPushButton::clicked, this, &StudentManager::saveChanges);
        connect(m_revertBtn, &QPushButton::clicked, this, &StudentManager::revertChanges);
        connect(m_searchBtn, &QPushButton::clicked, this, &StudentManager::search);
        connect(m_showAllBtn, &QPushButton::clicked, this, &StudentManager::showAll);
    }
    
    // 初始化模型
    void setupModel() {
        m_model = new QSqlTableModel(this);
        m_model->setTable("student");
        // 手动提交策略
        m_model->setEditStrategy(QSqlTableModel::OnManualSubmit);
        m_model->select();
        
        // 设置表头
        m_model->setHeaderData(0, Qt::Horizontal, "学号");
        m_model->setHeaderData(1, Qt::Horizontal, "姓名");
        m_model->setHeaderData(2, Qt::Horizontal, "年龄");
        m_model->setHeaderData(3, Qt::Horizontal, "分数");
        m_model->setHeaderData(4, Qt::Horizontal, "班级");
        
        // 设置视图
        m_view->setModel(m_model);
        m_view->setSelectionBehavior(QAbstractItemView::SelectRows);
        m_view->setAlternatingRowColors(true);
        m_view->horizontalHeader()->setStretchLastSection(true);
        m_view->hideColumn(0);  // 隐藏id列
    }

private slots:
    // 添加学生
    void addStudent() {
        int row = m_model->rowCount();
        m_model->insertRow(row);
        // 选中新行,方便编辑
        m_view->selectRow(row);
    }
    
    // 删除学生
    void deleteStudent() {
        QModelIndexList selected = m_view->selectionModel()->selectedRows();
        if (selected.isEmpty()) {
            QMessageBox::information(this, "提示", "请先选择要删除的行!");
            return;
        }
        
        if (QMessageBox::question(this, "确认", "确定要删除吗?") 
            == QMessageBox::Yes) {
            for (const QModelIndex &index : selected) {
                m_model->removeRow(index.row());
            }
        }
    }
    
    // 保存修改
    void saveChanges() {
        if (m_model->submitAll()) {
            QMessageBox::information(this, "提示", "保存成功!");
        } else {
            QMessageBox::warning(this, "错误", 
                "保存失败:" + m_model->lastError().text());
        }
    }
    
    // 撤销修改
    void revertChanges() {
        m_model->revertAll();
    }
    
    // 搜索
    void search() {
        QString keyword = m_searchEdit->text();
        m_model->setFilter(QString("name LIKE '%%1%'").arg(keyword));
        m_model->select();
    }
    
    // 显示全部
    void showAll() {
        m_searchEdit->clear();
        m_model->setFilter("");
        m_model->select();
    }

private:
    QTableView *m_view;
    QSqlTableModel *m_model;
    QLineEdit *m_searchEdit;
    QPushButton *m_searchBtn;
    QPushButton *m_showAllBtn;
    QPushButton *m_addBtn;
    QPushButton *m_deleteBtn;
    QPushButton *m_saveBtn;
    QPushButton *m_revertBtn;
};

5.2 功能说明

这个学生管理系统的功能:

  • ✅ 显示所有学生(表格形式)
  • ✅ 添加学生(新增行,直接在表格里编辑)
  • ✅ 删除学生(选中行删除,有确认提示)
  • ✅ 修改学生(表格里直接改)
  • ✅ 保存修改(统一提交到数据库)
  • ✅ 撤销修改(放弃未保存的修改)
  • ✅ 按姓名搜索
  • ✅ 显示全部

这是一个真正能用的数据库应用!用了QSqlTableModel,代码量比手写增删改查少很多。

5.3 两种方案对比

我们学了两种做数据库应用的方式:

方案 特点 适用场景
手写QSqlQuery(昨天) 灵活,可控 复杂查询、业务逻辑
QSqlTableModel(今天) 快速,代码少 简单的表格增删改查

💡 实际开发中经常两者结合:简单的表格用QSqlTableModel,复杂的查询用QSqlQuery。

六、今日总结

今天我们学习了数据库编程的下篇,主要是模型视图和数据展示。

知识点汇总

用途 特点
QSqlQueryModel 只读查询模型 显示查询结果,不能改
QSqlTableModel 可编辑表模型 直接编辑表数据
QTableView 表格视图 显示表格数据

重要概念

  • 模型视图架构:数据(模型)和显示(视图)分离
  • QSqlQueryModel:只读,显示查询结果
  • QSqlTableModel:可编辑,对应一张表
  • 编辑策略:OnFieldChange / OnRowChange / OnManualSubmit
  • submitAll/revertAll:提交/撤销修改
  • setFilter/setSort:筛选和排序

经验分享

  1. 展示数据用QSqlQueryModel:只读,简单
  2. 编辑数据用QSqlTableModel:可增删改,功能强
  3. 推荐OnManualSubmit策略:用户改完统一保存,体验好
  4. 记得隐藏id列:用户不需要看到自增id
  5. 整行选择更友好:setSelectionBehavior(SelectRows)
  6. 模型视图省代码:比手写增删改查+刷新界面简单太多

七、系列小结

到今天,我们的QT零基础系列已经学习了12天,覆盖了QT入门的核心内容:

  1. 环境搭建与Hello World
  2. 信号与槽机制
  3. 常用控件与表单
  4. 布局管理器
  5. 对话框QDialog
  6. 菜单栏工具栏状态栏
  7. 事件处理机制
  8. 绘图QPainter
  9. 文件操作(上)QFile与文本流
  10. 文件操作(下)文件信息与目录操作
  11. 数据库编程(上)SQLite与增删改查
  12. 数据库编程(下)模型视图与数据展示

从界面到事件,从绘图到文件,从数据库到数据展示,你已经掌握了开发一个完整QT桌面应用所需的核心知识!

八、明日预告

明天我们将学习QT网络编程入门

内容包括:

  • TCP通信(QTcpServer、QTcpSocket)
  • UDP通信(QUdpSocket)
  • HTTP请求(QNetworkAccessManager)
  • 综合实战:简易聊天室

学完网络编程,你的QT应用就能联网通信了!


📝 学习建议:数据库是很多应用的核心,一定要多练。

练习建议:

  • 把今天的学生管理系统完整敲一遍运行
  • 试试给管理系统加一个"按班级筛选"的下拉框
  • 试试给管理系统加数据导出功能(导出成CSV)
  • 试试用QSqlQueryModel做一个只读的数据统计页面

数据库编程上下两篇都学完了,你已经能做出真正能用的数据库应用了!明天我们学网络编程,继续加油!💪

相关推荐
云絮.2 小时前
数据库事务
java·开发语言·数据库
Leon-Ning Liu2 小时前
【真实经验分享】OGG抽取进程报错 ORA-07445 [kgherrordmp()+986] ORA-00600 [17114]分析步骤
运维·数据库
CCPC不拿奖不改名3 小时前
Redis 工程化部署深度解析
linux·服务器·数据库·redis·深度学习·缓存·rag
吴声子夜歌3 小时前
SQL进阶——自连接
数据库·sql
云贝教育-郑老师3 小时前
TDSQL(MySQL版)分布式事务实现机制深度解析:从两阶段提交到全局一致性读
数据库·sql
gb448oww53 小时前
Redis分布式锁进阶第三十五篇
数据库·redis·分布式
luoyayun3614 小时前
Qt/QML音视频文件原始十六进制查看器
qt·音视频·十六进制查看
Full Stack Developme4 小时前
正则表达式设计及工作原理
数据库·mysql·正则表达式