暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

QT 避坑指南:使用QSqlModel结合QTabelView快速实现数据库增删改查

猿武场 2022-03-14
3105

「 QT 避坑指南 」远离那些躺过无数次的坑

  整理 | 猿胖子

  出品 | 猿武场(ID:apesarena)

关注公众号并回复数字「 1024 」加入猿武场微信社群 

QT已经封装了常用的数据库驱动,我们可以很便对数据库操作,那么先来看看基于 SQLite 数据库操作的实例吧

首先,完成基本的界面布局创建

实例中的界面布局依次从上至下分为简单的三个区域(顶部功能按钮区域、中部编辑区域、底部表格区域)


在项目 pro 文件中添加 SQL 模块支持,如下


 1QT       += core gui
2
3# 在此处添加 SQL 模块支持
4QT       += sql
5
6greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
7
8CONFIG += c++11
9
10# You can make your code fail to compile if it uses deprecated APIs.
11# In order to do so, uncomment the following line.
12#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000    # disables all the APIs deprecated before Qt 6.0.0
13
14SOURCES += \
15    custommodel.cpp \
16    main.cpp \
17    mainwindow.cpp
18
19HEADERS += \
20    custommodel.h \
21    mainwindow.h


接下来,简单创建个交互界面


 1/**
2* 初始化 UI 布局
3**/

4void MainWindow::initUI()
5{
6    QWidget *main = new QWidget;
7    this->setCentralWidget(main);
8    QVBoxLayout *layout = new QVBoxLayout(main);
9
10    QTableView *view = new QTableView;
11    view->setObjectName("view");
12    QHBoxLayout *menu_layout = new QHBoxLayout;
13    QPushButton *add_btn = new QPushButton("新增");
14    menu_layout->addWidget(add_btn);
15    QPushButton *modify_btn = new QPushButton("修改");
16    menu_layout->addWidget(modify_btn);
17    QPushButton *del_btn = new QPushButton("删除");
18    menu_layout->addWidget(del_btn);
19
20    QVBoxLayout *content_layout = new QVBoxLayout;
21    QLineEdit *id_edit = new QLineEdit;
22    id_edit->setObjectName("id_edit");
23    id_edit->setPlaceholderText("序号");
24    id_edit->setEnabled(false);
25    content_layout->addWidget(id_edit);
26    QLineEdit *name_edit = new QLineEdit;
27    name_edit->setObjectName("name_edit");
28    name_edit->setPlaceholderText("姓名");
29    content_layout->addWidget(name_edit);
30    QLineEdit *power_edit = new QLineEdit;
31    power_edit->setObjectName("power_edit");
32    power_edit->setPlaceholderText("战力");
33    content_layout->addWidget(power_edit);
34    QLineEdit *create_edit = new QLineEdit;
35    create_edit->setObjectName("create_edit");
36    create_edit->setPlaceholderText("创建时间");
37    create_edit->setEnabled(false);
38    content_layout->addWidget(create_edit);
39    QLineEdit *update_edit = new QLineEdit;
40    update_edit->setObjectName("update_edit");
41    update_edit->setPlaceholderText("更新时间");
42    update_edit->setEnabled(false);
43    content_layout->addWidget(update_edit);
44
45    layout->addLayout(menu_layout);
46    layout->addLayout(content_layout);
47    layout->addWidget(view);
48}


完成这些,我们就看到运行起来的效果



添加 SQLite 数据库并创建表

我们在 mainwindow.h 中新增一个 initDB 函数,用于初始化 SQLite 连接、创建用户表和插入一些默认数据


 1#ifndef MAINWINDOW_H
2#define MAINWINDOW_H
3
4#include <QMainWindow>
5#include <QSqlDatabase>
6#include <QTableView>
7
8class MainWindow : public QMainWindow
9{
10    Q_OBJECT
11
12public:
13    MainWindow(QWidget *parent = nullptr);
14    ~MainWindow();
15
16private:
17    QSqlDatabase db;
18private:
19    void initUI();
20    bool initDB();


注意引用头文件 QSsqlDatabase


 1/**
2* 初始化数据库
3* 创建表并插入初始数据
4**/

5bool MainWindow::initDB()
6{
7    bool result = false;
8
9    // 创建 SQLite 连接
10    db = QSqlDatabase::addDatabase("QSQLITE");
11    // 设置数据库文件路径
12    db.setDatabaseName(QString("%1/apesarena.db").arg(QDir::currentPath()));
13
14    // 打开数据库连接
15    db.open();
16    if(!exists("tbl_users")){
17        // 创建用户表
18        QSqlQuery query(db);
19        bool result = query.exec("CREATE TABLE tbl_users (id INTEGER PRIMARY KEY AUTOINCREMENT, name VARCHAR(20),"
20                                 "power INT,create_date TIMESTAMP DEFAULT (datetime('now','localtime')),"
21                                 "update_date TIMESTAMP DEFAULT (datetime('now','localtime')))");
22        if(!result)
23            return result;
24        query.clear();
25
26        // 批量插入测试数据
27        QVariantList val_name;
28        val_name << "C" << "C++" << "QT";
29        QVariantList val_power;
30        val_power << 3000 << 2000 << 1000;
31
32        query.prepare("INSERT INTO tbl_users VALUES(NULL,:name,:power,(datetime('now','localtime')),(datetime('now','localtime')))");
33        query.bindValue(":power",val_power);
34        query.bindValue(":name",val_name);
35        result = query.execBatch();
36    }
37    return result;
38}

注意 SQLite 主键自增

这里有个小坑,如果你在创建语句中主键使用的是 INT 而非 INTEGER ,那么恭喜你 SQL语句会执行成功,但主键并不会自增。这里要实现自增除了写明AUTOINCREMENT 外不能是 INT 一定的写 INTEGER。


1// 主键 ID 自增
2// 必须是 INTEGER
3
4id INTEGER PRIMARY KEY AUTOINCREMENT
5
6// 完整创建语句
7query.exec("CREATE TABLE tbl_users (id INTEGER PRIMARY KEY AUTOINCREMENT, name VARCHAR(20),"
8           "power INT,create_date TIMESTAMP DEFAULT (datetime('now','localtime')),"
9           "update_date TIMESTAMP DEFAULT (datetime('now','localtime')))");

将数据从库中读取并绑定到 QTableView

我们使用QTableModel读取数据库,使用也是非常简单


 1// QSqlTableModel
2
3QSqlTableModel *model = new QSqlTableModel(this,db);
4model->setEditStrategy(QSqlTableModel::OnManualSubmit);
5
6// 设置访问 tbl_users 表
7model->setTable("tbl_users");
8// 查询 tbl_users 表所以数据
9model->select();
10
11// model 绑定到 QTableView 显示
12findChild<QTableView *>("view")->setModel(model);


这就 OK 了, 在 QSqlTableModel 中呢还可以设置排序和过滤条件,调用对应的方法就可以了,非常简单易用


1// 设置排序 按第二列升序 ,参数1指定第几列,参数2升序或降序
2model->setSort(1,Qt::SortOrder::AscendingOrder);
3
4// 查询 power 大于 100 的数据
5model->setFilter("power > 100");


看看程序运行结果是否符合预期


已经能顺利读取出来我们初始化时添加的数据了。但 QTableview 中的数据没有居中显示,表头也需要设置成我们所期望的代称而不是实际的字段名称。

修改表头显示,设置 View Item 内容居中 

设置内容居中这个问题,目前并不能使用QSS来解决,只能通过重写QSqlTabelModel 的 data 函数来实现。


 1#ifndef CUSTOMMODEL_H
2#define CUSTOMMODEL_H
3
4#include <QObject>
5#include <QSqlTableModel>
6#include <QVariant>
7#include <QSqlDatabase>
8
9class CustomModel : public QSqlTableModel
10{
11    Q_OBJECT
12public:
13    explicit CustomModel(QObject *parent = nullptr,QSqlDatabase db = QSqlDatabase());
14
15protected:
16    virtual QVariant data(const QModelIndex &idx, int role = Qt::DisplayRole) const override;
17signals:
18
19};
20
21#endif // CUSTOMMODEL_H


创建 CustomModel 类继承 QSqlTableModel ,重写其 data 函数 ,如下


 1QVariant CustomModel::data(const QModelIndex &idx, int role) const
2{
3
4    // 设置 Model 内容居中
5
6    QVariant value = QSqlQueryModel::data(idx,role);
7    if(Qt::TextAlignmentRole == role)
8        value = Qt::AlignCenter;
9    return value;
10}


在 CustomMode 中新增 setHeaderLabels 函数,用于设置自定义表头


 1// custommodel.h
2
3class CustomModel : public QSqlTableModel
4{
5    Q_OBJECT
6public:
7    explicit CustomModel(QObject *parent = nullptr,QSqlDatabase db = QSqlDatabase());
8
9// 用于设置表头
10void setHeaderLabels(const QStringList &labels);
11
12...
13
14// custommodel.cpp 
15
16void CustomModel::setHeaderLabels(const QStringList &labels)
17{
18    for(int i=0;i<labels.size();++i)
19        setHeaderData(i,Qt::Horizontal,labels.at(i));
20}
21
22...


完成后,我们需要在 mainwindow 中稍作修改,调用自定义表头设置


 1// custommodel.h
2
3void MainWindow::bindData()
4{
5    // 简略部分代码 ... 
6
7    model->setTable("tbl_users");
8    model->select();
9
10   // 设置自定义表头
11    model->setHeaderLabels(QString("序号,名讳,战力,绝技,创建时间,更新时间").split(",")); 
12
13    findChild<QTableView *>("view")->setModel(model);
14}
15
16
17...

运行程序,我们将看到表头及内容居中均已按预期显示

数据库操作:新增

通过 QSqlTabelModel 新增一条记录保存到数据库


 1void MainWindow::addData()
2{
3    // 获取用户输入
4    QString name = findChild<QLineEdit *>("name_edit")->text().trimmed();
5    QString power = findChild<QLineEdit *>("power_edit")->text().trimmed();
6
7    // 获取当前的 QSqlTableModel 实例
8    CustomModel *model = findChild<CustomModel *>("cmodel");
9    QSqlRecord record = model->record();
10    record.setValue("name",name);
11    record.setValue("power",power);
12    QString dt = QDateTime::currentDateTime().toString("yyyy-MM-dd hh:ss:mm");
13    record.setValue("create_date",dt);
14    record.setValue("update_date",dt);
15
16    // 新增记录
17    model->insertRecord(model->rowCount(),record);
18
19    // 提交修改
20    model->submitAll();
21...
22
23}


数据库操作:修改

通过 QSqlTabelModel 修改一条记录并保存到数据库


 1void MainWindow::modifyData()
2{
3    // 获取当前 View 选中的行
4    int index = findChild<QTableView *>("view")->currentIndex().row();
5    if(index<0)
6        return;
7
8    // 获取用户修改后的数据
9    QString name = findChild<QLineEdit *>("name_edit")->text().trimmed();
10    QString power = findChild<QLineEdit *>("power_edit")->text().trimmed();
11    QString create = findChild<QLineEdit *>("create_edit")->text().trimmed();
12
13    CustomModel *model = findChild<CustomModel *>("cmodel");
14    QSqlRecord record = model->record(index);
15    record.setValue("name",name);
16    record.setValue("power",power);
17    record.setValue("create_date",create);
18    record.setValue("update_date",QDateTime::currentDateTime().toString("yyyy-MM-dd hh:ss:mm"));
19
20    model->setRecord(index,record);
21
22    // 提交变更
23    model->submitAll();
24...


数据库操作:删除

通过 QSqlTabelModel 删除一条 View 中选中的记录并提交到数据库


 1void MainWindow::delData()
2{
3    CustomModel *model = findChild<CustomModel *>("cmodel");
4
5    // 删除当前 View 选中的行,如果未选中( row == -1 )不会有变化
6    model->removeRow(findChild<QTableView *>("view")->currentIndex().row());
7    // 提交变更
8    model->submitAll();
9
10...


最后,给对应的按钮添加响应事件


 1...
2
3    // View 点击绑定数据到文本框
4    connect(view,&QTableView::clicked,[=](QModelIndex index){
5        CustomModel *model = findChild<CustomModel *>("cmodel");
6        QSqlRecord record = model->record(index.row());
7        findChild<QLineEdit *>("id_edit")->setText(record.value(record.indexOf("id")).toString());
8        findChild<QLineEdit *>("name_edit")->setText(record.value(record.indexOf("name")).toString());
9        findChild<QLineEdit *>("power_edit")->setText(record.value(record.indexOf("power")).toString());
10        findChild<QLineEdit *>("create_edit")->setText(record.value(record.indexOf("create_date")).toDateTime().toString("yyyy-MM-dd hh:ss:mm"));
11        findChild<QLineEdit *>("update_edit")->setText(record.value(record.indexOf("update_date")).toDateTime().toString("yyyy-MM-dd hh:ss:mm"));
12    });
13    // 自适应宽度
14    view->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch);
15    // 整行选中
16    view->setSelectionBehavior(QAbstractItemView::SelectRows); 
17     // 单元格不可编辑
18    view->setEditTriggers(QAbstractItemView::NoEditTriggers);
19    view->horizontalHeader()->font().bold(); // 表头文字加粗
20    view->verticalHeader()->setHidden(true); // 不显示默认的序号列
21    view->setFocus(Qt::NoFocusReason);
22
23    QHBoxLayout *menu_layout = new QHBoxLayout;
24    QPushButton *add_btn = new QPushButton("新增");
25    menu_layout->addWidget(add_btn);
26    // 新增数据事件
27    connect(add_btn,&QPushButton::clicked,[=](){addData();});
28    QPushButton *modify_btn = new QPushButton("修改");
29    menu_layout->addWidget(modify_btn);
30    // 修改数据事件
31    connect(modify_btn,&QPushButton::clicked,[=](){modifyData();});
32    QPushButton *del_btn = new QPushButton("删除");
33    menu_layout->addWidget(del_btn);
34    // 删除数据事件
35    connect(del_btn,&QPushButton::clicked,[=](){delData();});
36...


如果您喜欢本期教程欢迎、转发、关注 !


更多详细教程,关注本公众号留言获取。



版权声明:本文来自原创,版权归猿武场作者所有。如需转载,请联系作者并注明出处。


注公众号并回复数字「 1024 」加入猿武场微信社群 

欢迎加入程序员社群,更多技术摘要等你拿走

社群福利:

1. 行业大牛技术手札,知识点汇总

2. 求职/招聘信息内推

4. 人际交往,增强技术宅人际交流;

5. 调节繁杂无趣的闲暇时光;

6. 不定期线上周边线下技术活动沙龙


代 码 / 改 / 变 / 世 / 界
感谢您对猿武场的关注与支持


文章转载自猿武场,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论