Chapter 3: Port bookdwindow.cpp
to bookwindow.py
¶
After the bookdelegate, port the C++ code for the
BookWindow
class. It offers a QMainWindow, containing a
QTableView
to present the books data, and a Details
section with a set of input fields to edit the selected row
in the table. To begin with, create the bookwindow.py
and add the following imports to it:
1
2from __future__ import print_function, absolute_import
3
4from PySide2.QtWidgets import (QAction, QAbstractItemView, QDataWidgetMapper,
5 QHeaderView, QMainWindow, QMessageBox)
6from PySide2.QtGui import QKeySequence
7from PySide2.QtSql import (QSqlRelation, QSqlRelationalTableModel, QSqlTableModel,
8 QSqlError)
9from PySide2.QtCore import QAbstractItemModel, QObject, QSize, Qt, Slot
10import createdb
11from ui_bookwindow import Ui_BookWindow
12from bookdelegate import BookDelegate
13
14
Note
The imports include the BookDelegate
you
ported earlier and the Ui_BookWindow
. The pyside-uic
tool generates the ui_bookwindow
Python code based
on the bookwindow.ui
XML file.
To generate this Python code, run the following command on the prompt:
pyside2-uic bookwindow.ui > ui_bookwindow.py
Try porting the remaining code now. To begin with, here is how both the versions of the constructor code looks:
C++ version¶
1BookWindow::BookWindow()
2{
3 ui.setupUi(this);
4
5 if (!QSqlDatabase::drivers().contains("QSQLITE"))
6 QMessageBox::critical(
7 this,
8 "Unable to load database",
9 "This demo needs the SQLITE driver"
10 );
11
12 // Initialize the database:
13 QSqlError err = initDb();
14 if (err.type() != QSqlError::NoError) {
15 showError(err);
16 return;
17 }
18
19 // Create the data model:
20 model = new QSqlRelationalTableModel(ui.bookTable);
21 model->setEditStrategy(QSqlTableModel::OnManualSubmit);
22 model->setTable("books");
23
24 // Remember the indexes of the columns:
25 authorIdx = model->fieldIndex("author");
26 genreIdx = model->fieldIndex("genre");
27
28 // Set the relations to the other database tables:
29 model->setRelation(authorIdx, QSqlRelation("authors", "id", "name"));
30 model->setRelation(genreIdx, QSqlRelation("genres", "id", "name"));
31
32 // Set the localized header captions:
33 model->setHeaderData(authorIdx, Qt::Horizontal, tr("Author Name"));
34 model->setHeaderData(genreIdx, Qt::Horizontal, tr("Genre"));
35 model->setHeaderData(model->fieldIndex("title"),
36 Qt::Horizontal, tr("Title"));
37 model->setHeaderData(model->fieldIndex("year"), Qt::Horizontal, tr("Year"));
38 model->setHeaderData(model->fieldIndex("rating"),
39 Qt::Horizontal, tr("Rating"));
40
41 // Populate the model:
42 if (!model->select()) {
43 showError(model->lastError());
44 return;
45 }
46
47 // Set the model and hide the ID column:
48 ui.bookTable->setModel(model);
49 ui.bookTable->setItemDelegate(new BookDelegate(ui.bookTable));
50 ui.bookTable->setColumnHidden(model->fieldIndex("id"), true);
51 ui.bookTable->setSelectionMode(QAbstractItemView::SingleSelection);
52
53 // Initialize the Author combo box:
54 ui.authorEdit->setModel(model->relationModel(authorIdx));
55 ui.authorEdit->setModelColumn(
56 model->relationModel(authorIdx)->fieldIndex("name"));
57
58 ui.genreEdit->setModel(model->relationModel(genreIdx));
59 ui.genreEdit->setModelColumn(
60 model->relationModel(genreIdx)->fieldIndex("name"));
61
62 // Lock and prohibit resizing of the width of the rating column:
63 ui.bookTable->horizontalHeader()->setSectionResizeMode(
64 model->fieldIndex("rating"),
65 QHeaderView::ResizeToContents);
66
67 QDataWidgetMapper *mapper = new QDataWidgetMapper(this);
68 mapper->setModel(model);
69 mapper->setItemDelegate(new BookDelegate(this));
70 mapper->addMapping(ui.titleEdit, model->fieldIndex("title"));
71 mapper->addMapping(ui.yearEdit, model->fieldIndex("year"));
72 mapper->addMapping(ui.authorEdit, authorIdx);
73 mapper->addMapping(ui.genreEdit, genreIdx);
74 mapper->addMapping(ui.ratingEdit, model->fieldIndex("rating"));
75
76 connect(ui.bookTable->selectionModel(),
77 &QItemSelectionModel::currentRowChanged,
78 mapper,
79 &QDataWidgetMapper::setCurrentModelIndex
80 );
81
82 ui.bookTable->setCurrentIndex(model->index(0, 0));
83 createMenuBar();
84}
Python version¶
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()
Note
The Python version of the BookWindow
class
definition inherits from both QMainWindow
and
Ui_BookWindow
, which is defined in the
ui_bookwindow.py
file that you generated earlier.
Here is how the rest of the code looks like:
C++ version¶
1 ui.genreEdit->setModelColumn(
2 model->relationModel(genreIdx)->fieldIndex("name"));
3
4 // Lock and prohibit resizing of the width of the rating column:
5 ui.bookTable->horizontalHeader()->setSectionResizeMode(
6 model->fieldIndex("rating"),
7 QHeaderView::ResizeToContents);
8
9 QDataWidgetMapper *mapper = new QDataWidgetMapper(this);
10 mapper->setModel(model);
11 mapper->setItemDelegate(new BookDelegate(this));
12 mapper->addMapping(ui.titleEdit, model->fieldIndex("title"));
13 mapper->addMapping(ui.yearEdit, model->fieldIndex("year"));
14 mapper->addMapping(ui.authorEdit, authorIdx);
15 mapper->addMapping(ui.genreEdit, genreIdx);
16 mapper->addMapping(ui.ratingEdit, model->fieldIndex("rating"));
17
18 connect(ui.bookTable->selectionModel(),
19 &QItemSelectionModel::currentRowChanged,
20 mapper,
21 &QDataWidgetMapper::setCurrentModelIndex
22 );
23
24 ui.bookTable->setCurrentIndex(model->index(0, 0));
25 createMenuBar();
26}
27
28void BookWindow::showError(const QSqlError &err)
29{
30 QMessageBox::critical(this, "Unable to initialize Database",
31 "Error initializing database: " + err.text());
32}
33
34void BookWindow::createMenuBar()
35{
36 QAction *quitAction = new QAction(tr("&Quit"), this);
37 QAction *aboutAction = new QAction(tr("&About"), this);
38 QAction *aboutQtAction = new QAction(tr("&About Qt"), this);
39
40 QMenu *fileMenu = menuBar()->addMenu(tr("&File"));
41 fileMenu->addAction(quitAction);
42
43 QMenu *helpMenu = menuBar()->addMenu(tr("&Help"));
44 helpMenu->addAction(aboutAction);
45 helpMenu->addAction(aboutQtAction);
46
47 connect(quitAction, &QAction::triggered, this, &BookWindow::close);
48 connect(aboutAction, &QAction::triggered, this, &BookWindow::about);
49 connect(aboutQtAction, &QAction::triggered, qApp, &QApplication::aboutQt);
50}
51
52void BookWindow::about()
53{
54 QMessageBox::about(this, tr("About Books"),
55 tr("<p>The <b>Books</b> example shows how to use Qt SQL classes "
56 "with a model/view framework."));
57}
Python version¶
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."))
Now that all the necessary pieces are in place, try to put
them together in main.py
.
1
2import sys
3from PySide2.QtWidgets import QApplication
4from bookwindow import BookWindow
5import rc_books
6
7if __name__ == "__main__":
8 app = QApplication([])
9
10 window = BookWindow()
11 window.resize(800, 600)
12 window.show()
13
14 sys.exit(app.exec_())
Try running this to see if you get the following output:
Now, if you look back at chapter2,
you’ll notice that the bookdelegate.py
loads the
star.png
from the filesytem. Instead, you could add it
to a qrc
file, and load from it. The later approach is
rececommended if your application is targeted for
different platforms, as most of the popular platforms
employ stricter file access policy these days.
To add the star.png
to a .qrc
, create a file called
books.qrc
and the following XML content to it:
1<!DOCTYPE RCC><RCC version="1.0">
2<qresource>
3 <file>images/star.png</file>
4</qresource>
5</RCC>
This is a simple XML file defining a list all resources that
your application needs. In this case, it is the star.png
image only.
Now, run the pyside2-rcc
tool on the books.qrc
file
to generate rc_books.py
.
pyside2-rcc books.qrc > rc_books.py
Once you have the Python script generated, make the
following changes to bookdelegate.py
and main.py
:
--- /data/snapshot-pyside2-rel/tqtc-pyside-setup/testenv-tqtc_lts-5.15.113_build/py3.10-qt5.15.12-64bit-release/pyside2/doc/rst/tutorials/portingguide/chapter2/bookdelegate.py
+++ /data/snapshot-pyside2-rel/tqtc-pyside-setup/testenv-tqtc_lts-5.15.113_build/py3.10-qt5.15.12-64bit-release/pyside2/doc/rst/tutorials/portingguide/chapter3/bookdelegate.py
@@ -48,10 +48,9 @@
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 = os.path.dirname(__file__) + "\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-pyside2-rel/tqtc-pyside-setup/testenv-tqtc_lts-5.15.113_build/py3.10-qt5.15.12-64bit-release/pyside2/doc/rst/tutorials/portingguide/chapter3/main-old.py
+++ /data/snapshot-pyside2-rel/tqtc-pyside-setup/testenv-tqtc_lts-5.15.113_build/py3.10-qt5.15.12-64bit-release/pyside2/doc/rst/tutorials/portingguide/chapter3/main.py
@@ -41,6 +41,7 @@
import sys
from PySide2.QtWidgets import QApplication
from bookwindow import BookWindow
+import rc_books
if __name__ == "__main__":
app = QApplication([])
Although there will be no noticeable difference in the UI
after these changes, using a .qrc
is a better approach.
Now that you have successfully ported the SQL Books example, you know how easy it is. Try porting another C++ application.
© 2022 The Qt Company Ltd. Documentation contributions included herein are the copyrights of their respective owners. The documentation provided herein is licensed under the terms of the GNU Free Documentation License version 1.3 as published by the Free Software Foundation. Qt and respective logos are trademarks of The Qt Company Ltd. in Finland and/or other countries worldwide. All other trademarks are property of their respective owners.