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:筛选和排序
经验分享
- 展示数据用QSqlQueryModel:只读,简单
- 编辑数据用QSqlTableModel:可增删改,功能强
- 推荐OnManualSubmit策略:用户改完统一保存,体验好
- 记得隐藏id列:用户不需要看到自增id
- 整行选择更友好:setSelectionBehavior(SelectRows)
- 模型视图省代码:比手写增删改查+刷新界面简单太多
七、系列小结
到今天,我们的QT零基础系列已经学习了12天,覆盖了QT入门的核心内容:
- 环境搭建与Hello World
- 信号与槽机制
- 常用控件与表单
- 布局管理器
- 对话框QDialog
- 菜单栏工具栏状态栏
- 事件处理机制
- 绘图QPainter
- 文件操作(上)QFile与文本流
- 文件操作(下)文件信息与目录操作
- 数据库编程(上)SQLite与增删改查
- 数据库编程(下)模型视图与数据展示
从界面到事件,从绘图到文件,从数据库到数据展示,你已经掌握了开发一个完整QT桌面应用所需的核心知识!
八、明日预告
明天我们将学习QT网络编程入门。
内容包括:
- TCP通信(QTcpServer、QTcpSocket)
- UDP通信(QUdpSocket)
- HTTP请求(QNetworkAccessManager)
- 综合实战:简易聊天室
学完网络编程,你的QT应用就能联网通信了!
📝 学习建议:数据库是很多应用的核心,一定要多练。
练习建议:
- 把今天的学生管理系统完整敲一遍运行
- 试试给管理系统加一个"按班级筛选"的下拉框
- 试试给管理系统加数据导出功能(导出成CSV)
- 试试用QSqlQueryModel做一个只读的数据统计页面
数据库编程上下两篇都学完了,你已经能做出真正能用的数据库应用了!明天我们学网络编程,继续加油!💪