ВУЗ: Не указан
Категория: Не указан
Дисциплина: Не указана
Добавлен: 05.12.2023
Просмотров: 873
Скачиваний: 3
ВНИМАНИЕ! Если данный файл нарушает Ваши авторские права, то обязательно сообщите нам.
СОДЕРЖАНИЕ
Глава 23. Работа с базами данных
547
П
РИМЕЧАНИЕ
Нужно отметить, что после удаления записи вызовом метода removeRow() или removeRows()
в модели останется пустая запись, реально не представляющая никакой записи из таблицы. Чтобы убрать ее, достаточно выполнить повторное считывание данных в модель вызовом метода select().
insertRecord(<Индекс>,
— добавляет в модель новую запись в позицию, указанную первым параметром. Если значение первого параметра отрицательное, запись добавляется в конец модели. Добавляемая запись представляется экземпляром объекта
QSqlRecord
, уже заполненным необходимыми данными. Возвращает
True
, если запись была успешно добавлена, и
False
— в противном случае;
setRecord(<Индекс>,
— заменяет запись в позиции, указанной первым параметром, новой записью, которая передается вторым параметром в виде экземпляра объекта
QSqlRecord
, уже заполненного необходимыми данными. Возвращает
True
, если запись была успешно изменена, и
False
— в противном случае;
submit()
— переносит в базу данных изменения, сделанные в текущей записи, если был задан режим редактирования
OnManualSubmit
. Возвращает
True
, если изменения были успешно перенесены, и
False
— в противном случае. Метод является слотом;
submitAll()
— переносит в базу данных изменения, сделанные во всех записях, если был задан режим редактирования
OnManualSubmit
. Возвращает
True
, если изменения были успешно перенесены, и
False
— в противном случае. Метод является слотом;
revert()
— отменяет изменения, сделанные в текущей записи, если был задан режим редактирования
OnManualSubmit
. Возвращает
True
, если изменения были успешно отме- нены, и
False
— в противном случае. Метод является слотом;
revertRow(<Индекс записи>)
— отменяет изменения, сделанные в записи с заданным индексом, если был задан режим редактирования
OnManualSubmit
;
revertAll()
— отменяет изменения, сделанные во всех записях, если был задан режим редактирования
OnManualSubmit
. Возвращает
True
, если изменения были успешно отме- нены, и
False
— в противном случае. Метод является слотом;
selectRow(<Индекс строки>)
— обновляет содержимое строки с указанным индексом.
Возвращает
True
, если запись была успешно обновлена, и
False
— в противном случае.
Метод является слотом;
isDirty(
— возвращает
True
, если запись с указанным индексом (экземп- ляр класса
QModelIndex
) была изменена, но эти изменения еще не были перенесены в ба- зу данных, и
False
— в противном случае;
isDirty()
— возвращает
True
, если хотя бы одна запись в модели была изменена, но эти изменения еще не были перенесены в базу данных, и
False
— в противном случае;
fieldIndex(<Имя поля>)
— возвращает индекс поля с указанным именем или
-1
, если такого поля нет;
primaryKey()
— возвращает сведения о ключевом индексе таблицы, представленные экземпляром класса
QSqlIndex
, или пустой экземпляр этого класса, если таблица не со- держит ключевого индекса.
Методы insertRecord()
и setRecord()
, предназначенные, соответственно, для добавления и изменения записи, принимают в качестве второго параметра экземпляр класса
QSqlRecord
548
Часть II. Библиотека PyQt 5
Чтобы создать этот экземпляр, нам следует знать формат вызова конструктора класса.
Вот он:
<Объект> = QSqlRecord([
Если в параметре указать экземпляр класса
QSqlRecord
, будет создана его копия. Обычно при создании новой записи здесь указывают значение, возвращенное методом record()
класса
QSqlDatabase
(оно хранит сведения о структуре таблицы и, следовательно, представ- ляет пустую запись), а при правке существующей записи — значение, возвращенное мето- дом record()
, который унаследован классом
QSqlTableModel от класса
QSqlQueryModel
(оно представляет запись, которую нужно отредактировать).
Класс
QSqlRecord
, в дополнение к методам, рассмотренным нами в разд. 23.2.1, поддержи- вает следующие методы:
value(<Индекс поля>)
— возвращает значение поля текущей записи с заданным индек- сом;
value(<Имя поля>)
— возвращает значение поля текущей записи с заданным именем;
setValue(<Индекс поля>, <Значение>)
— заносит в поле с указанным индексом новое значение;
setValue(<Имя поля>, <Значение>)
— заносит в поле с указанным именем новое значе- ние;
isNull(<Индекс поля>)
— возвращает
True
, если в поле с указанным индексом нет зна- чения, и
False
— в противном случае;
isNull(<Имя поля>)
— возвращает
True
, если в поле с указанным именем нет значения, и
False
— в противном случае;
setNull(<Индекс поля>)
— удаляет значение из поля с указанным индексом;
setNull(<Имя поля>)
— удаляет значение из поля с указанным именем;
clearValues()
— удаляет значения из всех полей записи;
setGenerated(<Индекс поля>, <Флаг>)
— если вторым параметром передано
False
, поле с указанным индексом помечается как неактуальное, и хранящееся в нем значение не будет перенесено в таблицу;
setGenerated(<Имя поля>, <Флаг>)
— если вторым параметром передано
False
, поле с указанным именем помечается как неактуальное, и хранящееся в нем значение не бу- дет перенесено в таблицу;
isGenerated(<Индекс поля>)
— возвращает
False
, если поле с указанным индексом по- мечено как неактуальное, и
True
— в противном случае;
isGenerated(<Имя поля>)
— возвращает
False
, если поле с указанным именем помечено как неактуальное, и
True
— в противном случае.
Вот пример кода, добавляющего новую запись в модель: con = QtSql.QSqlDatabase.addDatabase('QSQLITE') con.setDatabaseName('data.sqlite') con.open() stm = QtSql.QSqlTableModel() stm.setTable('good') stm.select() rec = con.record('good')
Глава 23. Работа с базами данных
549 rec.setValue('goodname', 'Коврик для мыши') rec.setValue('goodcount', 2) stm.insertRecord(-1, rec)
А вот пример кода, редактирующего существующую запись с индексом
3
: rec = stm.record(3) rec.setValue('goodcount', 5) stm.setRecord(3, rec)
Класс
QSqlTableModel поддерживает такие сигналы:
primeInsert(<Индекс записи>,
— генерируется перед добавлением запи- си в модель. В первом параметре доступен целочисленный индекс добавляемой записи, а во втором — сама добавляемая запись, обычно пустая, в которую можно занести какие- либо изначальные данные;
beforeInsert(
— генерируется перед добавлением новой записи в табли- цу. В параметре доступна добавляемая запись;
beforeUpdate(<Индекс записи>,
— генерируется перед изменением запи- си в таблице. В параметрах доступны целочисленный индекс изменяемой записи и сама изменяемая запись;
beforeDelete(<Индекс записи>)
— генерируется перед удалением записи из таблицы.
В параметре доступен целочисленный индекс удаляемой записи;
dataChanged(
— генерируется при изменении данных в модели пользователем. Первым параметром передается индекс верхней левой из набора измененных записей, вторым — индекс правой нижней. Необязательный па- раметр roles хранит список ролей, данные которых изменились. Если указан пустой список, значит, изменились данные во всех ролях.
Сигнал dataChanged
— идеальное место для вызова методов submit()
или submitAll() в случае, если для модели был задан режим редактирования
OnManualSubmit
. Как мы зна- ем, эти методы выполняют сохранение отредактированных данных в базе.
В листинге 23.9 представлен код тестового складского приложения, позволяющего не толь- ко править, но и добавлять и удалять записи нажатием соответствующих кнопок. А на рис. 23.1 можно увидеть само это приложение в работе.
Листинг 23.9. Использование модели, привязанной к таблице from PyQt5 import QtCore, QtWidgets, QtSql import sys def addRecord():
# Вставляем пустую запись, в которую пользователь сможет
# ввести нужные данные stm.insertRow(stm.rowCount()) def delRecord():
# Удаляем запись из модели stm.removeRow(tv.currentIndex().row())
# Выполняем повторное считывание данных в модель,
# чтобы убрать пустую "мусорную" запись stm.select()
550
Часть II. Библиотека PyQt 5 app = QtWidgets.QApplication(sys.argv) window = QtWidgets.QWidget() window.setWindowTitle("QSqlTableModel")
# Устанавливаем соединение с базой данных con = QtSql.QSqlDatabase.addDatabase('QSQLITE') con.setDatabaseName('data.sqlite') con.open()
# Создаем модель stm = QtSql.QSqlTableModel(parent=window) stm.setTable('good') stm.setSort(1, QtCore.Qt.AscendingOrder) stm.select()
# Задаем заголовки для столбцов модели stm.setHeaderData(1, QtCore.Qt.Horizontal, 'Название') stm.setHeaderData(2, QtCore.Qt.Horizontal, 'Кол-во')
# Задаем для таблицы только что созданную модель vbox = QtWidgets.QVBoxLayout() tv = QtWidgets.QTableView() tv.setModel(stm)
# Скрываем первый столбец, в котором выводится идентификатор tv.hideColumn(0) tv.setColumnWidth(1, 150) tv.setColumnWidth(2, 60) vbox.addWidget(tv) btnAdd = QtWidgets.QPushButton("&Добавить запись") btnAdd.clicked.connect(addRecord) vbox.addWidget(btnAdd) btnDel = QtWidgets.QPushButton("&Удалить запись") btnDel.clicked.connect(delRecord) vbox.addWidget(btnDel) window.setLayout(vbox) window.resize(300, 250) window.show() sys.exit(app.exec_())
Рис. 23.1. Пример складского приложения, использующего модель
QSqlTableModel
Глава 23. Работа с базами данных
551 23.4.3. Модель, поддерживающая межтабличные связи
Предположим, что мы решили расширить наше простенькое складское приложение, введя разбиение товаров на категории. В базе данных data.sqlite мы создали таблицу category с полями id и catname
, а в таблицу good добавили поле category
, где будут храниться иден- тификаторы категорий.
Теперь попытаемся вывести содержимое таблицы good на экран с помощью модели
QSqlTableModel и компонента таблицы
QTableView
. И сразу увидим, что в колонке, где пока- зывается содержимое поля category
, выводятся числовые идентификаторы категорий
(рис. 23.2). А нам хотелось бы видеть там наименования категорий вместо непонятной ци- фири.
Рис. 23.2. Пример складского приложения после доработки: вместо названий категорий выводятся их числовые идентификаторы
Сделать это поможет класс
QSqlRelationalTableModel
, добавляющий уже известной нам модели
QSqlTableModel возможность связывать таблицы. Мы указываем поле внешнего ключа, первичную таблицу и в ней — поле первичного ключа и поле, откуда будет взято значение для вывода на экран.
Иерархия наследования класса
QSqlRelationalTableModel
:
QObject – QAbstractItemModel – QAbstractTableModel – QSqlQueryModel –
QSqlTableModel - QSqlRelationalTableModel
Конструктор класса:
<Объект> = QSqlRelationalQueryModel([parent=None][, db=QSqlDatabase()])
Необязательный параметр db задает соединение с базой данных, запрос к которой следует выполнить, — если он не указан, будет использоваться соединение по умолчанию.
Класс
QSqlRelationalTableModel наследует все методы класса
QSqlTableModel
(см. разд. 23.4.2) и в дополнение к ним определяет следующие полезные для нас методы (полный их список приведен на странице https://doc.qt.io/qt-5/qsqlrelationaltablemodel.html):
setRelation(<Индекс столбца>,
— задает связь для поля с указанным индексом. Сведения об устанавливаемой связи представляются экземпляром класса
QSqlRelation
, о котором мы поговорим чуть позже;
552
Часть II. Библиотека PyQt 5
setJoinMode(<Режим связывания>)
— задает режим связывания для всей модели. В каче- стве параметра указывается один из атрибутов класса
QSqlRelationalTableModel
:
•
InnerJoin
—
0
— каждой записи вторичной таблицы должна соответствовать связан- ная с ней запись первичной таблицы. Используется по умолчанию;
•
LeftJoin
—
1
— записи вторичной таблицы не обязательно должна соответствовать связанная запись первичной таблицы.
Теперь о классе
QSqlRelation
. Он представляет связь, устанавливаемую между полями таб- лиц. Конструктор этого класса имеет такой формат:
<Объект> = QSqlRelation(<Имя первичной таблицы>,
<Имя поля первичного ключа>,
<Имя поля, выводящегося на экран>)
Поля, чьи имена указываются во втором и третьем параметрах, относятся к первичной таб- лице.
Класс
QSqlRelation поддерживает несколько методов, но они не очень нам интересны (пол- ное описание этого класса доступно на странице https://doc.qt.io/qt-5/qsqlrelation.html).
В листинге 23.10 приведен код исправленного складского приложения, а на рис. 23.3 пока- зан его интерфейс.
Листинг 23.10. Использование модели QSqlRelationalTableModel from PyQt5 import QtCore, QtWidgets, QtSql import sys def addRecord(): stm.insertRow(stm.rowCount()) def delRecord(): stm.removeRow(tv.currentIndex().row()) stm.select() app = QtWidgets.QApplication(sys.argv) window = QtWidgets.QWidget() window.setWindowTitle("QRelationalSqlTableModel") con = QtSql.QSqlDatabase.addDatabase('QSQLITE') con.setDatabaseName('data.sqlite') con.open() stm = QtSql.QSqlRelationalTableModel(parent=window) stm.setTable('good') stm.setSort(1, QtCore.Qt.AscendingOrder)
# Задаем для поля категории связь с таблицей списка категорий stm.setRelation(3, QtSql.QSqlRelation('category', 'id', 'catname')) stm.select() stm.setHeaderData(1, QtCore.Qt.Horizontal, 'Название') stm.setHeaderData(2, QtCore.Qt.Horizontal, 'Кол-во') stm.setHeaderData(3, QtCore.Qt.Horizontal, 'Категория') vbox = QtWidgets.QVBoxLayout() tv = QtWidgets.QTableView()
Глава 23. Работа с базами данных
553 tv.setModel(stm) tv.hideColumn(0) tv.setColumnWidth(1, 150) tv.setColumnWidth(2, 60) tv.setColumnWidth(3, 150) vbox.addWidget(tv) btnAdd = QtWidgets.QPushButton("&Добавить запись") btnAdd.clicked.connect(addRecord) vbox.addWidget(btnAdd) btnDel = QtWidgets.QPushButton("&Удалить запись") btnDel.clicked.connect(delRecord) vbox.addWidget(btnDel) window.setLayout(vbox) window.resize(420, 250) window.show() sys.exit(app.exec_())
Рис. 23.3. Пример складского приложения, использующего модель
QSqlRelationalTableModel
: на экран выводятся названия категорий
23.4.4. Использование связанных делегатов
К сожалению, наше новое приложение имеет один существеннейший недостаток — как только мы решим добавить новую запись или даже исправить уже существующую, то столкнемся с тем, что все сделанные нами изменения не сохраняются. Почему?
Дело в том, что модель
QSqlRelationalTableModel
«не знает», как перевести введенное нами название категории в ее идентификатор, который и хранится в поле category таблицы good
Она лишь выполняет попытку занести строковое название категории в поле целочисленного типа, что вполне ожидаемо вызывает ошибку, и запись в таблице не сохраняется.
Исправить такое положение дел нам позволит особый делегат, называемый связанным
(о делегатах рассказывалось в разд. 22.8). Он способен выполнить поиск в первичной таб- лице нужной записи, извлечь ее идентификатор и сохранить его в поле вторичной таблицы.
А, кроме того, он представляет все доступные для занесения в поле значения, взятые из первичной таблицы, в виде раскрывающегося списка — очень удобно!
554
Часть II. Библиотека PyQt 5
Функциональность связанного делегата реализует класс
QSqlRelationalDelegate
. Иерархия наследования:
QObject – QAbstractItemDelegate – QItemDelegate - QSqlRelationalDelegate
Использовать связанный делегат очень просто — нужно лишь создать его экземпляр, пере- дав конструктору класса ссылку на компонент-представление (в нашем случае — таблицу), и вызвать у представления метод setItemDelegate()
, setItemDelegateForColumn()
или setItemDelegateForRow()
, указав в нем только что созданный делегат.
Исходя из этого, давайте, наконец, доделаем до конца наше складское приложение, дав пользователю возможность выбирать категории товаров из списка. Для этого нам потребу- ется лишь вставить в код листинга 23.10 всего одно новое выражение (в приведенном далее листинге 23.11 оно выделено полужирным шрифтом):
Листинг 23.11. Использование связанного делегата
(фрагмент исправленного кода из листинга 23.10) tv = QtWidgets.QTableView() tv.setModel(stm) tv.setItemDelegateForColumn(3, QtSql.QSqlRelationalDelegate(tv)) tv.hideColumn(0)
Интерфейс законченного приложения показан на рис. 23.4.
Рис. 23.4. Окончательный вариант складского приложения, использующего связанный делегат
ГЛ А В А
24
Работа с графикой
Все компоненты, которые мы рассматривали в предыдущих главах, на самом деле нарисо- ваны. То есть, каждый раз, когда компонент становится видимым (в первый раз, при ото- бражении части компонента, ранее перекрытой другим окном, или после изменения его па- раметров), вызывается метод paintEvent()
(см. разд. 19.7.3). Вызвать событие перерисовки компонента можно и искусственно — с помощью методов repaint()
и update()
класса
QWidget
. Внутри метода paintEvent()
выполняется рисование компонента с помощью мето- дов класса
QPainter
Класс
QPainter поддерживает все необходимые средства, позволяющие выполнять рисова- ние геометрических фигур и вывод текста на поверхности, которая реализуется классом
QPaintDevice
. Класс
QWidget наследует класс
QPaintDevice
. В свою очередь класс
QWidget наследуют все компоненты, поэтому мы можем рисовать на поверхности любого ком- понента. Класс
QPaintDevice наследуют также классы
QPicture
,
QPixmap
,
QImage
,
QPagedPaintDevice и некоторые другие.
Класс
QPicture позволяет сохранить команды рисования в метафайл, а затем считать их из файла и воспроизвести на какой-либо поверхности. Классы
QPixmap и
QImage позволяют об- рабатывать изображения. Основные методы этих классов мы рассмотрим далее в этой главе.
Класс
QPagedPaintDevice является базовым для классов
QPrinter
(позволяет выводить документы на печать) и
QPdfWriter
(используется для экспорта документов в PDF-файлы).
Мы рассмотрим их в главе 29.
Все описанные в этой главе классы объявлены в модуле
QtGui
, если не указано обратное.
Библиотека PyQt 5 также позволяет работать с SVG-графикой и включает в свой состав поддержку технологии OpenGL, предназначенной для обработки двумерной и трехмерной графики. Рассмотрение этих возможностей выходит за рамки нашей книги, поэтому за под- робной информацией о них вам следует обратиться к соответствующей документации.
24.1. Вспомогательные классы
Прежде чем изучать работу с графикой, необходимо рассмотреть несколько вспомогатель- ных классов, с помощью которых производится настройка различных параметров: цвета, характеристик шрифта, стиля пера и кисти. Кроме того, мы рассмотрим классы, описываю- щие геометрические фигуры (например, линию и многоугольник).