零SQL实战用QSqlTableModel构建高效学生管理系统在桌面应用开发中数据库操作一直是让开发者又爱又恨的部分。传统方式需要编写大量SQL语句不仅容易出错还让代码变得冗长难维护。Qt框架提供的QSqlTableModel彻底改变了这一局面——它让数据库操作变得像操作内存中的数据结构一样简单直观。本文将带你从零开始用Qt5.15/6和QSqlTableModel构建一个功能完整的学生信息管理系统全程无需编写任何SQL语句。1. 环境准备与项目初始化首先确保你的开发环境已经安装了Qt5.15或Qt6版本。这两个版本对QSqlTableModel的支持都非常完善API也保持高度一致。新建一个Qt Widgets Application项目基类选择QMainWindow命名为StudentManager。在项目配置文件(StudentManager.pro)中添加SQL模块支持QT core gui sql widgets创建数据库连接是第一个关键步骤。我们使用SQLite作为后端数据库它无需额外配置非常适合快速开发和原型验证。新建一个connection.h头文件包含以下内容#ifndef CONNECTION_H #define CONNECTION_H #include QSqlDatabase #include QSqlError #include QMessageBox static bool initDatabase() { QSqlDatabase db QSqlDatabase::addDatabase(QSQLITE); db.setDatabaseName(students.db); if (!db.open()) { QMessageBox::critical(nullptr, 数据库错误, 无法打开数据库: db.lastError().text()); return false; } // 检查表是否存在不存在则创建 QStringList tables db.tables(); if (!tables.contains(student)) { QSqlQuery query; bool success query.exec( CREATE TABLE student ( id INTEGER PRIMARY KEY AUTOINCREMENT, name VARCHAR(40) NOT NULL, gender VARCHAR(10), age INTEGER, major VARCHAR(40)) ); if (!success) { QMessageBox::critical(nullptr, 建表失败, 创建student表失败: query.lastError().text()); return false; } } return true; } #endif // CONNECTION_H在main.cpp中调用初始化函数#include mainwindow.h #include QApplication #include connection.h int main(int argc, char *argv[]) { QApplication a(argc, argv); if (!initDatabase()) { return 1; } MainWindow w; w.show(); return a.exec(); }2. 核心模型配置与界面绑定QSqlTableModel的核心优势在于它将数据库表抽象为一个可编辑的内存模型。在MainWindow的构造函数中我们初始化模型并绑定到界面#include mainwindow.h #include ui_mainwindow.h #include QSqlTableModel #include QMessageBox MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) , ui(new Ui::MainWindow) , model(new QSqlTableModel(this)) { ui-setupUi(this); // 配置模型 model-setTable(student); model-setEditStrategy(QSqlTableModel::OnManualSubmit); model-select(); // 设置表头显示名称 model-setHeaderData(0, Qt::Horizontal, tr(学号)); model-setHeaderData(1, Qt::Horizontal, tr(姓名)); model-setHeaderData(2, Qt::Horizontal, tr(性别)); model-setHeaderData(3, Qt::Horizontal, tr(年龄)); model-setHeaderData(4, Qt::Horizontal, tr(专业)); // 绑定模型到视图 ui-tableView-setModel(model); ui-tableView-setSelectionMode(QAbstractItemView::SingleSelection); ui-tableView-setSelectionBehavior(QAbstractItemView::SelectRows); ui-tableView-resizeColumnsToContents(); // 连接信号槽 connect(ui-btnSubmit, QPushButton::clicked, this, MainWindow::submitChanges); connect(ui-btnRevert, QPushButton::clicked, this, MainWindow::revertChanges); // 其他连接... }模型编辑策略(setEditStrategy)有三种选择策略类型常量值行为描述手动提交OnManualSubmit所有修改必须显式调用submitAll()才会生效行级提交OnRowChange当用户选择另一行时自动提交当前行修改字段提交OnFieldChange字段值改变时立即提交对于学生管理系统推荐使用OnManualSubmit策略这样可以实现批量修改和原子性提交。3. 增删改查功能实现3.1 添加新记录添加学生记录时我们利用模型的insertRow和setData方法void MainWindow::onAddStudent() { // 在末尾插入新行 int newRow model-rowCount(); model-insertRow(newRow); // 设置默认值 QModelIndex index model-index(newRow, 0); model-setData(index, generateStudentId()); // 自动生成学号 // 定位到新行便于编辑 ui-tableView-setCurrentIndex(index); ui-tableView-edit(index); } int MainWindow::generateStudentId() { // 简单实现取当前最大ID1 int maxId 0; for (int i 0; i model-rowCount(); i) { int id model-data(model-index(i, 0)).toInt(); maxId qMax(maxId, id); } return maxId 1; }3.2 删除记录删除操作需要处理用户确认和事务void MainWindow::onRemoveStudent() { QModelIndexList selected ui-tableView-selectionModel()-selectedRows(); if (selected.isEmpty()) { QMessageBox::warning(this, tr(警告), tr(请先选择要删除的学生)); return; } // 开启事务 model-database().transaction(); // 删除所有选中行注意从后往前删避免索引变化 for (int i selected.count() - 1; i 0; --i) { model-removeRow(selected.at(i).row()); } // 确认对话框 QMessageBox::StandardButton reply QMessageBox::question( this, tr(确认删除), tr(确定要删除选中的%1条记录吗).arg(selected.count()), QMessageBox::Yes | QMessageBox::No ); if (reply QMessageBox::Yes) { if (model-submitAll()) { model-database().commit(); statusBar()-showMessage(tr(成功删除%1条记录).arg(selected.count()), 3000); } else { model-database().rollback(); QMessageBox::critical(this, tr(错误), tr(删除失败: %1).arg(model-lastError().text())); } } else { model-database().rollback(); model-revertAll(); } }3.3 查询与筛选QSqlTableModel提供了强大的筛选能力无需编写SQL WHERE子句void MainWindow::onSearch() { QString name ui-editName-text().trimmed(); QString major ui-comboMajor-currentText(); int minAge ui-spinMinAge-value(); int maxAge ui-spinMaxAge-value(); // 构建筛选条件 QStringList filters; if (!name.isEmpty()) { filters QString(name LIKE %%1%).arg(name); } if (major ! 全部) { filters QString(major %1).arg(major); } if (minAge 0) { filters QString(age %1).arg(minAge); } if (maxAge 150) { filters QString(age %1).arg(maxAge); } // 应用筛选 model-setFilter(filters.join( AND )); if (!model-select()) { QMessageBox::warning(this, tr(查询错误), tr(筛选失败: %1).arg(model-lastError().text())); } ui-labelResult-setText(tr(找到%1条记录).arg(model-rowCount())); }3.4 排序功能通过setSort方法实现多列排序void MainWindow::onSort(int column) { static Qt::SortOrder order Qt::AscendingOrder; // 如果是同一列点击切换排序方向 if (model-sortColumn() column) { order (order Qt::AscendingOrder) ? Qt::DescendingOrder : Qt::AscendingOrder; } else { order Qt::AscendingOrder; } model-setSort(column, order); model-select(); // 更新表头排序指示器 ui-tableView-horizontalHeader()-setSortIndicator(column, order); ui-tableView-horizontalHeader()-setSortIndicatorShown(true); }4. 高级功能与性能优化4.1 批量导入导出利用QSqlTableModel可以轻松实现数据交换// 导出到CSV void MainWindow::exportToCsv(const QString filename) { QFile file(filename); if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) { QMessageBox::critical(this, tr(错误), tr(无法创建文件)); return; } QTextStream out(file); // 输出表头 for (int col 0; col model-columnCount(); col) { if (col 0) out ,; out \ model-headerData(col, Qt::Horizontal).toString() \; } out \n; // 输出数据 for (int row 0; row model-rowCount(); row) { for (int col 0; col model-columnCount(); col) { if (col 0) out ,; out \ model-data(model-index(row, col)).toString() \; } out \n; } file.close(); statusBar()-showMessage(tr(成功导出%1条记录到%2).arg(model-rowCount()).arg(filename), 3000); } // 从CSV导入 void MainWindow::importFromCsv(const QString filename) { QFile file(filename); if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { QMessageBox::critical(this, tr(错误), tr(无法打开文件)); return; } // 开启事务 model-database().transaction(); QTextStream in(file); bool firstLine true; int imported 0; while (!in.atEnd()) { QString line in.readLine(); if (line.isEmpty()) continue; if (firstLine) { firstLine false; continue; // 跳过表头 } QStringList fields parseCsvLine(line); if (fields.size() ! model-columnCount()) { QMessageBox::warning(this, tr(警告), tr(第%1行列数不匹配).arg(imported 1)); continue; } // 添加新记录 int row model-rowCount(); model-insertRow(row); for (int col 0; col fields.size(); col) { model-setData(model-index(row, col), fields.at(col)); } imported; } file.close(); if (model-submitAll()) { model-database().commit(); statusBar()-showMessage(tr(成功导入%1条记录).arg(imported), 3000); } else { model-database().rollback(); QMessageBox::critical(this, tr(错误), tr(导入失败: %1).arg(model-lastError().text())); } }4.2 视图定制与数据验证通过委托(Delegate)可以增强表格视图的表现力和数据安全性// 在MainWindow构造函数中添加 ui-tableView-setItemDelegate(new StudentDelegate(this)); // StudentDelegate类实现数据验证和特殊渲染 class StudentDelegate : public QStyledItemDelegate { public: StudentDelegate(QObject *parent nullptr) : QStyledItemDelegate(parent) {} QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem option, const QModelIndex index) const override { if (index.column() 2) { // 性别列 QComboBox *editor new QComboBox(parent); editor-addItems({男, 女, 其他}); return editor; } else if (index.column() 3) { // 年龄列 QSpinBox *editor new QSpinBox(parent); editor-setRange(15, 50); return editor; } return QStyledItemDelegate::createEditor(parent, option, index); } void setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex index) const override { if (index.column() 1) { // 姓名验证 QLineEdit *nameEditor qobject_castQLineEdit*(editor); if (nameEditor nameEditor-text().isEmpty()) { QMessageBox::warning(editor, tr(错误), tr(姓名不能为空)); return; } } QStyledItemDelegate::setModelData(editor, model, index); } };4.3 性能优化技巧当处理大量数据时可以采用以下优化措施批量操作模式// 开始批量操作 model-setEditStrategy(QSqlTableModel::OnManualSubmit); model-database().transaction(); // 执行大量插入/更新... for (int i 0; i 1000; i) { int row model-rowCount(); model-insertRow(row); model-setData(model-index(row, 0), i1); // 设置其他字段... } // 提交批量操作 if (model-submitAll()) { model-database().commit(); } else { model-database().rollback(); }按需加载数据// 设置每页数据量 const int PAGE_SIZE 50; void MainWindow::loadPage(int page) { QString filter QString(LIMIT %1 OFFSET %2) .arg(PAGE_SIZE) .arg((page - 1) * PAGE_SIZE); model-setFilter(filter); model-select(); }缓存常用数据// 专业列表缓存 QStringList MainWindow::getMajorList() { static QStringList majors; if (majors.isEmpty()) { QSqlQuery query(SELECT DISTINCT major FROM student ORDER BY major); while (query.next()) { majors query.value(0).toString(); } } return majors; }
告别SQL语句!用Qt的QSqlTableModel在Qt5.15/6上快速搞定学生信息管理系统(附完整源码)
发布时间:2026/6/5 4:40:43
零SQL实战用QSqlTableModel构建高效学生管理系统在桌面应用开发中数据库操作一直是让开发者又爱又恨的部分。传统方式需要编写大量SQL语句不仅容易出错还让代码变得冗长难维护。Qt框架提供的QSqlTableModel彻底改变了这一局面——它让数据库操作变得像操作内存中的数据结构一样简单直观。本文将带你从零开始用Qt5.15/6和QSqlTableModel构建一个功能完整的学生信息管理系统全程无需编写任何SQL语句。1. 环境准备与项目初始化首先确保你的开发环境已经安装了Qt5.15或Qt6版本。这两个版本对QSqlTableModel的支持都非常完善API也保持高度一致。新建一个Qt Widgets Application项目基类选择QMainWindow命名为StudentManager。在项目配置文件(StudentManager.pro)中添加SQL模块支持QT core gui sql widgets创建数据库连接是第一个关键步骤。我们使用SQLite作为后端数据库它无需额外配置非常适合快速开发和原型验证。新建一个connection.h头文件包含以下内容#ifndef CONNECTION_H #define CONNECTION_H #include QSqlDatabase #include QSqlError #include QMessageBox static bool initDatabase() { QSqlDatabase db QSqlDatabase::addDatabase(QSQLITE); db.setDatabaseName(students.db); if (!db.open()) { QMessageBox::critical(nullptr, 数据库错误, 无法打开数据库: db.lastError().text()); return false; } // 检查表是否存在不存在则创建 QStringList tables db.tables(); if (!tables.contains(student)) { QSqlQuery query; bool success query.exec( CREATE TABLE student ( id INTEGER PRIMARY KEY AUTOINCREMENT, name VARCHAR(40) NOT NULL, gender VARCHAR(10), age INTEGER, major VARCHAR(40)) ); if (!success) { QMessageBox::critical(nullptr, 建表失败, 创建student表失败: query.lastError().text()); return false; } } return true; } #endif // CONNECTION_H在main.cpp中调用初始化函数#include mainwindow.h #include QApplication #include connection.h int main(int argc, char *argv[]) { QApplication a(argc, argv); if (!initDatabase()) { return 1; } MainWindow w; w.show(); return a.exec(); }2. 核心模型配置与界面绑定QSqlTableModel的核心优势在于它将数据库表抽象为一个可编辑的内存模型。在MainWindow的构造函数中我们初始化模型并绑定到界面#include mainwindow.h #include ui_mainwindow.h #include QSqlTableModel #include QMessageBox MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) , ui(new Ui::MainWindow) , model(new QSqlTableModel(this)) { ui-setupUi(this); // 配置模型 model-setTable(student); model-setEditStrategy(QSqlTableModel::OnManualSubmit); model-select(); // 设置表头显示名称 model-setHeaderData(0, Qt::Horizontal, tr(学号)); model-setHeaderData(1, Qt::Horizontal, tr(姓名)); model-setHeaderData(2, Qt::Horizontal, tr(性别)); model-setHeaderData(3, Qt::Horizontal, tr(年龄)); model-setHeaderData(4, Qt::Horizontal, tr(专业)); // 绑定模型到视图 ui-tableView-setModel(model); ui-tableView-setSelectionMode(QAbstractItemView::SingleSelection); ui-tableView-setSelectionBehavior(QAbstractItemView::SelectRows); ui-tableView-resizeColumnsToContents(); // 连接信号槽 connect(ui-btnSubmit, QPushButton::clicked, this, MainWindow::submitChanges); connect(ui-btnRevert, QPushButton::clicked, this, MainWindow::revertChanges); // 其他连接... }模型编辑策略(setEditStrategy)有三种选择策略类型常量值行为描述手动提交OnManualSubmit所有修改必须显式调用submitAll()才会生效行级提交OnRowChange当用户选择另一行时自动提交当前行修改字段提交OnFieldChange字段值改变时立即提交对于学生管理系统推荐使用OnManualSubmit策略这样可以实现批量修改和原子性提交。3. 增删改查功能实现3.1 添加新记录添加学生记录时我们利用模型的insertRow和setData方法void MainWindow::onAddStudent() { // 在末尾插入新行 int newRow model-rowCount(); model-insertRow(newRow); // 设置默认值 QModelIndex index model-index(newRow, 0); model-setData(index, generateStudentId()); // 自动生成学号 // 定位到新行便于编辑 ui-tableView-setCurrentIndex(index); ui-tableView-edit(index); } int MainWindow::generateStudentId() { // 简单实现取当前最大ID1 int maxId 0; for (int i 0; i model-rowCount(); i) { int id model-data(model-index(i, 0)).toInt(); maxId qMax(maxId, id); } return maxId 1; }3.2 删除记录删除操作需要处理用户确认和事务void MainWindow::onRemoveStudent() { QModelIndexList selected ui-tableView-selectionModel()-selectedRows(); if (selected.isEmpty()) { QMessageBox::warning(this, tr(警告), tr(请先选择要删除的学生)); return; } // 开启事务 model-database().transaction(); // 删除所有选中行注意从后往前删避免索引变化 for (int i selected.count() - 1; i 0; --i) { model-removeRow(selected.at(i).row()); } // 确认对话框 QMessageBox::StandardButton reply QMessageBox::question( this, tr(确认删除), tr(确定要删除选中的%1条记录吗).arg(selected.count()), QMessageBox::Yes | QMessageBox::No ); if (reply QMessageBox::Yes) { if (model-submitAll()) { model-database().commit(); statusBar()-showMessage(tr(成功删除%1条记录).arg(selected.count()), 3000); } else { model-database().rollback(); QMessageBox::critical(this, tr(错误), tr(删除失败: %1).arg(model-lastError().text())); } } else { model-database().rollback(); model-revertAll(); } }3.3 查询与筛选QSqlTableModel提供了强大的筛选能力无需编写SQL WHERE子句void MainWindow::onSearch() { QString name ui-editName-text().trimmed(); QString major ui-comboMajor-currentText(); int minAge ui-spinMinAge-value(); int maxAge ui-spinMaxAge-value(); // 构建筛选条件 QStringList filters; if (!name.isEmpty()) { filters QString(name LIKE %%1%).arg(name); } if (major ! 全部) { filters QString(major %1).arg(major); } if (minAge 0) { filters QString(age %1).arg(minAge); } if (maxAge 150) { filters QString(age %1).arg(maxAge); } // 应用筛选 model-setFilter(filters.join( AND )); if (!model-select()) { QMessageBox::warning(this, tr(查询错误), tr(筛选失败: %1).arg(model-lastError().text())); } ui-labelResult-setText(tr(找到%1条记录).arg(model-rowCount())); }3.4 排序功能通过setSort方法实现多列排序void MainWindow::onSort(int column) { static Qt::SortOrder order Qt::AscendingOrder; // 如果是同一列点击切换排序方向 if (model-sortColumn() column) { order (order Qt::AscendingOrder) ? Qt::DescendingOrder : Qt::AscendingOrder; } else { order Qt::AscendingOrder; } model-setSort(column, order); model-select(); // 更新表头排序指示器 ui-tableView-horizontalHeader()-setSortIndicator(column, order); ui-tableView-horizontalHeader()-setSortIndicatorShown(true); }4. 高级功能与性能优化4.1 批量导入导出利用QSqlTableModel可以轻松实现数据交换// 导出到CSV void MainWindow::exportToCsv(const QString filename) { QFile file(filename); if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) { QMessageBox::critical(this, tr(错误), tr(无法创建文件)); return; } QTextStream out(file); // 输出表头 for (int col 0; col model-columnCount(); col) { if (col 0) out ,; out \ model-headerData(col, Qt::Horizontal).toString() \; } out \n; // 输出数据 for (int row 0; row model-rowCount(); row) { for (int col 0; col model-columnCount(); col) { if (col 0) out ,; out \ model-data(model-index(row, col)).toString() \; } out \n; } file.close(); statusBar()-showMessage(tr(成功导出%1条记录到%2).arg(model-rowCount()).arg(filename), 3000); } // 从CSV导入 void MainWindow::importFromCsv(const QString filename) { QFile file(filename); if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { QMessageBox::critical(this, tr(错误), tr(无法打开文件)); return; } // 开启事务 model-database().transaction(); QTextStream in(file); bool firstLine true; int imported 0; while (!in.atEnd()) { QString line in.readLine(); if (line.isEmpty()) continue; if (firstLine) { firstLine false; continue; // 跳过表头 } QStringList fields parseCsvLine(line); if (fields.size() ! model-columnCount()) { QMessageBox::warning(this, tr(警告), tr(第%1行列数不匹配).arg(imported 1)); continue; } // 添加新记录 int row model-rowCount(); model-insertRow(row); for (int col 0; col fields.size(); col) { model-setData(model-index(row, col), fields.at(col)); } imported; } file.close(); if (model-submitAll()) { model-database().commit(); statusBar()-showMessage(tr(成功导入%1条记录).arg(imported), 3000); } else { model-database().rollback(); QMessageBox::critical(this, tr(错误), tr(导入失败: %1).arg(model-lastError().text())); } }4.2 视图定制与数据验证通过委托(Delegate)可以增强表格视图的表现力和数据安全性// 在MainWindow构造函数中添加 ui-tableView-setItemDelegate(new StudentDelegate(this)); // StudentDelegate类实现数据验证和特殊渲染 class StudentDelegate : public QStyledItemDelegate { public: StudentDelegate(QObject *parent nullptr) : QStyledItemDelegate(parent) {} QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem option, const QModelIndex index) const override { if (index.column() 2) { // 性别列 QComboBox *editor new QComboBox(parent); editor-addItems({男, 女, 其他}); return editor; } else if (index.column() 3) { // 年龄列 QSpinBox *editor new QSpinBox(parent); editor-setRange(15, 50); return editor; } return QStyledItemDelegate::createEditor(parent, option, index); } void setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex index) const override { if (index.column() 1) { // 姓名验证 QLineEdit *nameEditor qobject_castQLineEdit*(editor); if (nameEditor nameEditor-text().isEmpty()) { QMessageBox::warning(editor, tr(错误), tr(姓名不能为空)); return; } } QStyledItemDelegate::setModelData(editor, model, index); } };4.3 性能优化技巧当处理大量数据时可以采用以下优化措施批量操作模式// 开始批量操作 model-setEditStrategy(QSqlTableModel::OnManualSubmit); model-database().transaction(); // 执行大量插入/更新... for (int i 0; i 1000; i) { int row model-rowCount(); model-insertRow(row); model-setData(model-index(row, 0), i1); // 设置其他字段... } // 提交批量操作 if (model-submitAll()) { model-database().commit(); } else { model-database().rollback(); }按需加载数据// 设置每页数据量 const int PAGE_SIZE 50; void MainWindow::loadPage(int page) { QString filter QString(LIMIT %1 OFFSET %2) .arg(PAGE_SIZE) .arg((page - 1) * PAGE_SIZE); model-setFilter(filter); model-select(); }缓存常用数据// 专业列表缓存 QStringList MainWindow::getMajorList() { static QStringList majors; if (majors.isEmpty()) { QSqlQuery query(SELECT DISTINCT major FROM student ORDER BY major); while (query.next()) { majors query.value(0).toString(); } } return majors; }