auther: abinng date: 2026-05-14 15:47
createDate:2026-05-14 15:47
复习路线
这篇笔记要回答的问题是:Qt 如何连接数据库、执行
SQL,以及怎样通过模型视图架构将数据库中的数据高效地展示在界面上?
Qt SQL 模块总体上分为 3 层:
1 2 3 4 5 驱动层(桥接具体数据库) ↓ SQL 接口层(QSqlDatabase 连接 + QSqlQuery 执行 SQL) ↓ 用户接口层(QSqlQueryModel / QSqlTableModel / QSqlRelationalTableModel → QTableView)
下次忘记时,可以按这个顺序复习:
先看“数据库连接“,理解 QSqlDatabase
的分层设计以及如何创建连接。
再看“执行SQL语句“,掌握 QSqlQuery 的增删改查和批量操作。
看“MVD 模型视图架构“,理解 Model / View / Delegate
三个角色的分工与通信。
看“三个SQL模型“,对比 QSqlQueryModel / QSqlTableModel /
QSqlRelationalTableModel 的能力差异和继承关系。
看“QSqlTableModel 实战“,用完整代码走通单表的增删改查 +
事务提交。
看“QSqlRelationalTableModel 实战“,理解外键关联的展示和过滤。
最后看“自定义委托“,掌握如何定制单元格的显示和编辑行为。
1. 数据库连接
1.1 分层架构
Qt 对数据库的支持采用分层设计,保证了“一份代码多处使用“——无论底层是
MySQL、SQLite 还是 PostgreSQL,上层业务代码基本无需改动。
Qt SQL 模块中的类大致分为 3 层:
驱动层 :为具体数据库和 SQL
接口层之间提供底层桥接。
SQL
接口层 :提供对数据库的访问。QSqlDatabase
用来创建连接,QSqlQuery 使用 SQL 语句与数据库交互。
用户接口层 :将数据库中的数据链接到窗口部件上。这些类基于模型视图框架实现,即便不熟悉
SQL 也可以操作数据库。
要使用 Qt SQL 模块,需要在 CMake 中配置如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 cmake_minimum_required (VERSION 4.1 )project (xxx)set (CMAKE_CXX_STANDARD 17 )set (CMAKE_AUTOMOC ON )set (CMAKE_AUTORCC ON )set (CMAKE_AUTOUIC ON )set (CMAKE_PREFIX_PATH "/path/to/Qt/6.5.3/mingw_64" )find_package (Qt6 COMPONENTS Core Gui Widgets Sql REQUIRED) add_executable (xxx main.cpp)target_link_libraries (xxx Qt::Core Qt::Gui Qt::Widgets Qt::Sql)
1.2 连接的四层模型
一次成功的数据库连接,从上到下包含四层:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 业务代码 (QSqlQuery 等) │ ▼ ┌───────────────────────────┐ │ QSqlDatabase (通用连接管理) │ └──────────┬────────────────┘ │ (依赖) ▼ ┌───────────────────────────┐ │ Qt SQL 驱动插件动态库 │ ← QSqlDriver 的具体实现(Qt 提供) │ (例: qsqlmysql.dll) │ └──────────┬────────────────┘ │ (调用) ▼ ┌───────────────────────────┐ │ 数据库厂商客户端动态库 │ ← 真正实现网络通信的底层接口 │ (例: libmysqlclient.dll) │ (数据库官网提供) └──────────┬────────────────┘ │ (TCP/IP 网络通信等) ▼ 【具体的数据库服务器 (如 MySQL Server)】
业务代码层 :我们写的 QSqlDatabase 和
QSqlQuery 代码。
Qt 驱动插件层 :Qt 官方提供的动态库(如
qsqlmysql.dll / libqsqlite.so),实现了
QSqlDriver 接口。
数据库厂商客户端库层 :各大数据库官网提供的动态库(如
MySQL 的 libmysqlclient.dll),负责真正的网络通信。
具体的数据库服务器 :数据库本身。
可以通过 QSqlDatabase::drivers() 查看 Qt
当前支持哪些数据库驱动,也可以在 Qt 安装目录下的
plugins/sqldrivers 文件夹中看到所有的驱动插件文件。
1.3 创建连接
通过 QSqlDatabase::addDatabase() 来新建数据库连接:
点击展开
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 #include <QApplication> #include <QSql> #include <QSqlDatabase> #include <QDebug> #include <QSqlError> int main (int argc, char *argv[]) { QApplication a (argc, argv) ; QStringList drivers = QSqlDatabase::drivers (); qDebug () << "查看驱动列表" ; foreach(QString d, drivers) { qDebug () << d; } QSqlDatabase db = QSqlDatabase::addDatabase ("QSQLITE" , "first" ); db.setDatabaseName ("/path/to/your/test.db" ); if (!db.open ()) { qDebug () << "database open failed" << db.lastError ().text (); return -1 ; } qDebug () << "open database success!" ; db.close (); return QApplication::exec (); }
addDatabase() 的第一个参数是驱动名称(如
"QSQLITE"、"QMYSQL"),第二个参数是连接名,用于区分同一应用中的多个数据库连接。
1.4 驱动加载问题
可能会遇到 QSQLITE driver not loaded
错误,同时也不会打印驱动列表。
原因通常是:虽然 Qt 安装目录下的 plugins/sqldrivers
文件夹中有驱动插件文件,但插件搜索路径可能指向了项目目录下的
debug/plugins/sqldrivers,那里缺少驱动文件。
CLion 的 CMakeLists.txt 末尾有一段拷贝
plugins、Qt6Core.dll、Qt6Gui.dll、Qt6Widgets.dll
的代码,删除那段拷贝代码并清除已拷贝的文件后,程序就能正确找到 Qt
安装目录下的驱动插件了。
CLion
也可以很方便地连接数据库:右侧工具栏有数据库插件,相当于一个数据库客户端,新建连接
→ 数据源选择 SQLite:
初次连接 SQLite 会让下载驱动文件:
下载好后填写属性信息,可以测试连接:
2. 执行SQL语句
Qt 通过 QSqlQuery 对象来执行 SQL 语句,核心方法是
exec()。
2.1 创建表
先准备好 SQL 语句,然后调用 exec() 即可:
1 2 3 4 5 6 7 8 9 10 void create_table01 (const QSqlDatabase &db) { QSqlQuery query (db) ; bool ret = query.exec ("CREATE TABLE student (id INTEGER PRIMARY KEY, name TEXT, age INTEGER)" ); if (ret) { qDebug () << "Create student table success" ; } else { qDebug () << "Create student table failed:" << query.lastError ().text (); } }
2.2 查询数据
执行完 exec() 后,QSqlQuery
的内部指针位于第一条记录之前 。必须调用一次
next() 使其前进到第一条记录,然后可以反复调用
next() 遍历所有记录,直到返回 false:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 void query_table (const QSqlDatabase &db) { QSqlQuery query (db) ; if (query.exec ("SELECT * FROM student" )) { while (query.next ()) { QString name = query.value (1 ).toString (); int age = query.value (2 ).toInt (); qDebug () << "-------: " << name << " " << age; } } }
value() 返回
QVariant,不同的数据库类型会自动映射为 Qt
中最接近的相应类型。
2.3 插入数据(单条)
插入单条记录有两种绑定方式——名称绑定 和位置绑定 :
1 2 3 4 5 6 7 8 9 10 11 query.prepare ("INSERT INTO student (id, name) VALUES (:id, :name)" ); query.bindValue (":id" , idValue); query.bindValue (":name" , nameValue); query.exec (); query.prepare ("INSERT INTO student (id, name) VALUES (?, ?)" ); query.addBindValue (idValue); query.addBindValue (nameValue); query.exec ();
2.4 批量插入
当要插入多条记录时,只需 prepare() 一次,然后使用
QVariantList 绑定多组数据,最后调用
execBatch():
1 2 3 4 5 6 7 8 9 10 11 12 13 14 void insert_test (const QSqlDatabase &db) { QSqlQuery query (db) ; query.prepare ("INSERT INTO student (name, age) VALUES (:_name, :_age)" ); QVariantList names; names << "x1" << "x2" << "x3" ; QVariantList ages; ages << 18 << 19 << 20 ; query.bindValue (":_name" , names); query.bindValue (":_age" , ages); query.execBatch (); }
3. MVD 模型视图架构
3.1 架构概述
Qt 中的模型/视图架构(MVD)是经典 MVC
架构的演进,主要用来将数据的存储 与界面的展示 进行解耦和分离。该架构包含
3 个核心组件:模型(Model)、视图(View)和委托(Delegate)。
3.2 核心组件
模型(Model) :
定义 :是应用对象,用来表示数据。
职责 :模型与底层的真实数据源(如数据库、文件、内存结构)进行通信,为架构中的其他组件提供标准的数据交互接口。
视图(View) :
定义 :是模型的用户界面,用来显示数据。
职责 :负责数据的整体布局和展示。视图从模型中获得模型索引(Model
Index),模型索引用来精确表示具体的数据项。
委托 / 代理(Delegate) :
定义 :用于定制单个数据项的渲染和编辑方式。
职责 :在标准的视图中,委托负责渲染(绘制)具体的数据项。它接管了
View 中局部 UI 的展示逻辑。
3.3 组件间的通信机制
MVD
架构中,各个组件通过模型索引 和信号与槽 进行高效通信:
View 与 Model 的直接通信(展示数据) :
读取 :视图根据需要,拿着模型索引直接向模型请求数据用于展示。
通知 :当后台模型的数据发生变化时,模型会发出信号通知视图,视图随即请求新数据刷新界面。
Delegate 与 Model 的直接通信(编辑数据) :
编辑机制 :当用户双击视图中的某个项目进入编辑状态时,委托会被唤起。
数据回写 :编辑完成后,委托使用模型索引直接与模型进行通信,将修改后的新数据写回模型中。
4. 三个SQL模型
4.1 概述
除了直接使用 QSqlQuery 执行 SQL 语句,Qt 还提供了 3
个基于 MVD 架构的高级模型类。它们都继承自
QAbstractTableModel,可以直接绑定到 QTableView
/ QListView 等视图上展示数据库数据:
类
继承自
读写
适用场景
QSqlQueryModel
QAbstractTableModel
只读
任意 SQL 查询结果展示
QSqlTableModel
QSqlQueryModel
可读写
单表增删改查,无需手写 SQL
QSqlRelationalTableModel
QSqlTableModel
可读写
含外键关联的表,自动将外键 ID 替换为可读名称
三者能力递增,下面逐个说明。
4.2 QSqlQueryModel —— 查询模型
是什么 :一个只读模型,执行一条 SQL
查询后将结果集缓存在内存中,供视图显示。它不关心表结构、不区分主键外键——只负责“你给
SQL,我出结果“。
1 2 3 auto *model = new QSqlQueryModel (this );model->setQuery ("SELECT * FROM student" , QSqlDatabase::database ("first" )); ui->tableView->setModel (model);
用到的接口 :
setQuery(sql, db) — 设置 SQL
查询并执行,结果集存入模型
适合展示统计报表、多表联查结果等不需要编辑的场景。
4.3 QSqlTableModel —— 表格模型
是什么 :继承自
QSqlQueryModel,专门操作单张数据库表 ,且可读可写 。它把
INSERT / UPDATE / DELETE 操作封装成了方法调用,不需要拼接 SQL
字符串。配合 QTableView
就能快速搭建一个数据库表格编辑界面。
下面按功能分组介绍其常用接口。
初始化
1 2 3 4 5 m_model = new QSqlTableModel (this , m_db); m_model->setTable ("student" ); m_model->select (); m_model->setHeaderData (0 , Qt::Horizontal, "ID号" ); m_model->setEditStrategy (QSqlTableModel::OnManualSubmit);
setTable("表名") — 指定要操作的表
select() —
执行查询、拉取数据。修改排序/过滤条件后需重新调用才会生效
setHeaderData(col, Qt::Horizontal, "名称")
— 自定义列的表头文字
setEditStrategy(QSqlTableModel::OnManualSubmit)
— 设置为“手动提交“模式:所有修改先缓存在内存中,调用
submitAll() 后才写入数据库
增删行
1 2 3 4 5 int row_num = m_model->rowCount ();m_model->insertRow (row_num); int cur_row = ui->tableView->currentIndex ().row ();m_model->removeRow (cur_row);
insertRow(row) —
在指定位置插入一个空行
removeRow(row) — 删除指定行
rowCount() — 获取当前总行数
提交与撤销
1 2 m_model->submitAll (); m_model->revertAll ();
事务
结合数据库事务确保数据安全——要么全部成功写入,要么全部回滚:
1 2 3 4 5 6 m_model->database ().transaction (); if (m_model->submitAll ()) { m_model->database ().commit (); } else { m_model->database ().rollback (); }
database().transaction() —
开启事务
database().commit() — 提交事务
database().rollback() — 回滚事务
排序与过滤
1 2 3 4 m_model->setSort (2 , Qt::AscendingOrder); m_model->select (); m_model->setFilter (QString ("name = '%1'" ).arg (input_name));
setSort(col, order) —
设置排序列和方向(Qt::AscendingOrder /
Qt::DescendingOrder),需配合 select()
生效
setFilter("条件") —
设置过滤条件,等价于 SQL 的 WHERE 子句。设为空字符串
"" 可清除过滤
4.4
QSqlRelationalTableModel —— 关联外键模型
是什么 :继承自
QSqlTableModel,专门解决外键关联 的显示问题。
举个例子:books 表的 category_id
字段存的是分类 ID(1、2),直接用
QSqlTableModel
显示出来就是一串数字,用户根本看不懂。QSqlRelationalTableModel
通过 setRelation()
告诉模型“这一列是外键,去另一张表查对应的名称来显示“。同时配合
QSqlRelationalDelegate,编辑时该列会自动弹出下拉框让用户选择。
1 2 3 4 5 6 7 8 9 model = new QSqlRelationalTableModel (this , m_db); model->setTable ("books" ); model->setRelation (4 , QSqlRelation ("category" , "id" , "name" )); model->setHeaderData (4 , Qt::Horizontal, "图书分类" ); model->select (); ui->tableView->setModel (model); ui->tableView->setItemDelegate (new QSqlRelationalDelegate (ui->tableView));
用到的接口 :
setRelation(col, QSqlRelation("关联表", "匹配列", "显示列"))
— 将第 col
列设为外键:用“匹配列“去“关联表“中查找,将“显示列“的值替代原始 ID
展示给用户
QSqlRelationalDelegate — Qt
内置的外键列代理,编辑时自动生成 QComboBox
列出关联表的所有可选值供用户选择
这三个模型的能力是递增的:只读查询 → 单表读写 →
外键关联。实际开发中根据需求选择合适的即可。
5. QSqlTableModel 实战
在实际开发中,QSqlTableModel 配合
QTableView
可以极大地简化数据库表的增删改查逻辑。它隐藏了复杂的 SQL
语句拼接过程,下面用完整代码走通整个流程。
5.1 模型与视图的初始化
要正确展示数据,需要分别对 Model(数据层)和
View(展示层)进行配置:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 m_model = new QSqlTableModel (this , m_db); m_model->setTable ("student" ); m_model->select (); m_model->setHeaderData (0 , Qt::Horizontal, "ID号" ); m_model->setHeaderData (1 , Qt::Horizontal, "姓名" ); m_model->setHeaderData (2 , Qt::Horizontal, "年龄" ); m_model->setEditStrategy (QSqlTableModel::OnManualSubmit); ui->tableView->setModel (m_model); ui->tableView->setAlternatingRowColors (true ); ui->tableView->verticalHeader ()->hide (); ui->tableView->horizontalHeader ()->setSectionResizeMode (QHeaderView::Stretch); ui->tableView->setSelectionBehavior (QAbstractItemView::SelectRows); ui->tableView->setColumnHidden (0 , true );
setEditStrategy(QSqlTableModel::OnManualSubmit)
是本例的关键:选择了“手动提交“,所有增删改先在内存中缓存,只有调用
submitAll()
才真正写入数据库。这样做的好处是可以配合数据库事务 ,确保一组操作要么全部成功、要么全部回滚。
5.2 增删行
1 2 3 4 5 6 7 int row_num = m_model->rowCount (); m_model->insertRow (row_num); int cur_row = ui->tableView->currentIndex ().row (); m_model->removeRow (cur_row);
5.3 事务提交与撤销
配合 OnManualSubmit
策略,结合数据库事务确保数据安全:
1 2 3 4 5 6 7 8 9 10 11 12 m_model->revertAll (); m_model->database ().transaction (); if (m_model->submitAll ()) { m_model->database ().commit (); QMessageBox::information (this , "提示" , "数据修改成功" ); } else { m_model->database ().rollback (); QMessageBox::warning (this , "错误" , "数据库错误" ); }
5.4 排序与过滤
QSqlTableModel 封装了 ORDER BY 和
WHERE 语句,直接调用方法即可实现:
1 2 3 4 5 6 7 8 9 m_model->setSort (2 , Qt::AscendingOrder); m_model->select (); QString input_name = "小明" ; m_model->setFilter (QString ("name = '%1'" ).arg (input_name));
5.5 完整代码
点击展开
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 #ifndef WIN_DEMO_H #define WIN_DEMO_H #include <QWidget> #include <QSqlDatabase> #include <QSqlTableModel> QT_BEGIN_NAMESPACE namespace Ui { class WinDemo ; } QT_END_NAMESPACE class WinDemo : public QWidget { Q_OBJECT public : explicit WinDemo (QWidget *parent = nullptr ) ; ~WinDemo () override ; public slots: void on_btnSubmit_clicked () ; void on_btnCancel_clicked () ; void on_btnAdd_clicked () ; void on_btnDel_clicked () ; void on_btnAsce_clicked () ; void on_btnDesc_clicked () ; void on_btnQuery_clicked () ; void on_btnAll_clicked () ; private : Ui::WinDemo *ui; QSqlDatabase m_db; QSqlTableModel *m_model; void setupView () ; void setupModel () ; }; #endif
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 #include "win_demo.h" #include "ui_win_demo.h" #include <QMessageBox> #include <QSqlError> #include "GenderDelegate.h" WinDemo::WinDemo (QWidget *parent) : QWidget (parent), ui (new Ui::WinDemo) { ui->setupUi (this ); m_db = QSqlDatabase::addDatabase ("QSQLITE" , "first" ); m_db.setDatabaseName ("/path/to/your/test.db" ); if (!m_db.open ()) { QMessageBox::critical (this , "警告" , QString ("database open failed: %1" ).arg (m_db.lastError ().text ())); return ; } m_model = new QSqlTableModel (this , m_db); m_model->setTable ("student" ); m_model->select (); setupModel (); ui->tableView->setModel (m_model); setupView (); ui->tableView->setItemDelegateForColumn (3 , new GenderDelegate (this )); } WinDemo::~WinDemo () { delete ui; } void WinDemo::on_btnSubmit_clicked () { m_model->database ().transaction (); if (m_model->submitAll ()) { if (m_model->database ().commit ()) { QMessageBox::information (this , "TableModel" , "数据修改成功" ); } } else { m_model->database ().rollback (); QMessageBox::warning (this , "TableModel" , QString ("数据库错误: %1" ).arg (m_model->lastError ().text ())); } } void WinDemo::on_btnCancel_clicked () { m_model->revertAll (); } void WinDemo::on_btnAdd_clicked () { int row_num = m_model->rowCount (); m_model->insertRow (row_num); } void WinDemo::on_btnDel_clicked () { int cur_row = ui->tableView->currentIndex ().row (); m_model->removeRow (cur_row); int ok = QMessageBox::warning (this , "删除当前行" , "你确定删除当前行?" , QMessageBox::Yes, QMessageBox::No); if (ok == QMessageBox::Yes) { m_model->submitAll (); } else { m_model->revertAll (); } } void WinDemo::on_btnAsce_clicked () { m_model->setSort (2 , Qt::AscendingOrder); m_model->select (); } void WinDemo::on_btnDesc_clicked () { m_model->setSort (2 , Qt::DescendingOrder); m_model->select (); } void WinDemo::on_btnQuery_clicked () { QString input_name = ui->query_data->text (); m_model->setFilter (QString ("name = '%1'" ).arg (input_name)); } void WinDemo::on_btnAll_clicked () { m_model->setTable ("student" ); m_model->select (); setupModel (); setupView (); } void WinDemo::setupView () { ui->tableView->setAlternatingRowColors (true ); ui->tableView->verticalHeader ()->hide (); ui->tableView->horizontalHeader ()->setSectionResizeMode (QHeaderView::Stretch); ui->tableView->setSelectionBehavior (QAbstractItemView::SelectRows); ui->tableView->setColumnHidden (0 , true ); } void WinDemo::setupModel () { m_model->setHeaderData (0 , Qt::Horizontal, "ID号" ); m_model->setHeaderData (1 , Qt::Horizontal, "姓名" ); m_model->setHeaderData (2 , Qt::Horizontal, "年龄" ); m_model->setEditStrategy (QSqlTableModel::OnManualSubmit); }
6. QSqlRelationalTableModel
实战
这一节用一个完整的例子说明外键关联的使用场景和方法。
6.1 问题场景
假设有两张表:
books 表:id, title, author, price,
category_id
category 表:id ,
name
books.category_id 通过外键引用
category.id。如果用 QSqlTableModel 直接显示
books 表,用户看到的分类列是一串数字
ID,完全不可读。我们需要的是显示分类名称 (如“计算机““文学“),并且编辑时提供下拉框 来选择分类。
这就是 QSqlRelationalTableModel 的用武之地。
6.2 完整代码
点击展开
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 #ifndef DB_WIDGET_H #define DB_WIDGET_H #include <QWidget> #include <QSqlDatabase> #include <QSqlRelationalTableModel> #include <QSqlTableModel> QT_BEGIN_NAMESPACE namespace Ui { class DBWidget ; } QT_END_NAMESPACE class DBWidget : public QWidget { Q_OBJECT public : explicit DBWidget (QWidget *parent = nullptr ) ; ~DBWidget () override ; public slots: void on_btnQuery_clicked () ; void on_btnAll_clicked () ; private : Ui::DBWidget *ui; QSqlDatabase m_db; QSqlTableModel *categoryModel; QSqlRelationalTableModel *model; }; #endif
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 #include "db_widget.h" #include "ui_db_widget.h" #include <QSqlTableModel> #include <QSqlRelationalTableModel> #include <QSqlRelationalDelegate> DBWidget::DBWidget (QWidget *parent) : QWidget (parent), ui (new Ui::DBWidget) { ui->setupUi (this ); m_db = QSqlDatabase::addDatabase ("QSQLITE" ); m_db.setDatabaseName ("/path/to/your/test.db" ); m_db.open (); model = new QSqlRelationalTableModel (this , m_db); model->setTable ("books" ); model->setRelation (4 , QSqlRelation ("category" , "id" , "name" )); model->setHeaderData (4 , Qt::Horizontal, "图书分类" ); model->setEditStrategy (QSqlTableModel::OnManualSubmit); model->select (); ui->tableView->setModel (model); ui->tableView->setItemDelegate (new QSqlRelationalDelegate (ui->tableView)); categoryModel = new QSqlTableModel (this , m_db); categoryModel->setTable ("category" ); categoryModel->select (); ui->filterCombo->setModel (categoryModel); ui->filterCombo->setModelColumn (1 ); } DBWidget::~DBWidget () { delete ui; } void DBWidget::on_btnQuery_clicked () { int current_row = ui->filterCombo->currentIndex (); QModelIndex idIndex = categoryModel->index (current_row, 0 ); QString categoryID = categoryModel->data (idIndex).toString (); model->setFilter (QString ("category_id = '%1'" ).arg (categoryID)); model->select (); } void DBWidget::on_btnAll_clicked () { model->setFilter ("" ); model->select (); }
6.3 按外键分类过滤
上面的完整代码中已经演示了一个实用场景:使用下拉框按分类筛选图书。
思路是:用一个独立的 QSqlTableModel 给
QComboBox 提供分类列表,用户选择某个分类后,用该分类的 ID
去 QSqlRelationalTableModel 中设置
setFilter()。
关键点在于:QComboBox 本身也用了 MVD
架构——setModel() 绑定数据源,setModelColumn()
指定显示哪一列。而 on_btnQuery_clicked() 中通过
categoryModel->index(current_row, 0) 拿到分类 ID,再用
model->setFilter()
去过滤主表,on_btnAll_clicked()
则清空过滤条件恢复全量显示。
7. 自定义委托
默认情况下,双击 QTableView
的单元格弹出的都是普通文本输入框。如果我们需要特定的显示和编辑方式(例如:数据库里存的是整数
0/1,界面上需要显示
女/男,且编辑时弹出下拉菜单 ),就需要使用自定义委托(Delegate) 。
实现一个自定义委托,需要继承 QStyledItemDelegate
并重写四个核心方法。以下是“性别下拉框代理“的完整实现:
点击展开
GenderDelegate.h GenderDelegate.cpp 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 #ifndef GENDER_DELEGATE_H #define GENDER_DELEGATE_H #include <QStyledItemDelegate> class GenderDelegate : public QStyledItemDelegate { Q_OBJECT public : GenderDelegate (QObject *parent = nullptr ); ~GenderDelegate () override = default ; QString displayText (const QVariant &value, const QLocale &locale) const override ; QWidget *createEditor (QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const override ; void setEditorData (QWidget *editor, const QModelIndex &index) const override ; void setModelData (QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const override ; }; #endif
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 #include "GenderDelegate.h" #include <QComboBox> GenderDelegate::GenderDelegate (QObject *parent) : QStyledItemDelegate (parent) { } QString GenderDelegate::displayText (const QVariant &value, const QLocale &locale) const { int val = value.toInt (); return (val == 1 ) ? "男" : "女" ; } QWidget *GenderDelegate::createEditor (QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const { QComboBox *editor = new QComboBox (parent); editor->addItem ("女" , 0 ); editor->addItem ("男" , 1 ); return editor; } void GenderDelegate::setEditorData (QWidget *editor, const QModelIndex &index) const { int value = index.model ()->data (index, Qt::EditRole).toInt (); QComboBox *cb = static_cast <QComboBox *>(editor); cb->setCurrentIndex (cb->findData (value)); } void GenderDelegate::setModelData (QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const { QComboBox *cb = static_cast <QComboBox *>(editor); int value = cb->currentData ().toInt (); model->setData (index, value, Qt::EditRole); }
写好委托类后,只需在初始化时将其分配给视图的特定列即可:
1 2 ui->tableView->setItemDelegateForColumn (3 , new GenderDelegate (this ));
View 在处理这一列时会自动走我们自定义的显示和编辑逻辑。这 4
个方法的调用时机是:
渲染单元格时 → displayText()
被调用,决定显示什么文字
用户双击进入编辑 → createEditor() 创建编辑控件
编辑控件出现后 → setEditorData() 把 Model
的旧值填进去
用户编辑完成 → setModelData() 把新值写回 Model
这样就完成了从“数据库原始值“到“用户友好界面“的完整映射。