「 QT 避坑指南 」远离那些躺过无数次的坑
整理 | 猿胖子
出品 | 猿武场(ID:apesarena)
关注公众号并回复数字「 1024 」加入猿武场微信社群
首先,完成基本的界面布局创建
实例中的界面布局依次从上至下分为简单的三个区域(顶部功能按钮区域、中部编辑区域、底部表格区域)
在项目 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}
完成这些,我们就看到运行起来的效果
我们在 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}
这里有个小坑,如果你在创建语句中主键使用的是 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')))");
我们使用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 中的数据没有居中显示,表头也需要设置成我们所期望的代称而不是实际的字段名称。
设置内容居中这个问题,目前并不能使用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. 不定期线上周边於线下技术活动沙龙。