一、介绍
演示如何在模型/视图框架下使用Qt SQL类
Books示例展示了如何将Qt的SQL类与模型/视图框架一起使用,为存储在数据库中的信息创建富用户界面。
关于一套藏书的信息保存在一个数据库中。这些书按作者、书名、体裁和出版年份分类。尽管这些字段都可以使用标准小部件显示和编辑,但是描述图书任意评级的附加字段还需要一些额外的东西。
书籍的评级系统会给每本书分配一些星星;一本书拥有的越多,就越有价值。通过单击包含评级的单元格,可以修改星星的数量,并更新数据库中的评级。
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:”);
if (!db.open())return db.lastError();// 判断数据库是否存在 books authors表QStringList tables = db.tables();if (tables.contains("books", Qt::CaseInsensitive) && tables.contains("authors", Qt::CaseInsensitive))return QSqlError();// 创建表QSqlQuery q;if (!q.exec(QLatin1String("create table books("" id integer primary key, "" title varchar, "" author integer, "" genre integer, "" year integer, "" rating integer"")")))return q.lastError(); // 书if (!q.exec(QLatin1String("create table authors("" id integer primary key, "" name varchar, "" birthdate date"")")))return q.lastError(); // 作者if (!q.exec(QLatin1String("create table genres("" id integer primary key, "" name varchar"")")))return q.lastError(); // 类型// 插入作者信息if (!q.prepare(QLatin1String("insert into authors(name, birthdate) values(?, ?)")))return q.lastError();QVariant asimovId = addAuthor(q, QLatin1String("Isaac Asimov"), QDate(1920, 2, 1));QVariant greeneId = addAuthor(q, QLatin1String("Graham Greene"), QDate(1904, 10, 2));QVariant pratchettId = addAuthor(q, QLatin1String("Terry Pratchett"), QDate(1948, 4, 28));// 插入体裁信息if (!q.prepare(QLatin1String("insert into genres(name) values(?)")))return q.lastError();QVariant sfiction = addGenre(q, QLatin1String("Science Fiction"));QVariant fiction = addGenre(q, QLatin1String("Fiction"));QVariant fantasy = addGenre(q, QLatin1String("Fantasy"));// 插入数据信息if (!q.prepare(QLatin1String("insert into books(title, year, author, genre, rating) values(?, ?, ?, ?, ?)")))return q.lastError();addBook(q, QLatin1String("Foundation"), 1951, asimovId, sfiction, 3);addBook(q, QLatin1String("Foundation and Empire"), 1952, asimovId, sfiction, 4);addBook(q, QLatin1String("Second Foundation"), 1953, asimovId, sfiction, 3);addBook(q, QLatin1String("Foundation's Edge"), 1982, asimovId, sfiction, 3);addBook(q, QLatin1String("Foundation and Earth"), 1986, asimovId, sfiction, 4);addBook(q, QLatin1String("Prelude to Foundation"), 1988, asimovId, sfiction, 3);addBook(q, QLatin1String("Forward the Foundation"), 1993, asimovId, sfiction, 3);addBook(q, QLatin1String("The Power and the Glory"), 1940, greeneId, fiction, 4);addBook(q, QLatin1String("The Third Man"), 1950, greeneId, fiction, 5);addBook(q, QLatin1String("Our Man in Havana"), 1958, greeneId, fiction, 4);addBook(q, QLatin1String("Guards! Guards!"), 1989, pratchettId, fantasy, 3);addBook(q, QLatin1String("Night Watch"), 2002, pratchettId, fantasy, 3);addBook(q, QLatin1String("Going Postal"), 2004, pratchettId, fantasy, 3);return QSqlError();
}
endif
<a name="ca1jM"></a>### bookdelegate.h- `paint`和`sizeHint`这两个是继承`QItemDelegate`必须重写的函数- editorEvent:- 开始编辑项目时,会使用触发编辑的事件、模型、项目索引以及用于渲染项目的选项调用此函数。- 即使鼠标事件没有开始编辑项目,鼠标事件也会发送到 `editorEvent()`。- 例如,如果您希望在项目上按下鼠标右键时打开上下文菜单,这会很有用。- 基本实现返回 false(表明它尚未处理该事件)。- [https://doc.qt.io/qt-5/qabstractitemdelegate.html#editorEvent](https://doc.qt.io/qt-5/qabstractitemdelegate.html#editorEvent)- createEditor:- 返回用于编辑具有给定索引的数据项的编辑器。请注意,索引包含有关正在使用的模型的信息。编辑器的父小部件由parent指定,item选项由option指定。- 基本实现返回 nullptr。如果您想要自定义编辑,则需要重新实现此功能。- 返回的编辑器小部件应该有 `**Qt::StrongFocus**`;否则,小部件接收到的 QMouseEvents 将传播到视图。除非编辑器绘制自己的背景(例如,使用 `setAutoFillBackground()`),否则视图的背景会发光。```cpp#ifndef BOOKDELEGATE_H#define BOOKDELEGATE_H#include <QModelIndex>#include <QPixmap>#include <QSize>#include <QSqlRelationalDelegate>QT_FORWARD_DECLARE_CLASS(QPainter)class BookDelegate : public QSqlRelationalDelegate{public:BookDelegate(QObject *parent);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;QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem &option,const QModelIndex &index) const override;private:QPixmap star;};
bookdelegate.cpp
#include "bookdelegate.h"#include <QtWidgets>BookDelegate::BookDelegate(QObject* parent): QSqlRelationalDelegate(parent), star(QPixmap(":images/star.png")){}void BookDelegate::paint(QPainter* painter, const QStyleOptionViewItem& option,const QModelIndex& index) const{if (index.column() != 5) {QStyleOptionViewItem opt = option;// Since we draw the grid ourselves:opt.rect.adjust(0, 0, -1, -1);QSqlRelationalDelegate::paint(painter, opt, 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();int y = option.rect.y() + (option.rect.height() / 2) - (height / 2);for (int i = 0; i < rating; ++i) {painter->drawPixmap(x, y, star);x += width;}// Since we draw the grid ourselves:drawFocus(painter, option, option.rect.adjusted(0, 0, -1, -1));}QPen pen = 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(pen);}// 这个是设置第五列星星图标的大小QSize BookDelegate::sizeHint(const QStyleOptionViewItem& option,const QModelIndex& index) const{if (index.column() == 5)return QSize(5 * star.width(), star.height()) + QSize(1, 1);// Since we draw the grid ourselves:return QSqlRelationalDelegate::sizeHint(option, index) + QSize(1, 1);}// 处理鼠标按下事件,增加一个星星,然后返回false;其他事件就过滤掉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);int stars = qBound(0, int(0.7 + qreal(mouseEvent->pos().x() - option.rect.x()) / star.width()), 5);model->setData(index, QVariant(stars));// So that the selection can change:return false;}return true;}//创建editor widget控件的,该控件用于编辑model中的数据QWidget* BookDelegate::createEditor(QWidget* parent,const QStyleOptionViewItem& option, const QModelIndex& index) const{if (index.column() != 4)return QSqlRelationalDelegate::createEditor(parent, option, index);// 对于编辑年份,返回一个范围从-1000到2100的QSpinBoxQSpinBox* sb = new QSpinBox(parent);sb->setFrame(false);sb->setMaximum(2100);sb->setMinimum(-1000);return sb;}
book.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;
void createMenuBar();
};
endif
<a name="z9Fxa"></a>### book.cpp```cpp#include "bookwindow.h"#include "bookdelegate.h"#include "initdb.h"#include <QString>#include <QtSql>BookWindow::BookWindow(){ui.setupUi(this);if (!QSqlDatabase::drivers().contains("QSQLITE"))QMessageBox::critical(this, "无法加载数据库", "这个演示需要SQLITE驱动程序");// 初始化数据库:QSqlError err = initDb();if (err.type() != QSqlError::NoError) {showError(err);return;}// 创建数据模型:model = new QSqlRelationalTableModel(ui.bookTable);model->setEditStrategy(QSqlTableModel::OnManualSubmit);model->setTable("books");// 记录列的索引:authorIdx = model->fieldIndex("author");genreIdx = model->fieldIndex("genre");// 设置与其他数据库表的关系: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"));// 填充模型:if (!model->select()) {showError(model->lastError());return;}// 设置模型并隐藏ID列:ui.bookTable->setModel(model);ui.bookTable->setItemDelegate(new BookDelegate(ui.bookTable));ui.bookTable->setColumnHidden(model->fieldIndex("id"), true);ui.bookTable->setSelectionMode(QAbstractItemView::SingleSelection);// 初始化Author组合框: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);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));createMenuBar();}// 展示错误信息void BookWindow::showError(const QSqlError& err){QMessageBox::critical(this, QStringLiteral("无法初始化数据库"), "误差初始化数据库:" + err.text());}// 创建菜单栏void BookWindow::createMenuBar(){QAction* quitAction = new QAction(tr("&Quit"), 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, this, &BookWindow::close);connect(aboutAction, &QAction::triggered, this, &BookWindow::about);connect(aboutQtAction, &QAction::triggered, qApp, &QApplication::aboutQt);}void BookWindow::about(){QMessageBox::about(this, QStringLiteral("关于Books"),QStringLiteral("<b>Books</b>示例展示了如何在模型/视图框架下使用Qt SQL类"));}
