“深入浅出”系列之QT:实战篇(3)书籍管理系统(SQLite)

本项目不仅涵盖数据库表设计(支持书籍、作者、分类的多表关联)、QSqlRelationalTableModel的高级用法(自动解析外键关系),还深度实践了自定义UI委托技术(如星级评分控件)和双向数据绑定(QDataWidgetMapper)。无论是学习Qt SQL模块的集成开发,还是探索企业级桌面应用的架构设计,本案例都将为您提供清晰的实现路径与工业级代码范例,助您掌握从数据存储到界面呈现的全栈开发能力。

一:【工程项目】运行效果

图片

二:【工程项目】源码分享

图片

1:工程项目UI设计与实现

图片

2:initdb.h文件

/* * 文件名:initdb.h * 描述:该头文件用于初始化SQLite数据库,创建表结构并插入示例数据 * 作者:vico */
#ifndef INITDB_H#define INITDB_H
#include <QtSql>  // 包含Qt SQL模块头文件
// 向books表添加书籍记录void addBook(QSqlQuery &q, const QString &title, int year, const QVariant &authorId,             const QVariant &genreId, int rating){    // 绑定插入参数(顺序需与INSERT语句中的占位符对应)    q.addBindValue(title);      // 书籍标题    q.addBindValue(year);       // 出版年份    q.addBindValue(authorId);   // 作者ID(外键)    q.addBindValue(genreId);    // 类型ID(外键)    q.addBindValue(rating);     // 评分(0-5)    q.exec();                   // 执行插入操作}
// 向genres表添加新类型,并返回新插入记录的IDQVariant addGenre(QSqlQuery &q, const QString &name){    q.addBindValue(name);       // 类型名称    q.exec();                       return q.lastInsertId();    // 返回自动生成的主键ID}
// 向authors表添加新作者,并返回新插入记录的IDQVariant addAuthor(QSqlQuery &q, const QString &name, QDate birthdate){    q.addBindValue(name);       // 作者姓名    q.addBindValue(birthdate);  // 作者出生日期    q.exec();    return q.lastInsertId();    // 返回自动生成的主键ID}
// 创建books表的SQL语句const auto BOOKS_SQL = QLatin1String(R"(    create table books(        id integer primary key,         title varchar,         author integer,    -- 作者ID(逻辑外键)        genre integer,     -- 类型ID(逻辑外键)        year integer,      -- 出版年份        rating integer     -- 用户评分(0-5)    ))");
// 创建authors表的SQL语句const auto AUTHORS_SQL =  QLatin1String(R"(    create table authors(        id integer primary key,         name varchar,       -- 作者姓名        birthdate date      -- 出生日期    ))");
// 创建genres表的SQL语句const auto GENRES_SQL = QLatin1String(R"(    create table genres(        id integer primary key,         name varchar        -- 类型名称    ))");
// 预编译插入authors表的SQL语句(使用占位符?)const auto INSERT_AUTHOR_SQL = QLatin1String(R"(    insert into authors(name, birthdate) values(?, ?)    )");
// 预编译插入books表的SQL语句const auto INSERT_BOOK_SQL = QLatin1String(R"(    insert into books(title, year, author, genre, rating)                      values(?, ?, ?, ?, ?)    )");
// 预编译插入genres表的SQL语句const auto INSERT_GENRE_SQL = QLatin1String(R"(    insert into genres(name) values(?)    )");
// 初始化数据库的主函数QSqlError initDb(){    // 创建并配置SQLite内存数据库(无需磁盘存储)    QSqlDatabase db = QSqlDatabase::addDatabase("QSQLITE");    db.setDatabaseName(":memory:");  // 使用内存数据库
    // 尝试打开数据库    if (!db.open())        return db.lastError();  // 返回打开错误
    // 检查表是否已存在(避免重复初始化)    QStringList tables = db.tables();    if (tables.contains("books", Qt::CaseInsensitive) &&        tables.contains("authors", Qt::CaseInsensitive))        return QSqlError();  // 表已存在,直接返回
    // 执行建表操作    QSqlQuery q;    if (!q.exec(BOOKS_SQL))    return q.lastError();  // 创建books表    if (!q.exec(AUTHORS_SQL))  return q.lastError();  // 创建authors表    if (!q.exec(GENRES_SQL))   return q.lastError();  // 创建genres表
    // 预编译并插入作者数据    if (!q.prepare(INSERT_AUTHOR_SQL)) return q.lastError();    QVariant asimovId = addAuthor(q, "Isaac Asimov", QDate(1920, 2, 1));    QVariant greeneId = addAuthor(q, "Graham Greene", QDate(1904, 10, 2));    QVariant pratchettId = addAuthor(q, "Terry Pratchett", QDate(1948, 4, 28));
    // 预编译并插入类型数据    if (!q.prepare(INSERT_GENRE_SQL)) return q.lastError();    QVariant sfiction = addGenre(q, "Science Fiction");    QVariant fiction = addGenre(q, "Fiction");    QVariant fantasy = addGenre(q, "Fantasy");
    // 预编译并插入书籍数据    if (!q.prepare(INSERT_BOOK_SQL)) return q.lastError();    // 添加Isaac Asimov的书籍    addBook(q, "Foundation", 1951, asimovId, sfiction, 3);    addBook(q, "Foundation and Empire", 1952, asimovId, sfiction, 4);    // ...(其他书籍添加省略)    // 添加Terry Pratchett的书籍    addBook(q, "Going Postal", 2004, pratchettId, fantasy, 3);
    return QSqlError();  // 返回无错误}
#endif // INITDB_H

3:bookwindow.h文件

/* * 文件名:BookWindow.h * 描述:定义主窗口类,用于展示和管理书籍数据库的GUI界面 * 作者:vico */
#ifndef BOOKWINDOW_H#define BOOKWINDOW_H
#include <QtWidgets>   // 包含Qt Widgets模块基础类(QMainWindow等)#include <QtSql>       // 包含Qt SQL数据库模块#include "ui_bookwindow.h" // 包含Qt Designer生成的UI类头文件
// 主窗口类,继承自QMainWindowclass BookWindow: public QMainWindow{    Q_OBJECT  // 启用Qt元对象系统(信号槽机制)public:    BookWindow();  // 构造函数
private slots:    // 槽函数 - 显示"关于"对话框    void about();
private:    // 显示数据库操作错误信息    void showError(const QSqlError &err);
    // 初始化菜单栏    void createMenuBar();
    // 成员变量    Ui::BookWindow ui;  // UI组件容器(包含所有设计器创建的控件)
    /* 关系型数据表模型指针     * 功能:     * - 连接数据库表与视图组件     * - 支持外键关系映射(如将author_id显示为作者姓名)     * - 提供数据编辑和过滤能力 */    QSqlRelationalTableModel *model = nullptr;  
    // 模型列索引缓存(避免重复查找)    int authorIdx = 0;  // 作者字段在模型中的列索引    int genreIdx = 0;   // 类型字段在模型中的列索引};
#endif

4:bookwindow.cpp文件

/* 文件名:bookwindow.cpp * 描述:实现书籍管理主窗口的业务逻辑,集成数据库操作与UI交互 * 作者:vico */
#include "bookwindow.h"#include "bookdelegate.h"    // 自定义表格委托,用于数据呈现和编辑#include "initdb.h"          // 数据库初始化函数
#include <QtSql>            // SQL数据库模块
/* 构造函数:初始化UI、数据库连接和模型视图 */BookWindow::BookWindow(){    ui.setupUi(this);  // 加载并布局UI设计文件中的组件
    // 检查SQLITE驱动是否可用    if (!QSqlDatabase::drivers().contains("QSQLITE"))        QMessageBox::critical(                    this,                    "Unable to load database",                    "This demo needs the SQLITE driver"                    );
    // 初始化数据库连接并创建表结构    QSqlError err = initDb();    if (err.type() != QSqlError::NoError) {        showError(err);  // 显示初始化错误        return;    }
    /* 创建关系型表格模型     * 参数:     * - ui.bookTable:父对象,用于内存管理     * - 功能:将数据库表映射为可编辑的表格模型 */    model = new QSqlRelationalTableModel(ui.bookTable);    model->setEditStrategy(QSqlTableModel::OnManualSubmit); // 手动提交修改    model->setTable("books");  // 关联数据库中的books表
    // 获取外键字段的列索引(优化后续操作)    authorIdx = model->fieldIndex("author"); // 作者字段索引    genreIdx = model->fieldIndex("genre");  // 类型字段索引
    /* 设置外键关系映射     * 参数说明:     * - authorIdx:当前模型的字段索引     * - QSqlRelation("authors", "id", "name"):关联authors表的id到name字段 */    model->setRelation(authorIdx, QSqlRelation("authors", "id", "name"));    model->setRelation(genreIdx, QSqlRelation("genres", "id", "name"));
    // 设置本地化表头(中文字段名)    model->setHeaderData(authorIdx, Qt::Horizontal, tr("Author Name"));    model->setHeaderData(genreIdx, Qt::Horizontal, tr("Genre"));    model->setHeaderData(model->fieldIndex("title"), Qt::Horizontal, tr("Title"));    model->setHeaderData(model->fieldIndex("year"), Qt::Horizontal, tr("Year"));    model->setHeaderData(model->fieldIndex("rating"), Qt::Horizontal, tr("Rating"));
    // 加载数据到模型(执行SELECT * FROM books)    if (!model->select()) {        showError(model->lastError());        return;    }
    /* 配置表格视图     * - 设置模型和自定义委托(处理数据显示和编辑)     * - 隐藏ID列(model->fieldIndex("id")获取列索引)     * - 设置单选模式 */    ui.bookTable->setModel(model);    ui.bookTable->setItemDelegate(new BookDelegate(ui.bookTable)); // 自定义委托    ui.bookTable->setColumnHidden(model->fieldIndex("id"), true);    ui.bookTable->setSelectionMode(QAbstractItemView::SingleSelection);
    /* 配置作者下拉框     * - 使用关系模型获取authors表数据     * - 显示name字段 */    ui.authorEdit->setModel(model->relationModel(authorIdx));    ui.authorEdit->setModelColumn(                model->relationModel(authorIdx)->fieldIndex("name"));
    // 同理配置类型下拉框    ui.genreEdit->setModel(model->relationModel(genreIdx));    ui.genreEdit->setModelColumn(                model->relationModel(genreIdx)->fieldIndex("name"));
    /* 设置评分列宽度策略     * - 固定宽度,根据内容自动调整     * - 防止用户手动调整列宽 */    ui.bookTable->horizontalHeader()->setSectionResizeMode(                model->fieldIndex("rating"),                QHeaderView::ResizeToContents);
    /* 数据映射器配置(UI控件与模型数据绑定)     * 功能:将表单控件与模型字段实时同步 */    QDataWidgetMapper *mapper = new QDataWidgetMapper(this);    mapper->setModel(model);    mapper->setItemDelegate(new BookDelegate(this)); // 复用委托    // 添加字段映射    mapper->addMapping(ui.titleEdit, model->fieldIndex("title"));    mapper->addMapping(ui.yearEdit, model->fieldIndex("year"));    mapper->addMapping(ui.authorEdit, authorIdx);    mapper->addMapping(ui.genreEdit, genreIdx);    mapper->addMapping(ui.ratingEdit, model->fieldIndex("rating"));
    /* 连接表格行选择信号     * 当用户选择不同行时,自动更新表单控件内容 */    connect(ui.bookTable->selectionModel(),            &QItemSelectionModel::currentRowChanged,            mapper,            &QDataWidgetMapper::setCurrentModelIndex            );
    // 初始化选中第一行    ui.bookTable->setCurrentIndex(model->index(0, 0));    ui.bookTable->selectRow(0);
    createMenuBar(); // 创建菜单栏}
/* 显示数据库错误弹窗 * 参数:QSqlError对象,包含错误详细信息 */void BookWindow::showError(const QSqlError &err){    QMessageBox::critical(this, "Database Error",                "Error: " + err.text()); // 显示错误描述}
/* 创建菜单栏及动作 */void BookWindow::createMenuBar(){    // 创建动作对象    QAction *quitAction = new QAction(tr("&Exit"), this);    QAction *aboutAction = new QAction(tr("&About"), this);    QAction *aboutQtAction = new QAction(tr("About &Qt"), this);
    // 构建文件菜单    QMenu *fileMenu = menuBar()->addMenu(tr("&File"));    fileMenu->addAction(quitAction); // 添加退出动作
    // 构建帮助菜单    QMenu *helpMenu = menuBar()->addMenu(tr("&Help"));    helpMenu->addAction(aboutAction);    helpMenu->addAction(aboutQtAction);
    // 连接信号与槽    connect(quitAction, &QAction::triggered, qApp, &QCoreApplication::quit); // 退出应用    connect(aboutAction, &QAction::triggered, this, &BookWindow::about);    // 关于本程序    connect(aboutQtAction, &QAction::triggered, qApp, &QApplication::aboutQt); // 关于Qt}
/* 显示关于对话框 */void BookWindow::about(){    QMessageBox::about(this, tr("About Book Manager"),            tr("<p>This <b>Book Manager</b> demonstrates how to use "               "Qt SQL with model/view architecture.</p>"));}

5:bookdelegate.h文件

/* * 文件名:bookdelegate.h * 描述:自定义委托类,用于实现星级评分显示和编辑功能 * 作者:vico */
#ifndef BOOKDELEGATE_H#define BOOKDELEGATE_H
#include <QModelIndex>#include <QPixmap>#include <QSize>#include <QSqlRelationalDelegate>  // 继承自关系型数据库委托基类
QT_FORWARD_DECLARE_CLASS(QPainter) // 前向声明QPainter,减少头文件依赖
// 继承QSqlRelationalDelegate以支持关系型字段处理class BookDelegate : public QSqlRelationalDelegate{public:    // 构造函数    explicit BookDelegate(QObject *parent = nullptr);
    // 重写绘制方法(实现星级评分显示)    void paint(QPainter *painter,                const QStyleOptionViewItem &option,               const QModelIndex &index) const override;
    // 返回单元格建议尺寸(根据星图大小计算)    QSize sizeHint(const QStyleOptionViewItem &option,                  const QModelIndex &index) const override;
    // 处理编辑事件(鼠标点击修改评分)    bool editorEvent(QEvent *event,                      QAbstractItemModel *model,                     const QStyleOptionViewItem &option,                     const QModelIndex &index) override;
    // 创建编辑器控件(默认使用QSpinBox编辑评分)    QWidget *createEditor(QWidget *parent,                          const QStyleOptionViewItem &option,                          const QModelIndex &index) const override;
private:    QPixmap star; // 星形图标位图(用于绘制评分)};
#endif // BOOKDELEGATE_H

6:bookdelegate.cpp文件

/* 文件名:bookdelegate.cpp * 描述:实现书籍表格的自定义委托,主要处理评分星标显示和年份输入控制 * 作者:vico */
#include "bookdelegate.h"#include <QtWidgets>  // 包含Qt Widgets模块头文件
/* 构造函数 * 参数:parent - 父对象指针(通常为QTableView) * 功能:初始化星标图片资源 */BookDelegate::BookDelegate(QObject *parent)    : QSqlRelationalDelegate(parent),    // 调用基类构造函数      star(QPixmap(":images/star.png"))  // 从资源文件加载星标图片{    // 注:":images/star.png"为Qt资源系统路径,需在.qrc文件中定义}
/* 自定义单元格绘制方法 * 参数: *   painter - 绘制工具 *   option  - 样式选项(包含位置、状态等信息) *   index   - 数据模型索引 * 功能:对评分列(第5列)进行星标绘制,其他列使用默认绘制 */void BookDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option,                          const QModelIndex &index) const{    // 非评分列(第5列)使用基类默认绘制    if (index.column() != 5) {        QSqlRelationalDelegate::paint(painter, option, index);    } else {        // 获取数据模型和颜色组状态        const QAbstractItemModel *model = index.model();        QPalette::ColorGroup cg = option.state & QStyle::State_Enabled ?            (option.state & QStyle::State_Active ? QPalette::Normal : QPalette::Inactive)            : QPalette::Disabled;
        // 绘制选中状态背景        if (option.state & QStyle::State_Selected) {            painter->fillRect(option.rect, option.palette.color(cg, QPalette::Highlight));        }
        // 获取评分值并绘制星标        int rating = model->data(index, Qt::DisplayRole).toInt();        int width = star.width();    // 单颗星宽度        int height = star.height();  // 单颗星高度        int x = option.rect.x();     // 起始X坐标        int y = option.rect.y() + (option.rect.height() / 2) - (height / 2); // 垂直居中
        // 绘制实心星标(根据评分值循环)        for (int i = 0; i < rating; ++i) {            painter->drawPixmap(x, y, star); // 在(x,y)位置绘制星标            x += width; // 水平移动绘制位置        }    }
    // 绘制单元格底部和右侧边框线(覆盖默认边框)    QPen originalPen = painter->pen();    painter->setPen(option.palette.color(QPalette::Mid));    painter->drawLine(option.rect.bottomLeft(), option.rect.bottomRight()); // 底部线    painter->drawLine(option.rect.topRight(), option.rect.bottomRight());   // 右侧线    painter->setPen(originalPen); // 恢复原始画笔}
/* 单元格建议尺寸计算 * 返回值:QSize对象表示建议尺寸 * 功能:评分列根据星标尺寸计算,其他列使用基类计算 */QSize BookDelegate::sizeHint(const QStyleOptionViewItem &option,                             const QModelIndex &index) const{    if (index.column() == 5) {        // 5颗星的宽度 + 1像素边框,高度+1像素边框        return QSize(5 * star.width(), star.height()) + QSize(1, 1);    }    // 其他列使用基类计算并增加1像素边框补偿    return QSqlRelationalDelegate::sizeHint(option, index) + QSize(1, 1);}
/* 编辑事件处理 * 返回值:事件是否已处理 * 功能:实现点击评分列修改星标数量 */bool BookDelegate::editorEvent(QEvent *event, QAbstractItemModel *model,                               const QStyleOptionViewItem &option,                               const QModelIndex &index){    // 非评分列使用基类处理    if (index.column() != 5) {        return QSqlRelationalDelegate::editorEvent(event, model, option, index);    }
    // 处理鼠标左键点击事件    if (event->type() == QEvent::MouseButtonPress) {        QMouseEvent *mouseEvent = static_cast<QMouseEvent*>(event);        // 计算点击位置对应的星标数量        qreal clickPosX = mouseEvent->position().toPoint().x() - option.rect.x();        int stars = qBound(0, static_cast<int>(0.7 + clickPosX / star.width()), 5);
        // 更新模型数据(触发视图刷新)        model->setData(index, QVariant(stars));
        // 返回false允许选择状态变更        return false;    }
    return true; // 其他事件已处理}
/* 创建单元格编辑器 * 返回值:QWidget指针指向创建的编辑器 * 功能:为年份列(第4列)创建带范围的SpinBox */QWidget *BookDelegate::createEditor(QWidget *parent,                                    const QStyleOptionViewItem &option,                                    const QModelIndex &index) const{    // 非年份列使用基类编辑器(组合框等)    if (index.column() != 4) {        return QSqlRelationalDelegate::createEditor(parent, option, index);    }
    // 创建年份输入SpinBox(范围-1000到2100)    QSpinBox *yearSpinBox = new QSpinBox(parent);    yearSpinBox->setFrame(false);     // 无边框    yearSpinBox->setMinimum(-1000);  // 最小年份    yearSpinBox->setMaximum(2100);    // 最大年份
    return yearSpinBox; // 返回编辑器控件}

7:main.cpp文件

/* 文件名:main.cpp(通常) * 描述:Qt应用程序的主入口文件,创建并显示主窗口 * 作者:vico */
#include "bookwindow.h"  // 包含自定义的主窗口类头文件#include <QtWidgets>     // 包含Qt Widgets模块所有头文件(实际开发建议按需包含)
/* 主函数 - 应用程序入口点 * 参数: *   argc : 命令行参数个数 *   argv : 命令行参数数组指针 * 返回值: *   int  : 应用程序退出码 */int main(int argc, char *argv[]){    // 创建QApplication实例,管理GUI应用程序控制流和主设置    QApplication app(argc, argv); 
    /* 创建主窗口对象     * 说明:     * - 在栈上分配,生命周期随作用域结束自动销毁     * - 构造函数内会初始化数据库连接和UI组件 */    BookWindow win; 
    // 显示主窗口(默认隐藏,需显式调用show())    win.show(); 
    /* 进入Qt事件主循环     * 功能:     * - 监听和处理窗口系统事件(鼠标、键盘、重绘等)     * - 阻塞直到所有窗口关闭或调用quit()     * 返回值:     * - 传递应用程序退出状态码(通常0表示正常退出) */    return app.exec(); }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值