一、介绍

演示如何在模型/视图框架下使用Qt SQL类

Books示例展示了如何将Qt的SQL类与模型/视图框架一起使用,为存储在数据库中的信息创建富用户界面。

关于一套藏书的信息保存在一个数据库中。这些书按作者、书名、体裁和出版年份分类。尽管这些字段都可以使用标准小部件显示和编辑,但是描述图书任意评级的附加字段还需要一些额外的东西。

书籍的评级系统会给每本书分配一些星星;一本书拥有的越多,就越有价值。通过单击包含评级的单元格,可以修改星星的数量,并更新数据库中的评级。

initDB.h

  • 该类主要是创建数据库并插入数据
  • 其他内容在注释里 ```cpp

    ifndef INITDB_H

    define INITDB_H

include

include

// 添加book的插入语句 void addBook(QSqlQuery& q, const QString& title, int year, const QVariant& authorId, const QVariant& genreId, int rating) { q.addBindValue(title); q.addBindValue(year); q.addBindValue(authorId); q.addBindValue(genreId); q.addBindValue(rating); q.exec(); }

QVariant addGenre(QSqlQuery& q, const QString& name) { q.addBindValue(name); q.exec(); qDebug() << “sql1: “ << q.executedQuery() << “, name” << name << “, lastId” << q.lastInsertId(); return q.lastInsertId(); }

QVariant addAuthor(QSqlQuery& q, const QString& name, const QDate& birthdate) { q.addBindValue(name); q.addBindValue(birthdate); q.exec(); qDebug() << “sql2: “ << q.executedQuery() << “, name” << name << “, lastId” << q.lastInsertId(); return q.lastInsertId(); }

// 初始化数据库 QSqlError initDb() { // 设置数据库 QSqlDatabase db = QSqlDatabase::addDatabase(“QSQLITE”); db.setDatabaseName(“:memory:”);

  1. if (!db.open())
  2. return db.lastError();
  3. // 判断数据库是否存在 books authors表
  4. QStringList tables = db.tables();
  5. if (tables.contains("books", Qt::CaseInsensitive) && tables.contains("authors", Qt::CaseInsensitive))
  6. return QSqlError();
  7. // 创建表
  8. QSqlQuery q;
  9. if (!q.exec(QLatin1String("create table books("
  10. " id integer primary key, "
  11. " title varchar, "
  12. " author integer, "
  13. " genre integer, "
  14. " year integer, "
  15. " rating integer"
  16. ")")))
  17. return q.lastError(); // 书
  18. if (!q.exec(QLatin1String("create table authors("
  19. " id integer primary key, "
  20. " name varchar, "
  21. " birthdate date"
  22. ")")))
  23. return q.lastError(); // 作者
  24. if (!q.exec(QLatin1String("create table genres("
  25. " id integer primary key, "
  26. " name varchar"
  27. ")")))
  28. return q.lastError(); // 类型
  29. // 插入作者信息
  30. if (!q.prepare(QLatin1String("insert into authors(name, birthdate) values(?, ?)")))
  31. return q.lastError();
  32. QVariant asimovId = addAuthor(q, QLatin1String("Isaac Asimov"), QDate(1920, 2, 1));
  33. QVariant greeneId = addAuthor(q, QLatin1String("Graham Greene"), QDate(1904, 10, 2));
  34. QVariant pratchettId = addAuthor(q, QLatin1String("Terry Pratchett"), QDate(1948, 4, 28));
  35. // 插入体裁信息
  36. if (!q.prepare(QLatin1String("insert into genres(name) values(?)")))
  37. return q.lastError();
  38. QVariant sfiction = addGenre(q, QLatin1String("Science Fiction"));
  39. QVariant fiction = addGenre(q, QLatin1String("Fiction"));
  40. QVariant fantasy = addGenre(q, QLatin1String("Fantasy"));
  41. // 插入数据信息
  42. if (!q.prepare(QLatin1String("insert into books(title, year, author, genre, rating) values(?, ?, ?, ?, ?)")))
  43. return q.lastError();
  44. addBook(q, QLatin1String("Foundation"), 1951, asimovId, sfiction, 3);
  45. addBook(q, QLatin1String("Foundation and Empire"), 1952, asimovId, sfiction, 4);
  46. addBook(q, QLatin1String("Second Foundation"), 1953, asimovId, sfiction, 3);
  47. addBook(q, QLatin1String("Foundation's Edge"), 1982, asimovId, sfiction, 3);
  48. addBook(q, QLatin1String("Foundation and Earth"), 1986, asimovId, sfiction, 4);
  49. addBook(q, QLatin1String("Prelude to Foundation"), 1988, asimovId, sfiction, 3);
  50. addBook(q, QLatin1String("Forward the Foundation"), 1993, asimovId, sfiction, 3);
  51. addBook(q, QLatin1String("The Power and the Glory"), 1940, greeneId, fiction, 4);
  52. addBook(q, QLatin1String("The Third Man"), 1950, greeneId, fiction, 5);
  53. addBook(q, QLatin1String("Our Man in Havana"), 1958, greeneId, fiction, 4);
  54. addBook(q, QLatin1String("Guards! Guards!"), 1989, pratchettId, fantasy, 3);
  55. addBook(q, QLatin1String("Night Watch"), 2002, pratchettId, fantasy, 3);
  56. addBook(q, QLatin1String("Going Postal"), 2004, pratchettId, fantasy, 3);
  57. return QSqlError();

}

endif

  1. <a name="ca1jM"></a>
  2. ### bookdelegate.h
  3. - `paint`和`sizeHint`这两个是继承`QItemDelegate`必须重写的函数
  4. - editorEvent:
  5. - 开始编辑项目时,会使用触发编辑的事件、模型、项目索引以及用于渲染项目的选项调用此函数。
  6. - 即使鼠标事件没有开始编辑项目,鼠标事件也会发送到 `editorEvent()`。
  7. - 例如,如果您希望在项目上按下鼠标右键时打开上下文菜单,这会很有用。
  8. - 基本实现返回 false(表明它尚未处理该事件)。
  9. - [https://doc.qt.io/qt-5/qabstractitemdelegate.html#editorEvent](https://doc.qt.io/qt-5/qabstractitemdelegate.html#editorEvent)
  10. - createEditor:
  11. - 返回用于编辑具有给定索引的数据项的编辑器。请注意,索引包含有关正在使用的模型的信息。编辑器的父小部件由parent指定,item选项由option指定。
  12. - 基本实现返回 nullptr。如果您想要自定义编辑,则需要重新实现此功能。
  13. - 返回的编辑器小部件应该有 `**Qt::StrongFocus**`;否则,小部件接收到的 QMouseEvents 将传播到视图。除非编辑器绘制自己的背景(例如,使用 `setAutoFillBackground()`),否则视图的背景会发光。
  14. ```cpp
  15. #ifndef BOOKDELEGATE_H
  16. #define BOOKDELEGATE_H
  17. #include <QModelIndex>
  18. #include <QPixmap>
  19. #include <QSize>
  20. #include <QSqlRelationalDelegate>
  21. QT_FORWARD_DECLARE_CLASS(QPainter)
  22. class BookDelegate : public QSqlRelationalDelegate
  23. {
  24. public:
  25. BookDelegate(QObject *parent);
  26. void paint(QPainter *painter, const QStyleOptionViewItem &option,
  27. const QModelIndex &index) const override;
  28. QSize sizeHint(const QStyleOptionViewItem &option,
  29. const QModelIndex &index) const override;
  30. bool editorEvent(QEvent *event, QAbstractItemModel *model,
  31. const QStyleOptionViewItem &option,
  32. const QModelIndex &index) override;
  33. QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem &option,
  34. const QModelIndex &index) const override;
  35. private:
  36. QPixmap star;
  37. };

bookdelegate.cpp

  1. #include "bookdelegate.h"
  2. #include <QtWidgets>
  3. BookDelegate::BookDelegate(QObject* parent)
  4. : QSqlRelationalDelegate(parent)
  5. , star(QPixmap(":images/star.png"))
  6. {
  7. }
  8. void BookDelegate::paint(QPainter* painter, const QStyleOptionViewItem& option,
  9. const QModelIndex& index) const
  10. {
  11. if (index.column() != 5) {
  12. QStyleOptionViewItem opt = option;
  13. // Since we draw the grid ourselves:
  14. opt.rect.adjust(0, 0, -1, -1);
  15. QSqlRelationalDelegate::paint(painter, opt, index);
  16. } else {
  17. const QAbstractItemModel* model = index.model();
  18. QPalette::ColorGroup cg = (option.state & QStyle::State_Enabled) ?
  19. (option.state & QStyle::State_Active) ?
  20. QPalette::Normal : QPalette::Inactive : QPalette::Disabled;
  21. if (option.state & QStyle::State_Selected)
  22. painter->fillRect(
  23. option.rect,
  24. option.palette.color(cg, QPalette::Highlight));
  25. int rating = model->data(index, Qt::DisplayRole).toInt();
  26. int width = star.width();
  27. int height = star.height();
  28. int x = option.rect.x();
  29. int y = option.rect.y() + (option.rect.height() / 2) - (height / 2);
  30. for (int i = 0; i < rating; ++i) {
  31. painter->drawPixmap(x, y, star);
  32. x += width;
  33. }
  34. // Since we draw the grid ourselves:
  35. drawFocus(painter, option, option.rect.adjusted(0, 0, -1, -1));
  36. }
  37. QPen pen = painter->pen();
  38. painter->setPen(option.palette.color(QPalette::Mid));
  39. painter->drawLine(option.rect.bottomLeft(), option.rect.bottomRight());
  40. painter->drawLine(option.rect.topRight(), option.rect.bottomRight());
  41. painter->setPen(pen);
  42. }
  43. // 这个是设置第五列星星图标的大小
  44. QSize BookDelegate::sizeHint(const QStyleOptionViewItem& option,
  45. const QModelIndex& index) const
  46. {
  47. if (index.column() == 5)
  48. return QSize(5 * star.width(), star.height()) + QSize(1, 1);
  49. // Since we draw the grid ourselves:
  50. return QSqlRelationalDelegate::sizeHint(option, index) + QSize(1, 1);
  51. }
  52. // 处理鼠标按下事件,增加一个星星,然后返回false;其他事件就过滤掉
  53. bool BookDelegate::editorEvent(QEvent* event, QAbstractItemModel* model,
  54. const QStyleOptionViewItem& option,
  55. const QModelIndex& index)
  56. {
  57. if (index.column() != 5)
  58. return QSqlRelationalDelegate::editorEvent(event, model, option, index);
  59. if (event->type() == QEvent::MouseButtonPress) {
  60. QMouseEvent* mouseEvent = static_cast<QMouseEvent*>(event);
  61. int stars = qBound(0, int(0.7 + qreal(mouseEvent->pos().x() - option.rect.x()) / star.width()), 5);
  62. model->setData(index, QVariant(stars));
  63. // So that the selection can change:
  64. return false;
  65. }
  66. return true;
  67. }
  68. //创建editor widget控件的,该控件用于编辑model中的数据
  69. QWidget* BookDelegate::createEditor(QWidget* parent,
  70. const QStyleOptionViewItem& option, const QModelIndex& index) const
  71. {
  72. if (index.column() != 4)
  73. return QSqlRelationalDelegate::createEditor(parent, option, index);
  74. // 对于编辑年份,返回一个范围从-1000到2100的QSpinBox
  75. QSpinBox* sb = new QSpinBox(parent);
  76. sb->setFrame(false);
  77. sb->setMaximum(2100);
  78. sb->setMinimum(-1000);
  79. return sb;
  80. }

book.h

  • 这一部分就是与界面相关的了
  • 数据库初始化、数据查询设置代理等 ```cpp

    ifndef BOOKWINDOW_H

    define BOOKWINDOW_H

include

include

include “ui_bookwindow.h”

class BookWindow: public QMainWindow { Q_OBJECT public: BookWindow();

private slots: void about();

private: void showError(const QSqlError &err); Ui::BookWindow ui; QSqlRelationalTableModel *model; int authorIdx, genreIdx;

  1. void createMenuBar();

};

endif

  1. <a name="z9Fxa"></a>
  2. ### book.cpp
  3. ```cpp
  4. #include "bookwindow.h"
  5. #include "bookdelegate.h"
  6. #include "initdb.h"
  7. #include <QString>
  8. #include <QtSql>
  9. BookWindow::BookWindow()
  10. {
  11. ui.setupUi(this);
  12. if (!QSqlDatabase::drivers().contains("QSQLITE"))
  13. QMessageBox::critical(this, "无法加载数据库", "这个演示需要SQLITE驱动程序");
  14. // 初始化数据库:
  15. QSqlError err = initDb();
  16. if (err.type() != QSqlError::NoError) {
  17. showError(err);
  18. return;
  19. }
  20. // 创建数据模型:
  21. model = new QSqlRelationalTableModel(ui.bookTable);
  22. model->setEditStrategy(QSqlTableModel::OnManualSubmit);
  23. model->setTable("books");
  24. // 记录列的索引:
  25. authorIdx = model->fieldIndex("author");
  26. genreIdx = model->fieldIndex("genre");
  27. // 设置与其他数据库表的关系:
  28. model->setRelation(authorIdx, QSqlRelation("authors", "id", "name"));
  29. model->setRelation(genreIdx, QSqlRelation("genres", "id", "name"));
  30. // 设置本地化的标题头:
  31. model->setHeaderData(authorIdx, Qt::Horizontal, tr("Author Name"));
  32. model->setHeaderData(genreIdx, Qt::Horizontal, tr("Genre"));
  33. model->setHeaderData(model->fieldIndex("title"), Qt::Horizontal, tr("Title"));
  34. model->setHeaderData(model->fieldIndex("year"), Qt::Horizontal, tr("Year"));
  35. model->setHeaderData(model->fieldIndex("rating"), Qt::Horizontal, tr("Rating"));
  36. // 填充模型:
  37. if (!model->select()) {
  38. showError(model->lastError());
  39. return;
  40. }
  41. // 设置模型并隐藏ID列:
  42. ui.bookTable->setModel(model);
  43. ui.bookTable->setItemDelegate(new BookDelegate(ui.bookTable));
  44. ui.bookTable->setColumnHidden(model->fieldIndex("id"), true);
  45. ui.bookTable->setSelectionMode(QAbstractItemView::SingleSelection);
  46. // 初始化Author组合框:
  47. ui.authorEdit->setModel(model->relationModel(authorIdx));
  48. ui.authorEdit->setModelColumn(
  49. model->relationModel(authorIdx)->fieldIndex("name"));
  50. ui.genreEdit->setModel(model->relationModel(genreIdx));
  51. ui.genreEdit->setModelColumn(
  52. model->relationModel(genreIdx)->fieldIndex("name"));
  53. // 锁定并禁止调整评级列的宽度:
  54. ui.bookTable->horizontalHeader()->setSectionResizeMode(
  55. model->fieldIndex("rating"),
  56. QHeaderView::ResizeToContents);
  57. QDataWidgetMapper* mapper = new QDataWidgetMapper(this);
  58. mapper->setModel(model);
  59. mapper->setItemDelegate(new BookDelegate(this));
  60. mapper->addMapping(ui.titleEdit, model->fieldIndex("title"));
  61. mapper->addMapping(ui.yearEdit, model->fieldIndex("year"));
  62. mapper->addMapping(ui.authorEdit, authorIdx);
  63. mapper->addMapping(ui.genreEdit, genreIdx);
  64. mapper->addMapping(ui.ratingEdit, model->fieldIndex("rating"));
  65. connect(ui.bookTable->selectionModel(), &QItemSelectionModel::currentRowChanged,
  66. mapper, &QDataWidgetMapper::setCurrentModelIndex);
  67. ui.bookTable->setCurrentIndex(model->index(0, 0));
  68. createMenuBar();
  69. }
  70. // 展示错误信息
  71. void BookWindow::showError(const QSqlError& err)
  72. {
  73. QMessageBox::critical(this, QStringLiteral("无法初始化数据库"), "误差初始化数据库:" + err.text());
  74. }
  75. // 创建菜单栏
  76. void BookWindow::createMenuBar()
  77. {
  78. QAction* quitAction = new QAction(tr("&Quit"), this);
  79. QAction* aboutAction = new QAction(tr("&About"), this);
  80. QAction* aboutQtAction = new QAction(tr("&About Qt"), this);
  81. QMenu* fileMenu = menuBar()->addMenu(tr("&File"));
  82. fileMenu->addAction(quitAction);
  83. QMenu* helpMenu = menuBar()->addMenu(tr("&Help"));
  84. helpMenu->addAction(aboutAction);
  85. helpMenu->addAction(aboutQtAction);
  86. connect(quitAction, &QAction::triggered, this, &BookWindow::close);
  87. connect(aboutAction, &QAction::triggered, this, &BookWindow::about);
  88. connect(aboutQtAction, &QAction::triggered, qApp, &QApplication::aboutQt);
  89. }
  90. void BookWindow::about()
  91. {
  92. QMessageBox::about(this, QStringLiteral("关于Books"),
  93. QStringLiteral("<b>Books</b>示例展示了如何在模型/视图框架下使用Qt SQL类"));
  94. }