第3章:将bookdwindow.cpp移植到bookwindow.py

在bookdelegate之后,移植BookWindow类的C++代码。它提供了一个QMainWindow,包含一个QTableView来展示书籍数据,以及一个详细信息部分,其中包含一组输入字段,用于编辑表格中选定的行。首先,创建bookwindow.py并向其中添加以下导入:

 1from __future__ import annotations
 2
 3from PySide6.QtGui import QAction
 4from PySide6.QtWidgets import (QAbstractItemView, QDataWidgetMapper,
 5    QHeaderView, QMainWindow, QMessageBox)
 6from PySide6.QtGui import QKeySequence
 7from PySide6.QtSql import (QSqlRelation, QSqlRelationalTableModel, QSqlTableModel,
 8    QSqlError)
 9from PySide6.QtCore import QAbstractItemModel, QObject, QSize, Qt, Slot
10import createdb
11from ui_bookwindow import Ui_BookWindow
12from bookdelegate import BookDelegate
13
14

注意

导入包括您之前移植的BookDelegateUi_BookWindow。pyside-uic工具根据bookwindow.ui XML文件生成ui_bookwindow Python代码。

要生成此Python代码,请在提示符下运行以下命令:

pyside6-uic bookwindow.ui -o ui_bookwindow.py

尝试现在移植剩余的代码。首先,这里是两个版本的构造函数代码的样子:

C++ 版本

 1    // Initialize the database:
 2    QSqlError err = initDb();
 3    if (err.type() != QSqlError::NoError) {
 4        showError(err);
 5        return;
 6    }
 7
 8    // Create the data model:
 9    model = new QSqlRelationalTableModel(ui.bookTable);
10    model->setEditStrategy(QSqlTableModel::OnManualSubmit);
11    model->setTable("books");
12
13    // Remember the indexes of the columns:
14    authorIdx = model->fieldIndex("author");
15    genreIdx = model->fieldIndex("genre");
16
17    // Set the relations to the other database tables:
18    model->setRelation(authorIdx, QSqlRelation("authors", "id", "name"));
19    model->setRelation(genreIdx, QSqlRelation("genres", "id", "name"));
20
21    // Set the localized header captions:
22    model->setHeaderData(authorIdx, Qt::Horizontal, tr("Author Name"));
23    model->setHeaderData(genreIdx, Qt::Horizontal, tr("Genre"));
24    model->setHeaderData(model->fieldIndex("title"),
25                         Qt::Horizontal, tr("Title"));
26    model->setHeaderData(model->fieldIndex("year"), Qt::Horizontal, tr("Year"));
27    model->setHeaderData(model->fieldIndex("rating"),
28                         Qt::Horizontal, tr("Rating"));
29
30    // Populate the model:
31    if (!model->select()) {
32        showError(model->lastError());
33        return;
34    }
35
36    // Set the model and hide the ID column:
37    ui.bookTable->setModel(model);
38    ui.bookTable->setItemDelegate(new BookDelegate(ui.bookTable));
39    ui.bookTable->setColumnHidden(model->fieldIndex("id"), true);
40    ui.bookTable->setSelectionMode(QAbstractItemView::SingleSelection);
41
42    // Initialize the Author combo box:
43    ui.authorEdit->setModel(model->relationModel(authorIdx));
44    ui.authorEdit->setModelColumn(
45                model->relationModel(authorIdx)->fieldIndex("name"));
46
47    ui.genreEdit->setModel(model->relationModel(genreIdx));
48    ui.genreEdit->setModelColumn(
49                model->relationModel(genreIdx)->fieldIndex("name"));
50
51    // Lock and prohibit resizing of the width of the rating column:
52    ui.bookTable->horizontalHeader()->setSectionResizeMode(
53                model->fieldIndex("rating"),
54                QHeaderView::ResizeToContents);
55
56    QDataWidgetMapper *mapper = new QDataWidgetMapper(this);
57    mapper->setModel(model);
58    mapper->setItemDelegate(new BookDelegate(this));
59    mapper->addMapping(ui.titleEdit, model->fieldIndex("title"));
60    mapper->addMapping(ui.yearEdit, model->fieldIndex("year"));
61    mapper->addMapping(ui.authorEdit, authorIdx);
62    mapper->addMapping(ui.genreEdit, genreIdx);
63    mapper->addMapping(ui.ratingEdit, model->fieldIndex("rating"));
64
65    connect(ui.bookTable->selectionModel(),
66            &QItemSelectionModel::currentRowChanged,
67            mapper,
68            &QDataWidgetMapper::setCurrentModelIndex
69            );
70
71    ui.bookTable->setCurrentIndex(model->index(0, 0));
72    createMenuBar();
73}
74
75void BookWindow::showError(const QSqlError &err)
76{
77    QMessageBox::critical(this, "Unable to initialize Database",
78                "Error initializing database: " + err.text());
79}
80
81void BookWindow::createMenuBar()
82{
83    QAction *quitAction = new QAction(tr("&Quit"), this);

Python 版本

 1
 2class BookWindow(QMainWindow, Ui_BookWindow):
 3    # """A window to show the books available"""
 4
 5    def __init__(self):
 6        QMainWindow.__init__(self)
 7        self.setupUi(self)
 8
 9        #Initialize db
10        createdb.init_db()
11
12        model = QSqlRelationalTableModel(self.bookTable)
13        model.setEditStrategy(QSqlTableModel.OnManualSubmit)
14        model.setTable("books")
15
16        # Remember the indexes of the columns:
17        author_idx = model.fieldIndex("author")
18        genre_idx = model.fieldIndex("genre")
19
20        # Set the relations to the other database tables:
21        model.setRelation(author_idx, QSqlRelation("authors", "id", "name"))
22        model.setRelation(genre_idx, QSqlRelation("genres", "id", "name"))
23
24        # Set the localized header captions:
25        model.setHeaderData(author_idx, Qt.Horizontal, self.tr("Author Name"))
26        model.setHeaderData(genre_idx, Qt.Horizontal, self.tr("Genre"))
27        model.setHeaderData(model.fieldIndex("title"), Qt.Horizontal, self.tr("Title"))
28        model.setHeaderData(model.fieldIndex("year"), Qt.Horizontal, self.tr("Year"))
29        model.setHeaderData(model.fieldIndex("rating"), Qt.Horizontal, self.tr("Rating"))
30
31        if not model.select():
32            print(model.lastError())
33
34        # Set the model and hide the ID column:
35        self.bookTable.setModel(model)
36        self.bookTable.setItemDelegate(BookDelegate(self.bookTable))
37        self.bookTable.setColumnHidden(model.fieldIndex("id"), True)
38        self.bookTable.setSelectionMode(QAbstractItemView.SingleSelection)
39
40        # Initialize the Author combo box:
41        self.authorEdit.setModel(model.relationModel(author_idx))
42        self.authorEdit.setModelColumn(model.relationModel(author_idx).fieldIndex("name"))
43
44        self.genreEdit.setModel(model.relationModel(genre_idx))
45        self.genreEdit.setModelColumn(model.relationModel(genre_idx).fieldIndex("name"))
46
47        # Lock and prohibit resizing of the width of the rating column:
48        self.bookTable.horizontalHeader().setSectionResizeMode(model.fieldIndex("rating"),
49            QHeaderView.ResizeToContents)
50
51        mapper = QDataWidgetMapper(self)
52        mapper.setModel(model)
53        mapper.setItemDelegate(BookDelegate(self))
54        mapper.addMapping(self.titleEdit, model.fieldIndex("title"))
55        mapper.addMapping(self.yearEdit, model.fieldIndex("year"))
56        mapper.addMapping(self.authorEdit, author_idx)
57        mapper.addMapping(self.genreEdit, genre_idx)
58        mapper.addMapping(self.ratingEdit, model.fieldIndex("rating"))
59
60        selection_model = self.bookTable.selectionModel()
61        selection_model.currentRowChanged.connect(mapper.setCurrentModelIndex)
62
63        self.bookTable.setCurrentIndex(model.index(0, 0))
64        self.create_menubar()

注意

Python 版本的 BookWindow 类定义继承自 QMainWindowUi_BookWindow,后者定义在您之前生成的 ui_bookwindow.py 文件中。

以下是代码的其余部分:

C++ 版本

 1    mapper->setItemDelegate(new BookDelegate(this));
 2    mapper->addMapping(ui.titleEdit, model->fieldIndex("title"));
 3    mapper->addMapping(ui.yearEdit, model->fieldIndex("year"));
 4    mapper->addMapping(ui.authorEdit, authorIdx);
 5    mapper->addMapping(ui.genreEdit, genreIdx);
 6    mapper->addMapping(ui.ratingEdit, model->fieldIndex("rating"));
 7
 8    connect(ui.bookTable->selectionModel(),
 9            &QItemSelectionModel::currentRowChanged,
10            mapper,
11            &QDataWidgetMapper::setCurrentModelIndex
12            );
13
14    ui.bookTable->setCurrentIndex(model->index(0, 0));
15    createMenuBar();
16}
17
18void BookWindow::showError(const QSqlError &err)
19{
20    QMessageBox::critical(this, "Unable to initialize Database",
21                "Error initializing database: " + err.text());
22}
23
24void BookWindow::createMenuBar()
25{
26    QAction *quitAction = new QAction(tr("&Quit"), this);
27    QAction *aboutAction = new QAction(tr("&About"), this);
28    QAction *aboutQtAction = new QAction(tr("&About Qt"), this);
29
30    QMenu *fileMenu = menuBar()->addMenu(tr("&File"));
31    fileMenu->addAction(quitAction);
32
33    QMenu *helpMenu = menuBar()->addMenu(tr("&Help"));
34    helpMenu->addAction(aboutAction);
35    helpMenu->addAction(aboutQtAction);
36
37    connect(quitAction, &QAction::triggered, this, &BookWindow::close);
38    connect(aboutAction, &QAction::triggered, this, &BookWindow::about);
39    connect(aboutQtAction, &QAction::triggered, qApp, &QApplication::aboutQt);
40}
41
42void BookWindow::about()
43{
44    QMessageBox::about(this, tr("About Books"),
45            tr("<p>The <b>Books</b> example shows how to use Qt SQL classes "
46               "with a model/view framework."));
47}

Python 版本

 1
 2    def showError(err):
 3        QMessageBox.critical(self, "Unable to initialize Database",
 4                    "Error initializing database: " + err.text())
 5
 6    def create_menubar(self):
 7        file_menu = self.menuBar().addMenu(self.tr("&File"))
 8        quit_action = file_menu.addAction(self.tr("&Quit"))
 9        quit_action.triggered.connect(qApp.quit)
10
11        help_menu = self.menuBar().addMenu(self.tr("&Help"))
12        about_action = help_menu.addAction(self.tr("&About"))
13        about_action.setShortcut(QKeySequence.HelpContents)
14        about_action.triggered.connect(self.about)
15        aboutQt_action = help_menu.addAction("&About Qt")
16        aboutQt_action.triggered.connect(qApp.aboutQt)
17
18    def about(self):
19        QMessageBox.about(self, self.tr("About Books"),
20            self.tr("<p>The <b>Books</b> example shows how to use Qt SQL classes "
21                "with a model/view framework."))

现在所有必要的部分都已就位,尝试将它们整合到main.py中。

 1from __future__ import annotations
 2
 3import sys
 4from PySide6.QtWidgets import QApplication
 5from bookwindow import BookWindow
 6import rc_books
 7
 8if __name__ == "__main__":
 9    app = QApplication([])
10
11    window = BookWindow()
12    window.resize(800, 600)
13    window.show()
14
15    sys.exit(app.exec())

尝试运行此操作以查看是否获得以下输出:

BookWindow with a QTableView and a few input fields

现在,如果你回头看chapter2, 你会注意到bookdelegate.py从文件系统中加载了 star.png。相反,你可以将其添加到 qrc文件中,并从中加载。如果你的应用程序面向 不同的平台,推荐使用后一种方法,因为如今大多数流行的平台 都采用了更严格的文件访问策略。

要将star.png添加到.qrc中,创建一个名为books.qrc的文件,并将以下XML内容添加到其中:

1<!DOCTYPE RCC><RCC version="1.0">
2<qresource>
3  <file>images/star.png</file>
4</qresource>
5</RCC>

这是一个简单的XML文件,定义了应用程序所需的所有资源列表。在这种情况下,它只是star.png图像。

现在,在books.qrc文件上运行pyside6-rcc工具 以生成rc_books.py

pyside6-rcc books.qrc -o rc_books.py

一旦你生成了Python脚本,请对bookdelegate.pymain.py进行以下更改:

--- /data/snapshot-pyside-6.8-rel/tqtc-pyside-setup/build/testenv-tqtc_6.8/build/pyside6/doc/base/tutorials/portingguide/chapter2/bookdelegate.py
+++ /data/snapshot-pyside-6.8-rel/tqtc-pyside-setup/build/testenv-tqtc_6.8/build/pyside6/doc/base/tutorials/portingguide/chapter3/bookdelegate.py
@@ -2,24 +2,19 @@
 # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
 from __future__ import annotations
 
-import copy
-import os
-from pathlib import Path
-
+import copy, os
 from PySide6.QtSql import QSqlRelationalDelegate
 from PySide6.QtWidgets import (QItemDelegate, QSpinBox, QStyledItemDelegate,
     QStyle, QStyleOptionViewItem)
 from PySide6.QtGui import QMouseEvent, QPixmap, QPalette, QImage
 from PySide6.QtCore import QEvent, QSize, Qt, QUrl
 
-
 class BookDelegate(QSqlRelationalDelegate):
     """Books delegate to rate the books"""
 
-    def __init__(self, parent=None):
+    def __init__(self, star_png, parent=None):
         QSqlRelationalDelegate.__init__(self, parent)
-        star_png = Path(__file__).parent / "images" / "star.png"
-        self.star = QPixmap(star_png)
+        self.star = QPixmap(":/images/star.png")
 
     def paint(self, painter, option, index):
         """ Paint the items in the table.
--- /data/snapshot-pyside-6.8-rel/tqtc-pyside-setup/build/testenv-tqtc_6.8/build/pyside6/doc/base/tutorials/portingguide/chapter3/main-old.py
+++ /data/snapshot-pyside-6.8-rel/tqtc-pyside-setup/build/testenv-tqtc_6.8/build/pyside6/doc/base/tutorials/portingguide/chapter3/main.py
@@ -5,6 +5,7 @@
 import sys
 from PySide6.QtWidgets import QApplication
 from bookwindow import BookWindow
+import rc_books
 
 if __name__ == "__main__":
     app = QApplication([])

尽管这些更改后用户界面不会有明显差异,但使用.qrc是一种更好的方法。

现在你已经成功移植了SQL Books示例,你知道这有多容易。尝试移植另一个C++应用程序。