第二章:bookdelegate.cppbookdelegate.py#

现在,您的数据库已搭建完成,将 BookDelegate 类的 C++ 代码移植过来。此类的代理用于在 QTableView 中展示和编辑数据。它继承自 QSqlRelationalDelegate 接口,该接口特别提供了处理关系数据库特性的功能,如外键字段的组合框编辑器。首先,创建 bookdelegate.py 并向其中添加以下导入语句

1
2import copy
3import os
4from pathlib import Path
5
6from PySide6.QtSql import QSqlRelationalDelegate
7from PySide6.QtWidgets import (QItemDelegate, QSpinBox, QStyledItemDelegate,
8    QStyle, QStyleOptionViewItem)

在必要的导入语句之后,移植 BookDelegate 类的构造函数代码。该 C++ 和 Python 版本的代码都初始化了一个 QSqlRelationalDelegate 和一个 QPixmap 实例。下面是它们的形状:

C++ 版本#

1        QStyleOptionViewItem opt = option;
2        // Since we draw the grid ourselves:
3        opt.rect.adjust(0, 0, -1, -1);
4        QSqlRelationalDelegate::paint(painter, opt, index);
5    } else {
6        const QAbstractItemModel *model = index.model();

Python 版本#

1    QStyle, QStyleOptionViewItem)
2from PySide6.QtGui import QMouseEvent, QPixmap, QPalette, QImage
3from PySide6.QtCore import QEvent, QSize, Qt, QUrl
4
5
6class BookDelegate(QSqlRelationalDelegate):
7    """Books delegate to rate the books"""
8

注意

Python 版本通过在本地文件系统中包含 star.png 的绝对路径加载 QPixmap

由于 QSqlRelationalDelegate 默认功能不足以展示书籍数据,您需要重新实现一些函数。例如,在表格中为每本书绘制星号以表示评分。下面是修改后的代码的形状:

C++版本(bookdelegate)#

 1        const QAbstractItemModel *model = index.model();
 2        QPalette::ColorGroup cg = (option.state & QStyle::State_Enabled) ?
 3            (option.state & QStyle::State_Active) ?
 4                        QPalette::Normal :
 5                        QPalette::Inactive :
 6                        QPalette::Disabled;
 7
 8        if (option.state & QStyle::State_Selected)
 9            painter->fillRect(
10                        option.rect,
11                        option.palette.color(cg, QPalette::Highlight));
12
13        int rating = model->data(index, Qt::DisplayRole).toInt();
14        int width = star.width();
15        int height = star.height();
16        int x = option.rect.x();
17        int y = option.rect.y() + (option.rect.height() / 2) - (height / 2);
18        for (int i = 0; i < rating; ++i) {
19            painter->drawPixmap(x, y, star);
20            x += width;
21        }
22        // Since we draw the grid ourselves:
23        drawFocus(painter, option, option.rect.adjusted(0, 0, -1, -1));
24    }
25
26    QPen pen = painter->pen();
27    painter->setPen(option.palette.color(QPalette::Mid));
28    painter->drawLine(option.rect.bottomLeft(), option.rect.bottomRight());
29    painter->drawLine(option.rect.topRight(), option.rect.bottomRight());
30    painter->setPen(pen);
31}
32
33QSize BookDelegate::sizeHint(const QStyleOptionViewItem &option,
34                                 const QModelIndex &index) const
35{
36    if (index.column() == 5)
37        return QSize(5 * star.width(), star.height()) + QSize(1, 1);
38    // Since we draw the grid ourselves:
39    return QSqlRelationalDelegate::sizeHint(option, index) + QSize(1, 1);
40}
41
42bool BookDelegate::editorEvent(QEvent *event, QAbstractItemModel *model,
43                               const QStyleOptionViewItem &option,
44                               const QModelIndex &index)
45{
46    if (index.column() != 5)
47        return QSqlRelationalDelegate::editorEvent(event, model, option, index);
48
49    if (event->type() == QEvent::MouseButtonPress) {
50        QMouseEvent *mouseEvent = static_cast<QMouseEvent*>(event);
51        int stars = qBound(0, int(0.7 + qreal(mouseEvent->pos().x()
52            - option.rect.x()) / star.width()), 5);
53        model->setData(index, QVariant(stars));
54        // So that the selection can change:
55        return false;
56    }
57
58    return true;
59}
60
61QWidget *BookDelegate::createEditor(QWidget *parent,
62                                    const QStyleOptionViewItem &option,
63                                    const QModelIndex &index) const
64{
65    if (index.column() != 4)
66        return QSqlRelationalDelegate::createEditor(parent, option, index);
67
68    // For editing the year, return a spinbox with a range from -1000 to 2100.
69    QSpinBox *sb = new QSpinBox(parent);
70    sb->setFrame(false);
71    sb->setMaximum(2100);
72    sb->setMinimum(-1000);
73
74    return sb;
75}

Python版本(bookdelegate)#

 1    def __init__(self, parent=None):
 2        QSqlRelationalDelegate.__init__(self, parent)
 3        star_png = Path(__file__).parent / "images" / "star.png"
 4        self.star = QPixmap(star_png)
 5
 6    def paint(self, painter, option, index):
 7        """ Paint the items in the table.
 8
 9            If the item referred to by <index> is a StarRating, we
10            handle the painting ourselves. For the other items, we
11            let the base class handle the painting as usual.
12
13            In a polished application, we'd use a better check than
14            the column number to find out if we needed to paint the
15            stars, but it works for the purposes of this example.
16        """
17        if index.column() != 5:
18            # Since we draw the grid ourselves:
19            opt = copy.copy(option)
20            opt.rect = option.rect.adjusted(0, 0, -1, -1)
21            QSqlRelationalDelegate.paint(self, painter, opt, index)
22        else:
23            model = index.model()
24            if option.state & QStyle.State_Enabled:
25                if option.state & QStyle.State_Active:
26                    color_group = QPalette.Normal
27                else:
28                    color_group = QPalette.Inactive
29            else:
30                color_group = QPalette.Disabled
31
32            if option.state & QStyle.State_Selected:
33                painter.fillRect(option.rect,
34                    option.palette.color(color_group, QPalette.Highlight))
35            rating = model.data(index, Qt.DisplayRole)
36            width = self.star.width()
37            height = self.star.height()
38            x = option.rect.x()
39            y = option.rect.y() + (option.rect.height() / 2) - (height / 2)
40            for i in range(rating):
41                painter.drawPixmap(x, y, self.star)
42                x += width
43
44            # Since we draw the grid ourselves:
45            self.drawFocus(painter, option, option.rect.adjusted(0, 0, -1, -1))
46
47        pen = painter.pen()
48        painter.setPen(option.palette.color(QPalette.Mid))
49        painter.drawLine(option.rect.bottomLeft(), option.rect.bottomRight())
50        painter.drawLine(option.rect.topRight(), option.rect.bottomRight())
51        painter.setPen(pen)
52
53    def sizeHint(self, option, index):
54        """ Returns the size needed to display the item in a QSize object. """
55        if index.column() == 5:
56            size_hint = QSize(5 * self.star.width(), self.star.height()) + QSize(1, 1)
57            return size_hint
58        # Since we draw the grid ourselves:
59        return QSqlRelationalDelegate.sizeHint(self, option, index) + QSize(1, 1)
60
61    def editorEvent(self, event, model, option, index):
62        if index.column() != 5:
63            return False
64
65        if event.type() == QEvent.MouseButtonPress:
66            mouse_pos = event.pos()
67            new_stars = int(0.7 + (mouse_pos.x() - option.rect.x()) / self.star.width())
68            stars = max(0, min(new_stars, 5))
69            model.setData(index, stars)
70            # So that the selection can change
71            return False
72
73        return True
74
75    def createEditor(self, parent, option, index):
76        if index.column() != 4:
77            return QSqlRelationalDelegate.createEditor(self, parent, option, index)
78
79        # For editing the year, return a spinbox with a range from -1000 to 2100.
80        spinbox = QSpinBox(parent)
81        spinbox.setFrame(False)
82        spinbox.setMaximum(2100)
83        spinbox.setMinimum(-1000)
84        return spinbox

现在虚拟代理已经设置好了,运行以下 main.py 看一下数据展示的情况

 1
 2import sys
 3
 4from PySide6.QtCore import Qt
 5from PySide6.QtSql import QSqlQueryModel
 6from PySide6.QtWidgets import QTableView, QApplication
 7
 8import createdb
 9from bookdelegate import BookDelegate
10
11if __name__ == "__main__":
12    app = QApplication()
13    createdb.init_db()
14
15    model = QSqlQueryModel()
16    model.setQuery("select title, author, genre, year, rating from books")
17
18    table = QTableView()
19    table.setModel(model)
20    table.setItemDelegate(BookDelegate())
21    table.resize(800, 600)
22    table.show()
23
24    sys.exit(app.exec())

运行应用程序时的外观如下

Books table data

第1章相比,您现在唯一能注意到的不同是评级(rating)列的外观不同。

尝试通过添加以下功能进一步改进表格

  • 每列标题

  • 作者ID(author_id)和类别ID(genre_id)列的SQL关系

  • 设置窗口标题

拥有这些功能后,您的表格将如下所示

Books table with SQL relation