一、介绍
演示如何在模型/视图框架下使用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的QSpinBox
QSpinBox* 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类"));
}