数据库

数据库SQLite
SQLite 是一种轻量级的关系数据库管理系统,不需要单独的服务器进程,适合嵌入式应用。Qt 提供了丰富的 SQL 模块,使得在应用程序中集成数据库操作变得简单。本文将通过示例介绍如何使用 Qt 的 SQL 类与 SQLite 数据库进行交互,特别是如何使用 QSqlTableModel、QSqlQueryModel 以及 QSqlQuery 来实现数据的增删改查(CRUD)操作。
我们可以提前用navicat建立一个sqlite的数据库

创建sqlite3数据库,链接名字随便写,数据库名字我命名为example_sqlite

记得点一下数据库文件右边的三个小点的按钮,将数据库另存一下

点击确定后,会创建好sqlite数据库, 可以看到数据库下面有个main的库,库下面有表之类的结构。

建立好数据库后我们可以通过右键表,创建新的表结构

但是我们这里讲述的是如何通过QT代码创建表,所以不在navicat中手动创建表。

我们需要通过代码在数据库中创建一张employees表,并且插入数据

配置 Qt 项目

创建一个新的 Qt Widgets 应用程序项目,假设项目名称为 QtSQLiteExample

项目文件(.pro)配置:

要使用 Qt 的 SQL 模块,需要在项目文件中添加相关模块:sql

QT += core gui sql

greaterThan(QT_MAJOR_VERSION, 4): QT += widgets

TARGET = QtSQLiteExample
TEMPLATE = app

SOURCES += main.cpp \
           mainwindow.cpp

HEADERS += mainwindow.h

FORMS += mainwindow.ui

建立与 SQLite 的连接

在使用任何 SQL 模型或查询之前,需要先建立与 SQLite 数据库的连接。

1. 包含必要的头文件

在需要使用 SQL 功能的文件中,包含 Qt SQL 模块的头文件:

#include <QSqlDatabase>
#include <QSqlError>
#include <QSqlQuery>
#include <QDebug>

2. 初始化数据库连接

通常,可以在 main.cpp 或应用程序的初始化部分进行数据库连接设置。创建连接的接口如下


static QSqlDatabase addDatabase(const QString& type,
                             const QString& connectionName = QLatin1String(defaultConnection));

第一个参数type表示数据库的类型,第二个参数connectionName表示连接的名字,我们可以为连接指定名字,如果不设置就用默认连接。

以下示例在 main.cpp 中建立连接

// 添加 SQLite 数据库驱动
QSqlDatabase db = QSqlDatabase::addDatabase("QSQLITE");
// 设置数据库文件路径(相对路径或绝对路径)
db.setDatabaseName("example.db");

如果程序的执行路径下没有example.db,上述代码会在程序的运行路径创建一个"example.db"文件。

如果我们希望用户选择db文件可以用文件对话框,QFileDialog有一个static函数用来打开文件

   static QString getOpenFileName(QWidget *parent = nullptr,
                                   const QString &caption = QString(),
                                   const QString &dir = QString(),
                                   const QString &filter = QString(),
                                   QString *selectedFilter = nullptr,
                                   Options options = Options());

第一个参数是父窗口,第二个参数是标题,第三个参数是默认打开哪个文件夹,第四个参数是过滤器,用来指定显示哪些文件。

函数会返回所选择的文件的路径。

我们将建立连接代码稍作修改

// 利用文件对话框打开选择db文件
auto file_path = QFileDialog::getOpenFileName(nullptr,"选择sqlitedb文件","","*.db");
if(file_path.isEmpty()){
    QMessageBox::critical(nullptr,"文件错误信息","未选择文件!");
    return false;
}

我们设置默认选择db文件,如果不选择返回的就是空字符串,空字符串直接判断无效就返回。

接下来我们再调用之前写好的逻辑创建驱动并且设置数据库文件路径

// 添加 SQLite 数据库驱动
QSqlDatabase db = QSqlDatabase::addDatabase("QSQLITE");

// 设置数据库文件路径(相对路径或绝对路径)
db.setDatabaseName(file_path);

创建好db后我们打开数据库

// 打开数据库
if (!db.open()) {
    qDebug() << "无法打开数据库:" << db.lastError().text();
    return false;
}

接下来创建数据表,创建数据表的语句有多行,所以采用QT提供R”()“方式,表示原样字符串,我们可以把多行的字符串写到括号里

// 创建一个示例表,如果不存在的话
QSqlQuery query;
QString createTable = R"(
    CREATE TABLE IF NOT EXISTS employees (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        name TEXT NOT NULL,
        position TEXT NOT NULL,
        salary REAL
    )
)";

我们写了创建数据表的语句,接下来可以通过query去执行, query是QSqlQuery类型,包含exec方法传入要执行的sql语句,返回执行结果。

if (!query.exec(createTable)) {
    qDebug() << "创建表失败:" << query.lastError().text();
    return false;
}

同样的道理,我们可以基于employees表进行增删改查了, 我们可以查询一下employees表中的记录数,如果记录数为0,则向表中插入数据。我们能可以在navicat中新建一个查询,看看查到的结果

一般来说查询语句会返回多行,每一行有多列,因为count比较特殊,只返回1行1列表示员工数量。

所以我们可以取查询结果的第一列,下标为0获取记录数。

// 插入示例数据(如果表为空)
query.exec("SELECT COUNT(*) FROM employees");
if (query.next()) {
    //查询结果的第0列表示数量
    int count = query.value(0).toInt();
    if (count == 0) {
        QString insertData = "INSERT INTO employees (name, position, salary) VALUES "
                             "('Alice', 'Developer', 60000),"
                             "('Bob', 'Designer', 55000),"
                             "('Charlie', 'Manager', 75000)";
        if (!query.exec(insertData)) {
            qDebug() << "插入数据失败:" << query.lastError().text();
            return false;
        }
    }
}

所以我们给出完整代码

bool initializeDatabase()
{
    // 利用文件对话框打开选择db文件
    auto file_path = QFileDialog::getOpenFileName(nullptr,"选择sqlitedb文件","","*.db");
    if(file_path.isEmpty()){
        QMessageBox::critical(nullptr,"文件错误信息","未选择文件!");
        return false;
    }

    // 添加 SQLite 数据库驱动,使用默认连接
    QSqlDatabase db = QSqlDatabase::addDatabase("QSQLITE");

    // 设置数据库文件路径(相对路径或绝对路径)
    db.setDatabaseName(file_path);

    // 打开数据库
    if (!db.open()) {
        qDebug() << "无法打开数据库:" << db.lastError().text();
        return false;
    }

    // 创建一个示例表,如果不存在的话
    QSqlQuery query;
    QString createTable = R"(
        CREATE TABLE IF NOT EXISTS employees (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            name TEXT NOT NULL,
            position TEXT NOT NULL,
            salary REAL
        )
    )";

    if (!query.exec(createTable)) {
        qDebug() << "创建表失败:" << query.lastError().text();
        return false;
    }

    // 插入示例数据(如果表为空)
    query.exec("SELECT COUNT(*) FROM employees");
    if (query.next()) {
        //查询结果的第0列表示数量
        int count = query.value(0).toInt();
        if (count == 0) {
            QString insertData = "INSERT INTO employees (name, position, salary) VALUES "
                                 "('Alice', 'Developer', 60000),"
                                 "('Bob', 'Designer', 55000),"
                                 "('Charlie', 'Manager', 75000)";
            if (!query.exec(insertData)) {
                qDebug() << "插入数据失败:" << query.lastError().text();
                return false;
            }
        }
    }

    return true;
}

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    if(!initializeDatabase()){
        QMessageBox::information(nullptr,"数据库错误","创建数据库失败");
        return -1;
    }
    MainWindow w;
    w.show();

    return a.exec();
}

启动程序选择我们之前创建好的数据库,程序会在数据库中创建employees表,并且插入数据

QSqlTableModel

QSqlTableModel 是 Qt 提供的一个模型类,专门用于操作数据库中的单一表。它基于 SQL 数据库驱动,支持常见的数据库如 SQLite、MySQL、PostgreSQL 等。主要特点包括:

  • 直接操作单一表:能够方便地对单个数据库表进行增删改查操作。
  • 与视图的无缝集成:可以轻松地与 QTableViewQListView 等视图组件配合使用,实现数据的可视化展示和编辑。
  • 支持多种编辑策略:提供灵活的方式来管理数据的提交和取消。
  • 自动同步数据:在数据库和模型之间保持数据的一致性。

使用时要用#include <QSqlTableModel>

基本使用流程

使用 QSqlTableModel 的基本步骤如下:

  1. 建立数据库连接:确保与数据库的连接已建立,并且目标表存在。
  2. 创建模型实例:实例化 QSqlTableModel 对象。
  3. 设置目标表:指定要操作的数据库表。
  4. 选择数据:从数据库中检索数据以填充模型。
  5. 与视图集成:将模型与视图组件(如 QTableView)绑定,以显示和编辑数据。
  6. 操作数据:通过模型的接口插入、删除或修改数据。
  7. 提交或还原更改:根据编辑策略提交更改到数据库或撤销更改。

关键函数与属性

1 构造函数

QSqlTableModel(QObject *parent = nullptr, QSqlDatabase db = QSqlDatabase())
  • parent:父对象,通常设为 nullptr
  • db:使用的数据库连接,如果不传递参数,默认使用默认连接。

2 主要函数

  • setTable(const QString &tableName):设置模型所操作的数据库表。
  • select():从数据库中检索数据并填充模型。
  • setEditStrategy(QSqlTableModel::EditStrategy strategy):设置编辑策略。
  • insertRow(int row, const QModelIndex &parent = QModelIndex()):在指定行插入新行。
  • removeRow(int row, const QModelIndex &parent = QModelIndex()):删除指定行。
  • submitAll():提交所有更改到数据库(适用于手动提交策略)。
  • revertAll():撤销所有未提交的更改。
  • setFilter(const QString &filter):设置数据过滤条件。
  • setSort(int column, Qt::SortOrder order):设置排序条件。

4. 编辑策略

QSqlTableModel 提供了三种编辑策略,用于控制对数据库的更改是如何提交的:

  1. QSqlTableModel::OnManualSubmit(手动提交):
    • 默认策略。
    • 所有更改(插入、删除、修改)都存储在模型中,直到调用 submitAll() 才提交到数据库。
    • 可以通过 revertAll() 撤销所有未提交的更改。
    • 适用于需要在一次性提交所有更改的场景,例如用户完成一系列编辑后再决定提交。
  1. QSqlTableModel::OnRowChange(行更改时提交):
    • 当用户完成对一行的编辑并移动到另一行时,自动提交该行的更改。
    • 更改直接提交到数据库。
    • 适用于希望实时保存每行更改的场景。
  1. QSqlTableModel::OnFieldChange(字段更改时提交):
    • 当用户更改任何字段的值时,立即提交该更改到数据库。
    • 适用于需要每次字段编辑后立即保存更改的场景。

设置编辑策略示例:

model->setEditStrategy(QSqlTableModel::OnManualSubmit);

演示案例

我们通过QSqlTableModel和QTableView展示数据库的内容,并且支持在表格中修改提交保存到数据库

界面效果如下

我们在mainwindow中添加变量和函数声明

//数据模型
QSqlTableModel *model;
//设置模型
void setupModel();
//设置视图
void setupView();

实现setupModel

void MainWindow::setupModel()
{
    //创建模型
    model = new QSqlTableModel(this);
    //使用默认连接,对表employees操作
    model->setTable("employees");
    //设置提交策略为手动提交
    model->setEditStrategy(QSqlTableModel::OnManualSubmit);

    // 设置表头标签
    model->setHeaderData(0, Qt::Horizontal, "ID");
    model->setHeaderData(1, Qt::Horizontal, "姓名");
    model->setHeaderData(2, Qt::Horizontal, "职位");
    model->setHeaderData(3, Qt::Horizontal, "薪水");

    //将sql中的数据写入model中
    if (!model->select()) {
        QMessageBox::critical(this, "错误", "无法检索数据: " + model->lastError().text());
    }
}

在mainwindow构造函数中调用setupModel将数据加载到模型中

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::MainWindow)
{
    ui->setupUi(this);
    setupModel();
}

我们回到mainwindow.ui中添加tableview和几个按钮,下面是整体布局

下面是布局属性图

模型加载成功后,将模型加载到tableview中。

void MainWindow::setupView()
{
    //设置模型
    ui->tableView->setModel(model);
    //设置选中模式为单个选中
    ui->tableView->setSelectionMode(QAbstractItemView::SingleSelection);
    //设置整行选中
    ui->tableView->setSelectionBehavior(QAbstractItemView::SelectRows);
    //设置编辑触发模式为双击或者选中时点击
    ui->tableView->setEditTriggers(QAbstractItemView::DoubleClicked
                                   | QAbstractItemView::SelectedClicked);
    //重置列宽自适应内容
    ui->tableView->resizeColumnsToContents();
}

在构造函数中加载视图

MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::MainWindow)
{
    ui->setupUi(this);
    //加载模型
    setupModel();
    //加载视图
    setupView();
}

运行程序,选择db后弹出界面如下

接下来我们实现增加功能, 点击添加后默认添加一行数据

ui中右键添加按钮,选择槽函数跳转到实现,然后写具体添加逻辑

//添加按钮
void MainWindow::on_addBtn_clicked()
{
    //获取模型行数
    int row = model->rowCount();
    //在最后一行插入
    if (!model->insertRow(row)) {
        QMessageBox::warning(this, "错误", "无法插入行: "
                             + model->lastError().text());
        return;
    }

    // 自动设置默认值(可选)
    model->setData(model->index(row, 1), "新员工");
    model->setData(model->index(row, 2), "职位");
    model->setData(model->index(row, 3), 50000.0);

    //提交新插入的数据
    if (!model->submitAll()) {
        QMessageBox::warning(this, "错误", "无法提交新行: "
                             + model->lastError().text());
    }
}

查看数据库可以看到数据是写入成功了

接下来我们实现删除逻辑

void MainWindow::on_delBtn_clicked()
{
    //获取选中的行
    QModelIndexList selection = ui->tableView->selectionModel()->selectedRows();
    if (selection.isEmpty()) {
        QMessageBox::information(this, "提示", "请选择要删除的行。");
        return;
    }

    //行号列表
    QList<int> row_list;
    //遍历选中,将行号存起来
    foreach (QModelIndex index, selection) {
        row_list.append(index.row());
    }

    //排序从大到小
    std::sort(row_list.begin(),row_list.end(),std::greater<int>());

    //从行号大的删除
    for(auto & row : row_list){
        model->removeRow(row);
    }

    //提交修改
    if (!model->submitAll()) {
        QMessageBox::warning(this, "错误", "无法删除行: " + model->lastError().text());
    }
}

实现提交逻辑, 当我们修改表格数据后可以将修改提交到数据库

void MainWindow::on_subBtn_clicked()
{
    //提交模型修改到数据库
    if (!model->submitAll()) {
        QMessageBox::warning(this, "错误", "无法提交更改: " + model->lastError().text());
    } else {
        QMessageBox::information(this, "成功", "更改已提交。");
    }
}

我们新建一条数据,在数据库后台看到的还是默认值,修改后不提交,发现数据库也没变,点击提交后,再查看数据库就发现记录写入数据库了。

接下来实现撤销逻辑,当我们修改后点击还原,会将model还原为和数据库一致的状态

void MainWindow::on_revertBtn_clicked()
{
    //还原数据模型
    model->revertAll();
    QMessageBox::information(this, "已还原", "所有未提交的更改已还原。");
}

拓展

控制列宽

之前的表格列的宽度太小了,而且我们希望每一列文字居中对齐,并且我们希望薪水可以限制,

那么重新实现代理

#ifndef MYDELEGATE_H
#define MYDELEGATE_H
#include <QStyledItemDelegate>
#include <QPainter>
#include <QSpinBox>

class Mydelegate : public QStyledItemDelegate
{
public:
    Mydelegate(QObject *parent = nullptr):QStyledItemDelegate(parent){}
    void paint(QPainter *painter,
               const QStyleOptionViewItem &option, const QModelIndex &index)
    const override{
        //拷贝一份样式的副本,防止对引用做出污染
        QStyleOptionViewItem opt = option;
        //绑定opt和index
        initStyleOption(&opt, index);

        // 根据列设置不同的对齐方式
        opt.displayAlignment = Qt::AlignCenter;

        // 调用基类绘制(确保选中状态等被正确处理)
        QStyledItemDelegate::paint(painter, opt, index);
    }

    //创建编辑器
    QWidget *createEditor(QWidget *parent,
                          const QStyleOptionViewItem &option,
                          const QModelIndex &index) const override{
        //判断如果是第三列,则创建spinbox
        if(index.column() == 3){
            //创建spinbox
            QSpinBox *editor = new QSpinBox(parent);
            //设置最低薪水
            editor->setMinimum(3000);
            //设置最高薪水
            editor->setMaximum(2000000);
            //设置spinbox步长
            editor->setSingleStep(1000);
            return editor;
        }
        //其他列调用基类默认函数创建
        return QStyledItemDelegate::createEditor(parent,option,index);
    }

    //将模型的数据放入编辑器中
    void setEditorData(QWidget *editor, const QModelIndex &index) const override{
        //如果是第三列
        if(index.column() == 3){
            //获取数据
            int value = index.model()->data(index, Qt::EditRole).toInt();
            QSpinBox *spinBox = static_cast<QSpinBox*>(editor);
            //将数据设置到编辑器中
            spinBox->setValue(value);
            return;
        }
        //其他列调用基类设置数据
        return QStyledItemDelegate::setEditorData(editor,index);
    }
    //将编辑其中的数据放入模型中
    void setModelData(QWidget *editor,
                      QAbstractItemModel *model,
                      const QModelIndex &index) const override{
        if(index.column() == 3){
            //editor强制转换为QSpinBox
            QSpinBox *spinBox = static_cast<QSpinBox*>(editor);
            //检测输入是否合法,做数值限制
            spinBox->interpretText();
            //获取spinBox的值
            int value = spinBox->value();
            //将这个值写入模型
            model->setData(index, value, Qt::EditRole);
            return;
        }

        //其他类型调用默认的设置函数
        QStyledItemDelegate::setModelData(editor,model,index);
    }
    //更新编辑区域
    void updateEditorGeometry(QWidget *editor,
                              const QStyleOptionViewItem &option,
                              const QModelIndex &index) const override{
        //设置编辑区域
        editor->setGeometry(option.rect);
    }
};

#endif // MYDELEGATE_H

修改tableview表头的显示方式为拉伸,并且创建代理

void MainWindow::setupView()
{
    //设置模型
    ui->tableView->setModel(model);
    //设置选中模式为单个选中
    ui->tableView->setSelectionMode(QAbstractItemView::SingleSelection);
    //设置整行选中
    ui->tableView->setSelectionBehavior(QAbstractItemView::SelectRows);
    //设置编辑触发模式为双击或者选中时点击
    ui->tableView->setEditTriggers(QAbstractItemView::DoubleClicked
                                   | QAbstractItemView::SelectedClicked);
    //设置表头宽度拉伸
    ui->tableView->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch);
    //创建代理
    auto * delegate = new Mydelegate(this);
    //设置代理
    ui->tableView->setItemDelegate(delegate);
    //重置列宽自适应内容
    ui->tableView->resizeColumnsToContents();
}

再次运行能看到效果要求的效果

QSqlQueryModel

QSqlQueryModel 是只读模型,适用于执行自定义查询并将结果显示在视图中。它不支持对数据进行直接编辑。

1. 包含头文件

#include <QSqlQueryModel>

2. 基本用法

QSqlQueryModel有两个方法,都是用来执行sql语句的,只是参数不同。

//执行查询,传入QSqlQuery做的查询
void setQuery(const QSqlQuery &query);
//执行查询,可指定查询语句和数据库
void setQuery(const QString &query, const QSqlDatabase &db = QSqlDatabase());

3. 实现案例

以下示例展示如何使用 QSqlQueryModel 来执行自定义查询并显示结果。

我们需要显示如下的视图界面,右侧的为按薪水排序的只读视图。我们可以利用 QSqlQueryModel按照薪水排序存储,并且放入tableview中展示。

我们在之前的案例ui中再增加一个视图,用来显示最高薪水,并且增加按钮,用来刷新最高薪水

属性结构图

在mainwindow的类中添加QSqueryModel*类型的成员变量, 以及响应刷新的槽函数

private slots:
    void on_refreshBtn_clicked();
private:
    //查询模型,用来做显示工资排序
    QSqlQueryModel* query_model;

然后在setupModel中添加query_model的初始化,并且编写sql语句将薪水按从大到小排序列出来

void MainWindow::setupModel()
{
    //创建模型
    model = new QSqlTableModel(this);
    //使用默认连接,对表employees操作
    model->setTable("employees");
    //设置提交策略为手动提交
    model->setEditStrategy(QSqlTableModel::OnManualSubmit);

    // 设置表头标签
    model->setHeaderData(0, Qt::Horizontal, "ID");
    model->setHeaderData(1, Qt::Horizontal, "姓名");
    model->setHeaderData(2, Qt::Horizontal, "职位");
    model->setHeaderData(3, Qt::Horizontal, "薪水");

    //将sql中的数据写入model中
    if (!model->select()) {
        QMessageBox::critical(this, "错误", "无法检索数据: " + model->lastError().text());
    }

    //创建查询模型
    query_model = new QSqlQueryModel(this);

    //编写查询语句
    QString query_str = "SELECT * FROM employees ORDER BY salary DESC";
    //创建查询
    query_model->setQuery(query_str);

    //判断查询是否出错
    if (query_model->lastError().isValid()) {
        QMessageBox::critical(this, "错误", "无法检索数据: " +
                              query_model->lastError().text());
    }

    //设置表头标签
    query_model->setHeaderData(0, Qt::Horizontal, "ID");
    query_model->setHeaderData(1, Qt::Horizontal, "姓名");
    query_model->setHeaderData(2, Qt::Horizontal, "职位");
    query_model->setHeaderData(3, Qt::Horizontal, "薪水");

}

设置视图的逻辑把QSqlQueryModel和视图绑定, 为了美观,我们将表头模式设置为拉伸

void MainWindow::setupView()
{
    //设置模型
    ui->tableView->setModel(model);
    //设置选中模式为单个选中
    ui->tableView->setSelectionMode(QAbstractItemView::SingleSelection);
    //设置整行选中
    ui->tableView->setSelectionBehavior(QAbstractItemView::SelectRows);
    //设置编辑触发模式为双击或者选中时点击
    ui->tableView->setEditTriggers(QAbstractItemView::DoubleClicked
                                   | QAbstractItemView::SelectedClicked);
    //设置表头宽度拉伸
    ui->tableView->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch);

    // 设置model
    ui->highView->setModel(query_model);
    // 设置单选模式
    ui->highView->setSelectionMode(QAbstractItemView::SingleSelection);
    // 设置一次选择一行
    ui->highView->setSelectionBehavior(QAbstractItemView::SelectRows);
    // 设置不触发编辑
    ui->highView->setEditTriggers(QAbstractItemView::NoEditTriggers);
    // 设置表头自动拉伸
    ui->highView->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch);

}

实现刷新按钮的槽函数逻辑,其实就是再次查询一下数据库,将结果写入query_model,因为query_model已经和视图绑定了,就会导致视图自动刷新

void MainWindow::on_refreshBtn_clicked()
{
    //编写查询语句
    QString query_str = "SELECT * FROM employees ORDER BY salary DESC";
    //创建查询
    query_model->setQuery(query_str);

    //判断查询是否出错
    if (query_model->lastError().isValid()) {
        QMessageBox::critical(this, "错误", "无法检索数据: " +
                              query_model->lastError().text());
    }
}

再次运行,就会弹出案例界面,并且我们在左侧编辑数据后提交,点击刷新,右侧视图就会出现按照薪水排序的效果

4.实现代理(拓展内容)

同学们看到表格内容不是按照居中对齐的,对于这种情况我们可以重新实现代理

#ifndef MYDELEGATE_H
#define MYDELEGATE_H
#include <QStyledItemDelegate>
#include <QPainter>
#include <QSpinBox>

class Mydelegate : public QStyledItemDelegate
{
public:
    Mydelegate(QObject *parent = nullptr):QStyledItemDelegate(parent){}
    void paint(QPainter *painter,
               const QStyleOptionViewItem &option, const QModelIndex &index)
    const override{
        //拷贝一份样式的副本,防止对引用做出污染
        QStyleOptionViewItem opt = option;
        //绑定opt和index
        initStyleOption(&opt, index);

        // 根据列设置不同的对齐方式
        opt.displayAlignment = Qt::AlignCenter;

        // 调用基类绘制(确保选中状态等被正确处理)
        QStyledItemDelegate::paint(painter, opt, index);
    }

    //创建编辑器
    QWidget *createEditor(QWidget *parent,
                          const QStyleOptionViewItem &option,
                          const QModelIndex &index) const override{
        //判断如果是第三列,则创建spinbox
        if(index.column() == 3){
            //创建spinbox
            QSpinBox *editor = new QSpinBox(parent);
            //设置最低薪水
            editor->setMinimum(3000);
            //设置最高薪水
            editor->setMaximum(2000000);
            //设置spinbox步长
            editor->setSingleStep(1000);
            return editor;
        }
        //其他列调用基类默认函数创建
        return QStyledItemDelegate::createEditor(parent,option,index);
    }

    //将模型的数据放入编辑器中
    void setEditorData(QWidget *editor, const QModelIndex &index) const override{
        //如果是第三列
        if(index.column() == 3){
            //获取数据
            int value = index.model()->data(index, Qt::EditRole).toInt();
            QSpinBox *spinBox = static_cast<QSpinBox*>(editor);
            //将数据设置到编辑器中
            spinBox->setValue(value);
            return;
        }
        //其他列调用基类设置数据
        return QStyledItemDelegate::setEditorData(editor,index);
    }
    //将编辑其中的数据放入模型中
    void setModelData(QWidget *editor,
                      QAbstractItemModel *model,
                      const QModelIndex &index) const override{
        if(index.column() == 3){
            //editor强制转换为QSpinBox
            QSpinBox *spinBox = static_cast<QSpinBox*>(editor);
            //检测输入是否合法,做数值限制
            spinBox->interpretText();
            //获取spinBox的值
            int value = spinBox->value();
            //将这个值写入模型
            model->setData(index, value, Qt::EditRole);
            return;
        }

        //其他类型调用默认的设置函数
        QStyledItemDelegate::setModelData(editor,model,index);
    }
    //更新编辑区域
    void updateEditorGeometry(QWidget *editor,
                              const QStyleOptionViewItem &option,
                              const QModelIndex &index) const override{
        //设置编辑区域
        editor->setGeometry(option.rect);
    }
};

#endif // MYDELEGATE_H

新增setupDelegate方法,然后在setupDelegate中设置代理,我们为两个视图设置代理

//设置代理
void MainWindow::setupDelegate()
{
    //创建模型代理
   auto* model_delegate =  new Mydelegate(this);
   //设置代理
   ui->tableView->setItemDelegate(model_delegate);

   //创建查询模型代理
   auto* query_model_delegate = new Mydelegate(this);
   //设置代理
   ui->highView->setItemDelegate(query_model_delegate);
}

在mainwindow的构造函数添加调用

MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::MainWindow)
{
    ui->setupUi(this);
    //加载模型
    setupModel();
    //加载视图
    setupView();
    //设置代理
    setupDelegate();
}

QSqlQuery

QSqlQuery 提供了更底层的 SQL 操作能力,适用于执行自定义的 SQL 语句,如复杂的查询、事务处理以及动态参数绑定。

1. 基本用法

以下示例展示如何使用 QSqlQuery 来执行查询和插入操作。

在执行sql语句前,我们还是要创建数据库类型,并且设置要操作的数据库

bool executeQueries()
{
    // 利用文件对话框打开选择db文件
    auto file_path = QFileDialog::getOpenFileName(nullptr,"选择sqlitedb文件","","*.db");
    if(file_path.isEmpty()){
        QMessageBox::critical(nullptr,"文件错误信息","未选择文件!");
        return false;
    }

    // 添加 SQLite 数据库驱动,使用默认连接
    QSqlDatabase db = QSqlDatabase::addDatabase("QSQLITE");

    // 设置数据库文件路径(相对路径或绝对路径)
    db.setDatabaseName(file_path);

    // 打开数据库
    if (!db.open()) {
        qDebug() << "无法打开数据库:" << db.lastError().text();
        return false;
    }
    //...todo 查询逻辑
}

如果执行的sql语句不带参数,我们可以直接执行sql语句

bool executeQueries(){
    //...省略数据库连接
        QSqlQuery query;

    // 执行查询
    QString selectQuery = "SELECT id, name, position, salary FROM employees";
    if (!query.exec(selectQuery)) {
        qDebug() << "查询失败:" << query.lastError().text();
        return false;
    }

    //如果有多条数据,通过循环依次打印
    while (query.next()) {
        //获取每条查询结果的第一个字段,从0开始
        int id = query.value(0).toInt();
        //获取每条查询结果的第二个字段,从1开始
        QString name = query.value(1).toString();
        //获取每条查询结果的第三个字段,从2开始
        QString position = query.value(2).toString();
        //获取每条查询结果的第四个字段,从3开始
        double salary = query.value(3).toDouble();
        //输出查询结果
        qDebug() << id << name << position << salary;
    }

}

2. 参数绑定

使用参数绑定可以防止 SQL 注入,并使代码更清晰。

如果执行的sql语句需要携带参数,我们可以通过:占位名的方式填入查询语句,并且在查询前将实际参数绑定给这个占位符即可

bool executeQueries(){
    //...省略数据库连接和打开
    // 插入数据,通过:name, :position, :salary占位
    QString insertQuery = "INSERT INTO employees (name, position, salary) VALUES (:name, :position, :salary)";
    if (!query.prepare(insertQuery)) {
        qDebug() << "准备插入语句失败:" << query.lastError().text();
        return false;
    }
    //绑定参数,通过绑定:name,:position,:salary为具体参数
    query.bindValue(":name", "David");
    query.bindValue(":position", "Tester");
    query.bindValue(":salary", 48000.0);

    //执行sql语句
    if (!query.exec()) {
        qDebug() << "插入失败:" << query.lastError().text();
        return false;
    } else {
        qDebug() << "成功插入一条记录。";
    }

    return true;
}

如果写着麻烦,可以用?占位

bool executeQueries(){
    //...省略数据库连接和打开
    // 插入数据,通过?占位
    QString insertQuery = "INSERT INTO employees (name, position, salary) VALUES (?,?,?)";
    if (!query.prepare(insertQuery)) {
        qDebug() << "准备插入语句失败:" << query.lastError().text();
        return false;
    }
    //绑定参数,将具体参数name1,position1,salary1按顺序填入?位置
    query.addBindValue("name1");
    query.addBindValue("position1");
    query.addBindValue("salary1");

    //执行sql语句
    if (!query.exec()) {
        qDebug() << "插入失败:" << query.lastError().text();
        return false;
    } else {
        qDebug() << "成功插入一条记录。";
    }

    return true;
}

3. 事务处理(拓展内容)

在执行多个相关的 SQL 语句时,可以使用事务确保操作的原子性。

开启事物需要提前设置数据库,我们将数据库的打开操作提出到一个函数里,将数据库设置为全局变量

QSqlDatabase db;
bool openDB(){
    // 利用文件对话框打开选择db文件
    auto file_path = QFileDialog::getOpenFileName(nullptr,"选择sqlitedb文件","","*.db");
    if(file_path.isEmpty()){
        QMessageBox::critical(nullptr,"文件错误信息","未选择文件!");
        return false;
    }

    // 添加 SQLite 数据库驱动,使用默认连接
     db = QSqlDatabase::addDatabase("QSQLITE");

    // 设置数据库文件路径(相对路径或绝对路径)
    db.setDatabaseName(file_path);

    // 打开数据库
    if (!db.open()) {
        qDebug() << "无法打开数据库:" << db.lastError().text();
        return false;
    }
    return true;
}

创建事务,事务中有多条操作,最后一起提交,如果某一条失败了则回滚

//执行事物
bool performTransaction()
{
    //创建事务
    if (!db.transaction()) {
        qDebug() << "事务开始失败:" << db.lastError().text();
        return false;
    }

    QSqlQuery query;
    //注意sqlite查询语句中有字符串的用单引号包裹
    if (!query.exec("UPDATE employees SET salary = salary + 5000 WHERE position = 'Developer'")) {
        qDebug() << "更新失败:" << query.lastError().text();
        //如果失败则回滚
        db.rollback();
        return false;
    }

    //如果不会拼接可以采用之前替换的方式
    auto query_str = "UPDATE employees SET salary = salary + 3000 WHERE position = ?";
    //准备执行
    if(!query.prepare(query_str)){
        qDebug() << "准备更新失败:" << query.lastError().text();
        //回滚
        db.rollback();
        return false;
    }

    //绑定数值
    query.addBindValue("Designer");

    //执行语句

    if (!query.exec()) {
        qDebug() << "更新失败:" << query.lastError().text();
        db.rollback();
        return false;
    }

    //执行完成后要提交结果,将多次执行的结果一次提交
    if (!db.commit()) {
        qDebug() << "事务提交失败:" << db.lastError().text();
        db.rollback();
        return false;
    }

    qDebug() << "事务成功提交。";
    return true;
}

4 获取自动生成的键(扩展内容)

通过query.lastInsertId().toInt(),我们可以获得插入返回的id

//获取插入后返回的id
bool insertAndRetrieveId(const QString &name, const QString &position, double salary, int &id)
{
    QSqlQuery query;
    query.prepare("INSERT INTO employees (name, position, salary) VALUES (:name, :position, :salary)");
    query.bindValue(":name", name);
    query.bindValue(":position", position);
    query.bindValue(":salary", salary);

    if (!query.exec()) {
        qDebug() << "插入失败:" << query.lastError().text();
        return false;
    }

    // 获取最后插入的 ID
    id = query.lastInsertId().toInt();
    return true;
}

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐