ээдд. Прохоренок_Н_А__Дронов_В_А_Python_3_и_PyQt_5_Разработка_приложен. Николай Прохоренок Владимир Дронов
Скачать 7.92 Mb.
|
) запрос переходит в активное состояние. Это значит, что выполнить любую другую SQL-команду с его по- мощью невозможно. Метод isActive() класса QSqlQuery возвращает True , если запрос находится в активном со- стоянии. Если же запрос неактивен, метод возвращает False Если один и тот же экземпляр класса QSqlQuery планируется использовать для выполнения нескольких SQL-команд, перед выполнением новой команды следует сбросить его, переве- дя тем самым в неактивное состояние и освободив занимаемые им системные ресурсы. Это выполняется вызовом метода clear() : query = QtSql.QSqlQuery() query.exec("select * from good order by goodname") # Обрабатываем результат запроса query.clear() query.exec("select count(*) as cnt from good") # Работаем с новым запросом 23.3.4. Получение служебных сведений о запросе Класс QSqlQuery позволяет также получить всевозможные служебные сведения о запросе. Для этого применяются следующие методы: numRowsAffected() — возвращает количество записей, обработанных в процессе выпол- нения запроса, или -1 , если это количество не удается определить. Для запросов выбор- ки данных возвращает None — в этом случае следует вызывать метод size() ; lastInsertId() — возвращает идентификатор последней добавленной записи. Если за- прос не добавлял записи, или если формат базы данных не позволяет определить иден- тификатор последней добавленной записи, возвращает None ; lastError() — возвращает экземпляр объекта QSqlError , описывающий последнюю возникшую в базе данных ошибку; executedQuery() — возвращает SQL-код последнего выполненного запроса или пустую строку, если никакой запрос еще не был выполнен; lastQuery() — возвращает код последнего выполненного запроса или пустую строку, если никакой запрос еще не был выполнен. Отличается от метода executedQuery() тем, что все именованные параметры (заданные символьными обозначениями) в возвращае- мом SQL-коде заменяются вопросительными знаками; boundValue(<Номер параметра>) — возвращает значение параметра запроса с указанным номером; boundValue(<Обозначение параметра>) — возвращает значение параметра запроса с ука- занным символьным обозначением; boundValues() — возвращает словарь, ключами элементов которого служат символьные обозначения параметров, а значениями элементов — значения этих параметров. Если параметры обозначены вопросительными знаками, в качестве ключей используются произвольные строки вида :a для первого параметра, :bb для второго и т. д. 544 Часть II. Библиотека PyQt 5 23.4. Модели, связанные с данными Очень часто данные, хранящиеся в базе, выводятся на экран с применением таких компо- нентов, как списки или таблицы (подробно списки и таблицы описаны в главе 22). Для этих случаев PyQt предоставляет два класса-модели, извлекающие данные напрямую из базы. 23.4.1. Модель, связанная с SQL-запросом Если требуется вывести на экран данные, извлеченные в результате выполнения SQL- запроса, и эти данные не требуется редактировать, имеет смысл использовать класс QSqlQueryModel . Он представляет модель, связанную с SQL-запросом. Иерархия наследова- ния этого класса: QObject – QAbstractItemModel – QAbstractTableModel – QSqlQueryModel Конструктор класса: <Объект> = QSqlQueryModel([parent=None]) Класс QSqlQueryModel поддерживает следующие методы (здесь приведен их сокращенный список, а полный список методов этого класса доступен на страницах https://doc.qt.io/ qt-5/qsqlquerymodel.html и https://doc.qt.io/qt-5/qabstractitemmodel.html): setQuery(<Код запроса>[, db=QSqlDatabase()]) — задает код запроса для модели. Не- обязательный параметр db задает соединение с базой данных, запрос к которой следует выполнить, — если он не указан, будет использоваться соединение по умолчанию; query() — возвращает код запроса, заданного для модели; record() — возвращает экземпляр класса QSqlRecord , представляющий сведения о струк- туре результата запроса; record(<Индекс строки>) — возвращает экземпляр класса QSqlRecord , представляющий сведения о записи, которая соответствует строке модели с указанным индексом; lastError() — возвращает экземпляр объекта QSqlError , описывающий последнюю возникшую в базе данных ошибку; index(<Строка>, <Столбец>[, parent=QModelIndex()]) — возвращает индекс (экземпляр класса QModelIndex ) элемента модели, находящегося на пересечении строки и столбца с указанными индексами. Необязательный параметр parent позволяет задать элемент верхнего уровня для искомого элемента — если таковой не задан, будет выполнен поиск элемента на самом верхнем уровне иерархии; data( — возвращает данные, хранимые в указанной в параметре role роли элемента, на который ссылается индекс ; rowCount([parent=QModelIndex()]) — возвращает количество элементов в модели. Не- обязательный параметр parent указывает элемент верхнего уровня, при этом будет воз- вращено количество вложенных в него элементов. Если параметр не задан, возвращается количество элементов верхнего уровня иерархии; sort(<Индекс столбца>[, order=AscendingOrder]) — производит сортировку. Если во втором параметре указан атрибут AscendingOrder класса QtCore.Qt , сортировка произво- дится в прямом порядке, а если DescendingOrder — в обратном; setHeaderData(<Индекс>, <Ориентация>, <Значение>[, role=EditRole]) — задает зна- чение для указанной роли заголовка. В первом параметре указывается индекс строки или Глава 23. Работа с базами данных 545 столбца, а во втором — ориентация (атрибут Horizontal или Vertical класса QtCore.Qt ). Метод возвращает значение True , если операция успешно выполнена; headerData(<Индекс>, <Ориентация>[, role=DisplayRole]) — возвращает значение, со- ответствующее указанной роли заголовка. В первом параметре указывается индекс стро- ки или столбца, а во втором — ориентация. Рассмотрим пример, выводящий данные из созданной нами ранее базы с помощью компо- нента таблицы (листинг 23.8). Листинг 23.8. Использование модели, привязанной к SQL-запросу from PyQt5 import QtCore, QtWidgets, QtSql import sys app = QtWidgets.QApplication(sys.argv) window = QtWidgets.QTableView() window.setWindowTitle("QSqlQueryModel") # Устанавливаем соединение с базой данных con = QtSql.QSqlDatabase.addDatabase('QSQLITE') con.setDatabaseName('data.sqlite') con.open() # Создаем модель sqm = QtSql.QSqlQueryModel(parent=window) sqm.setQuery('select * from good order by goodname') # Задаем заголовки для столбцов модели sqm.setHeaderData(1, QtCore.Qt.Horizontal, 'Название') sqm.setHeaderData(2, QtCore.Qt.Horizontal, 'Кол-во') # Задаем для таблицы только что созданную модель window.setModel(sqm) # Скрываем первый столбец, в котором выводится идентификатор window.hideColumn(0) window.setColumnWidth(1, 150) window.setColumnWidth(2, 60) window.resize(260, 160) window.show() sys.exit(app.exec_()) 23.4.2. Модель, связанная с таблицей Если необходимо дать пользователю возможность редактировать данные, хранящиеся в базе, следует использовать класс QSqlTableModel . Он представляет модель, связанную не- посредственно с указанной таблицей базы данных. Иерархия наследования: QObject – QAbstractItemModel – QAbstractTableModel – QSqlQueryModel – QSqlTableModel Конструктор класса: <Объект> = QSqlTableModel([parent=None][, db=QSqlDatabase()]) Необязательный параметр db задает соединение с базой данных, запрос к которой следует выполнить, — если он не указан, будет использоваться соединение по умолчанию. 546 Часть II. Библиотека PyQt 5 Класс QSqlTableModel наследует все методы из класса QSqlQueryModel (см. разд. 23.4.1) и в дополнение к ним определяет следующие наиболее полезные для нас методы (полный их список приведен на странице https://doc.qt.io/qt-5/qsqltablemodel.html): setTable(<Имя таблицы>) — задает таблицу, данные из которой будут представлены в модели. Отметим, что этот метод лишь выполняет получение из базы данных структу- ры указанной таблицы, но не загружает сами эти данные; tableName() — возвращает имя таблицы, заданной для модели; setSort(<Индекс столбца>, <Порядок сортировки>) — задает сортировку данных. Если во втором параметре указан атрибут AscendingOrder класса QtCore.Qt , сортировка про- изводится в прямом порядке, а если DescendingOrder — в обратном; setFilter(<Условие фильтрации>) — задает условие для фильтрации данных в виде строки в том формате, который применяется в SQL-команде WHERE ; filter() — возвращает строку с фильтром, заданным для модели; select() — считывает в модель данные из заданной ранее таблицы с учетом указанных параметров сортировки и фильтрации. Возвращает True , если считывание данных про- шло успешно, и False — в противном случае: stm = QtSql.QSqlTableModel(parent=window) stm.setTable('good') stm.setSort(1, QtCore.Qt.DescendingOrder) stm.setFilter('goodcount > 2') stm.select() Метод является слотом; setEditStrategy(<Режим редактирования>) — указывает режим редактирования данных в модели. В качестве параметра используется один из атрибутов класса QSqlTableModel : • OnFieldChange — 0 — все изменения переносятся в базу данных немедленно; • OnRowChange — 1 — изменения переносятся в базу лишь после того, как пользователь перейдет на другую строку; • OnManualSubmit — 2 — изменения переносятся в базу только после вызова метода submit() или submitAll() ; insertRow(<Индекс>[, parent=QModelIndex()]) — вставляет пустую запись в позицию, заданную первым параметром. Возвращает значение True , если запись была успешно добавлена, и False — в противном случае; insertRows(<Индекс>, <Количество>[, parent=QModelIndex()]) — вставляет указанное количество пустых записей в позицию, заданную первым параметром. Возвращает зна- чение True , если записи были успешно добавлены, и False — в противном случае; setData( — задает значение для роли role поля записи, на которое указывает индекс . Возвращает значение True , если данные были успешно занесены в запись, и False — в противном случае; removeRow(<Индекс>[, parent=QModelIndex()]) — удаляет запись с указанным индексом. Возвращает значение True , если запись была успешно удалена, и False — в противном случае; removeRows(<Индекс>, <Количество>[, parent=QModelIndex()]) — удаляет указанное количество записей, начиная с записи с указанным индексом. Возвращает значение True , если записи были успешно удалены, и False — в противном случае; Глава 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. Вспомогательные классы Прежде чем изучать работу с графикой, необходимо рассмотреть несколько вспомогатель- ных классов, с помощью которых производится настройка различных параметров: цвета, характеристик шрифта, стиля пера и кисти. Кроме того, мы рассмотрим классы, описываю- щие геометрические фигуры (например, линию и многоугольник). 556 Часть II. Библиотека PyQt 5 24.1.1. Класс QColor: цвет Класс QColor описывает цвет в цветовых моделях RGB, CMYK, HSV или HSL. Форматы конструктора класса QColor : <Объект> = QColor() <Объект> = QColor(<Красный>, <Зеленый>, <Синий>[, alpha=255]) <Объект> = QColor(<Строка>) <Объект> = QColor(<Атрибут цвета>) <Объект> = QColor(<Число>) <Объект> = QColor( Первый конструктор создает невалидный объект. Проверить объект на валидность можно с помощью метода isValid() . Метод возвращает значение True , если объект является валидным, и False — в противном случае. Второй конструктор позволяет указать целочисленные значения красной, зеленой и синей составляющих цвета модели RGB. В качестве параметров указываются числа от 0 до 255 Необязательный параметр alpha задает степень прозрачности цвета. Значение 0 соответст- вует прозрачному цвету, а значение 255 — полностью непрозрачному. Вот пример указания красного цвета: red = QtGui.QColor(255, 0, 0) В третьем конструкторе цвет указывается в виде строки в форматах "#RGB" , "#RRGGBB" , "#AARRGGBB" (здесь AA обозначает степень прозрачности цвета), "#RRRGGGBBB" , "#RRRRGGGGBBBB" , "Название цвета" или "transparent" (для прозрачного цвета): red = QtGui.QColor("#f00") darkBlue = QtGui.QColor("#000080") semiTransparentDarkBlue = QtGui.QColor("#7F000080") white = QtGui.QColor("white") Получить список всех поддерживаемых названий цветов позволяет статический метод colorNames() . Проверить правильность строки с названием цвета можно с помощью стати- ческого метода isValidColor(<Строка>) , который возвращает значение True , если строка является правильным наименованием цвета, и False — в противном случае: print(QtGui.QColor.colorNames()) # ['aliceblue', 'antiquewhite', ...] print(QtGui.QColor.isValidColor("lightcyan")) # True В четвертом конструкторе указываются следующие атрибуты из класса QtCore.Qt : white , black , red , darkRed , green , darkGreen , blue , darkBlue , cyan , darkCyan , magenta , darkMagenta , yellow , darkYellow , gray , darkGray , lightGray , color0 , color1 или transparent (прозрачный цвет). Атрибуты color0 (прозрачный цвет) и color1 (непрозрачный цвет) используются в двухцветных изображениях: black = QtCore.Qt.black В пятом конструкторе указывается целочисленное значение цвета, а шестой конструктор создает новый объект на основе указанного в параметре. Задать или получить значения в цветовой модели RGB (Red, Green, Blue — красный, зеле- ный, синий) позволяют следующие методы: setNamedColor(<Строка>) — задает название цвета в виде строки в форматах "#RGB" , "#RRGGBB" , "#AARRGGBB" , "#RRRGGGBBB" , "#RRRRGGGGBBBB" , "Название цвета" или "transparent" (для прозрачного цвета); Глава 24. Работа с графикой 557 name() — возвращает строковое представление цвета в формате "#RRGGBB" ; name(<Формат>) — возвращает строковое представление цвета в заданном формате. В качестве формата указывается один из атрибутов класса QColor : HexRgb ( 0 , формат "#RRGGBB" ) или HexArgb ( 1 , формат "#AARRGGBB" ); setRgb(<Красный>, <Зеленый>, <Синий>[, alpha=255]) — задает целочисленные значе- ния красной, зеленой и синей составляющих цвета модели RGB. В качестве параметров указываются числа от 0 до 255 . Необязательный параметр alpha задает степень прозрач- ности цвета: значение 0 соответствует прозрачному цвету, а значение 255 — полностью непрозрачному; setRgb(<Число>) и setRgba(<Число>) — задают целочисленное значение цвета, второй метод — со степенью прозрачности; setRed(<Красный>) , setGreen(<Зеленый>) , setBlue(<Синий>) и setAlpha(<Прозрачность>) — задают значения отдельных составляющих цвета. В качестве параметров указываются числа от 0 до 255 ; fromRgb(<Красный>, <Зеленый>, <Синий>[, alpha=255]) — возвращает экземпляр класса QColor с указанными значениями. В качестве параметров указываются числа от 0 до 255 : white = QtGui.QColor.fromRgb(255, 255, 255, 255) Метод является статическим; fromRgb(<Число>) и fromRgba(<Число>) — возвращают экземпляр класса QColor со значе- ниями, соответствующими целым числам, которые указаны в параметрах: white = QtGui.QColor.fromRgba(4294967295) Метод является статическим; getRgb() — возвращает кортеж из четырех целочисленных значений (<Красный>, <Зеле- ный>, <Синий>, <Прозрачность>) ; red() , green() , blue() и alpha() — возвращают целочисленные значения отдельных со- ставляющих цвета; rgb() и rgba() — возвращают целочисленное значение цвета; setRgbF(<Красный>, <Зеленый>, <Синий>[, alpha=1.0]) — задает значения красной, зе- леной и синей составляющих цвета модели RGB. В качестве параметров указываются вещественные числа от 0.0 до 1.0 . Необязательный параметр alpha задает степень про- зрачности цвета: значение 0.0 соответствует прозрачному цвету, а значение 1.0 — пол- ностью непрозрачному; setRedF(<Красный>) , setGreenF(<Зеленый>) , setBlueF(<Синий>) и setAlphaF(<Прозрачность>) — задают значения отдельных составляющих цвета. В качестве параметров указываются вещественные числа от 0.0 до 1.0 ; fromRgbF(<Красный>, <Зеленый>, <Синий>[, alpha=1.0]) — возвращает экземпляр клас- са QColor с указанными значениями. В качестве параметров указываются вещественные числа от 0.0 до 1.0 : white = QtGui.QColor.fromRgbF(1.0, 1.0, 1.0, 1.0) Метод является статическим; getRgbF() — возвращает кортеж из четырех вещественных значений (<Красный>, <Зеле- ный>, <Синий>, <Прозрачность>) ; 558 Часть II. Библиотека PyQt 5 redF() , greenF() , blueF() и alphaF() — возвращают вещественные значения отдельных составляющих цвета; lighter([factor=150]) — если параметр имеет значение больше 100 , то возвращает новый объект с более светлым цветом, а если меньше 100 — то с более темным; darker([factor=200]) — если параметр имеет значение больше 100 , то возвращает новый объект с более темным цветом, а если меньше 100 — то с более светлым. Задать или получить значения в цветовой модели CMYK (Cyan, Magenta, Yellow, Key — голубой, пурпурный, желтый, «ключевой», он же черный) позволяют следующие методы: setCmyk(<Голубой>, <Пурпурный>, <Желтый>, <Черный>[, alpha=255]) — задает цело- численные значения составляющих цвета модели CMYK. В качестве параметров указы- ваются числа от 0 до 255 . Необязательный параметр alpha задает степень прозрачности цвета: значение 0 соответствует прозрачному цвету, а значение 255 — полностью непро- зрачному; fromCmyk(<Голубой>, <Пурпурный>, <Желтый>, <Черный>[, alpha=255]) — возвращает экземпляр класса QColor с указанными значениями. В качестве параметров указываются числа от 0 до 255 : white = QtGui.QColor.fromCmyk(0, 0, 0, 0, 255) Метод является статическим; getCmyk() — возвращает кортеж из пяти целочисленных значений (<Голубой>, <Пурпур- ный>, <Желтый>, <Черный>, <Прозрачность>) ; cyan() , magenta() , yellow() , black() и alpha() — возвращают целочисленные значения отдельных составляющих цвета; setCmykF(<Голубой>, <Пурпурный>, <Желтый>, <Черный>[, alpha=1.0]) — задает значе- ния составляющих цвета модели CMYK. В качестве параметров указываются вещест- венные числа от 0.0 до 1.0 . Необязательный параметр alpha задает степень прозрачно- сти цвета: значение 0.0 соответствует прозрачному цвету, а значение 1.0 — полностью непрозрачному; fromCmykF(<Голубой>, <Пурпурный>, <Желтый>, <Черный>[, alpha=1.0]) — возвращает экземпляр класса QColor с указанными значениями. В качестве параметров указываются вещественные числа от 0.0 до 1.0 : white = QtGui.QColor.fromCmykF(0.0, 0.0, 0.0, 0.0, 1.0) Метод является статическим; getCmykF() — возвращает кортеж из пяти вещественных значений (<Голубой>, <Пурпур- ный>, <Желтый>, <Черный>, <Прозрачность>) ; cyanF() , magentaF() , yellowF() , blackF() и alphaF() — возвращают вещественные зна- чения отдельных составляющих цвета. Задать или получить значения в цветовой модели HSV (Hue, Saturation, Value — оттенок, насыщенность, значение, она же яркость) позволяют следующие методы: setHsv(<Оттенок>, <Насыщенность>, <Значение>[, alpha=255]) — задает целочислен- ные значения составляющих цвета модели HSV. В первом параметре указывается число от 0 до 359 , а в остальных параметрах — числа от 0 до 255 ; Глава 24. Работа с графикой 559 fromHsv(<Оттенок>, <Насыщенность>, <Значение>[, alpha=255]) — возвращает экземп- ляр класса QColor с указанными значениями: white = QtGui.QColor.fromHsv(0, 0, 255, 255) Метод является статическим; getHsv() — возвращает кортеж из четырех целочисленных значений (<Оттенок>, <Насы- щенность>, <Значение>, <Прозрачность>) ; hsvHue() , hsvSaturation() , value() и alpha() — возвращают целочисленные значения отдельных составляющих цвета; setHsvF(<Оттенок>, <Насыщенность>, <Значение>[, alpha=1.0]) — задает значения со- ставляющих цвета модели HSV. В качестве параметров указываются вещественные чис- ла от 0.0 до 1.0 ; fromHsvF(<Оттенок>, <Насыщенность>, <Значение>[, alpha=1.0]) — возвращает экземп- ляр класса QColor с указанными значениями. В качестве параметров указываются веще- ственные числа от 0.0 до 1.0 : white = QtGui.QColor.fromHsvF(0.0, 0.0, 1.0, 1.0) Метод является статическим; getHsvF() — возвращает кортеж из четырех вещественных значений (<Оттенок>, <Насы- щенность>, <Значение>, <Прозрачность>) ; hsvHueF() , hsvSaturationF() , valueF() и alphaF() — возвращают вещественные значе- ния отдельных составляющих цвета. Цветовая модель HSL (Hue, Saturation, Lightness — оттенок, насыщенность, яркость) отли- чается от модели HSV только последней составляющей. Описание этой модели и полный перечень методов для установки и получения значений вы найдете в соответствующей документации. Для получения типа используемой модели и преобразования между моделями предназначе- ны следующие методы: spec() — позволяет узнать тип используемой модели. Возвращает значение одного из следующих атрибутов, определенных в классе QColor : Invalid ( 0 ), Rgb ( 1 ), Hsv ( 2 ), Cmyk ( 3 ) или Hsl ( 4 ); convertTo(<Тип модели>) — преобразует тип модели. В качестве параметра указываются атрибуты, которые приведены в описании метода spec() . Метод возвращает новый объ- ект. Пример преобразования: whiteHSV = QtGui.QColor.fromHsv(0, 0, 255) whiteRGB = whiteHSV.convertTo(QtGui.QColor.Rgb) Вместо метода convertTo() удобнее воспользоваться методами toRgb() , toCmyk() , toHsv() или toHsl() , которые возвращают новый объект: whiteHSV = QtGui.QColor.fromHsv(0, 0, 255) whiteRGB = whiteHSV.toRgb() 24.1.2. Класс QPen: перо Класс QPen описывает виртуальное перо, с помощью которого производится рисование то- чек, линий и контуров фигур. Форматы конструктора класса: 560 Часть II. Библиотека PyQt 5 <Объект> = QPen() <Объект> = QPen( <Объект> = QPen(<Стиль>) <Объект> = QPen( <Объект> = QPen( Первый конструктор создает перо черного цвета с настройками по умолчанию. Второй кон- структор задает только цвет пера с помощью экземпляра класса QColor . Третий конструктор позволяет указать стиль линии — в качестве значения указываются следующие атрибуты класса QtCore.Qt : NoPen — 0 — линия не выводится; SolidLine — 1 — сплошная линия; DashLine — 2 — штриховая линия; DotLine — 3 — точечная линия; DashDotLine — 4 — штрих и точка, штрих и точка и т. д.; DashDotDotLine — 5 — штрих и две точки, штрих и две точки и т. д.; CustomDashLine — 6 — пользовательский стиль. Четвертый конструктор позволяет задать все характеристики пера за один раз: в первом параметре указывается экземпляр класса QBrush или QColor , ширина линии передается во втором параметре, стиль линии — в необязательном параметре style , а необязательный параметр cap задает стиль концов линии, где в качестве значения указываются следующие атрибуты класса QtCore.Qt : FlatCap — 0 — квадратный конец линии. Длина линии не превышает указанных гранич- ных точек; SquareCap — 16 — квадратный конец линии. Длина линии увеличивается с обоих концов на половину ширины линии; RoundCap — 32 — скругленные концы. Длина линии увеличивается с обоих концов на половину ширины линии. Необязательный параметр join задает стиль перехода одной линии в другую — в качестве значения указываются следующие атрибуты класса QtCore.Qt : MiterJoin — 0 — линии соединяются под острым углом; BevelJoin — 64 — пространство между концами линий заполняется цветом линии; RoundJoin — 128 — скругленные углы; SvgMiterJoin — 256 — линии соединяются под острым углом, как определено в специ- фикации SVG 1.2 Tiny. Последний конструктор создает новый объект на основе указанного в параметре. Класс QPen поддерживает следующие методы (здесь приведены только основные — полный их список можно найти на странице https://doc.qt.io/qt-5/qpen.html): setColor( — задает цвет линии; setBrush( — задает кисть; setWidth(<Ширина типа int>) и setWidthF(<Ширина типа float>) — задают ширину ли- нии целым числом или числом с плавающей точкой соответственно; Глава 24. Работа с графикой 561 setStyle(<Стиль>) — задает стиль линии (см. значения параметра style в четвертом формате конструктора класса QPen ); setCapStyle(<Стиль>) — задает стиль концов линии (см. значения параметра cap в чет- вертом формате конструктора класса QPen ); setJoinStyle(<Стиль>) — задает стиль перехода одной линии в другую (см. значения параметра join в четвертом формате конструктора класса QPen ). 24.1.3. Класс QBrush: кисть Класс QBrush описывает виртуальную кисть, с помощью которой производится заливка фи- гур. Форматы конструктора класса: <Объект> = QBrush() <Объект> = QBrush( <Объект> = QBrush(<Атрибут цвета>[, style=SolidPattern]) <Объект> = QBrush(<Стиль кисти>) <Объект> = QBrush( <Объект> = QBrush( <Объект> = QBrush(<Атрибут цвета>, <Объект> = QBrush( <Объект> = QBrush( <Объект> = QBrush( Параметр задает цвет кисти в виде экземпляра класса QColor , а параметр <Атрибут цвета> — в виде атрибута класса QtCore.Qt (например, black ). В параметрах <Стиль кисти> и style указываются атрибуты класса QtCore.Qt , задающие стиль кисти: NoBrush , SolidPattern , Dense1Pattern , Dense2Pattern , Dense3Pattern , Dense4Pattern , Dense5Pattern , Dense6Pattern , Dense7Pattern , CrossPattern и др. С по- мощью этого параметра можно сделать цвет сплошным ( SolidPattern ) или имеющим тек- стуру (например, атрибут CrossPattern задает текстуру в виде сетки). Параметр позволяет установить градиентную заливку. В качестве значения указываются экземпляры классов, порожденных от класса QGradient : QLinearGradient (линейный градиент), QConicalGradient (конический градиент) или QRadialGradient (ра- диальный градиент). За подробной информацией по этим классам обращайтесь к соответст- вующей документации. Параметры и предназначены для установки изображения в качестве тек- стуры, которой будут заливаться рисуемые фигуры. Класс QBrush поддерживает следующие полезные для нас методы (полный их список приве- ден на странице https://doc.qt.io/qt-5/qbrush.html): setColor( и setColor(<Атрибут цвета>) — задают цвет кисти; setStyle(<Стиль>) — задает стиль кисти (см. значения параметра style в конструкторе класса QBrush ); setTexture( — устанавливает растровое изображение в качестве текстуры. Можно указать экземпляр класса QPixmap или QBitmap ; setTextureImage( — устанавливает изображение в качестве текстуры. 562 Часть II. Библиотека PyQt 5 24.1.4. Класс QLine: линия Класс QLine из модуля QtCore описывает координаты линии. Форматы конструктора класса: <Объект> = QLine() <Объект> = QLine( <Объект> = QLine( Первый конструктор создает линию, имеющую неустановленные местоположение и разме- ры. Во втором и третьем конструкторах указываются координаты начальной и конечной точек в виде экземпляров класса QPoint или целочисленных значений через запятую. Класс QLine поддерживает следующие основные методы (полный их список приведен на странице https://doc.qt.io/qt-5/qline.html): isNull() — возвращает значение True , если начальная или конечная точка не установле- ны, и False — в противном случае; setPoints( — задает координаты начальной и конечной точек в виде экземпляров класса QPoint ; setLine( — задает координаты начальной и конечной точек в виде целочисленных значений через запятую; setP1( — задает координаты начальной точки; setP2( — задает координаты конечной точки; p1() — возвращает координаты (экземпляр класса QPoint ) начальной точки; p2() — возвращает координаты (экземпляр класса QPoint ) конечной точки; center() — возвращает координаты (экземпляр класса QPoint ) центральной точки. Под- держка этого метода появилась в PyQt 5.8; x1() , y1() , x2() и y2() — возвращают значения отдельных составляющих координат начальной и конечной точек в виде целых чисел; dx() — возвращает горизонтальную составляющую вектора линии; dy() — возвращает вертикальную составляющую вектора линии. П РИМЕЧАНИЕ Класс QLine предназначен для работы с целыми числами. Чтобы работать с веществен- ными числами, необходимо использовать класс QLineF. 24.1.5. Класс QPolygon: многоугольник Класс QPolygon описывает координаты вершин многоугольника. Форматы конструктора класса: <Объект> = QPolygon() <Объект> = QPolygon(<Список с экземплярами класса QPoint>) <Объект> = QPolygon( <Объект> = QPolygon(<Количество вершин>) <Объект> = QPolygon( Первый конструктор создает пустой объект. Заполнить объект координатами вершин мож- но с помощью оператора << . Вот пример добавления координат вершин треугольника: Глава 24. Работа с графикой 563 polygon = QtGui.QPolygon() polygon << QtCore.QPoint(20, 50) << QtCore.QPoint(280, 50) polygon << QtCore.QPoint(150, 280) Во втором конструкторе указывается список с экземплярами класса QPoint , которые задают координаты отдельных вершин: polygon = QtGui.QPolygon([QtCore.QPoint(20, 50), QtCore.QPoint(280, 50), QtCore.QPoint(150, 280)]) Третий конструктор создает многоугольник на основе экземпляра класса QRect . Если пара- метр closed имеет значение False , то будут созданы четыре вершины, а если значение True — то пять вершин. В четвертом конструкторе можно указать количество вершин, а затем задать координаты путем присваивания значения по индексу: polygon = QtGui.QPolygon(3) polygon[0] = QtCore.QPoint(20, 50) polygon[1] = QtCore.QPoint(280, 50) polygon[2] = QtCore.QPoint(150, 280) Пятый конструктор создает новый объект на основе другого объекта. Класс QPolygon поддерживает следующие методы (здесь приведены только основные — полный их список можно найти на странице https://doc.qt.io/qt-5/qpolygon.html): setPoints() — устанавливает координаты вершин. Ранее установленные значения уда- ляются. Форматы метода: setPoints(<Список с координатами>) setPoints( Пример указания значений: polygon = QtGui.QPolygon() polygon.setPoints([20,50, 280,50, 150,280]) prepend( — добавляет новую вершину в начало объекта; append( — добавляет новую вершину в конец объекта. Добавить вершину можно также с помощью операторов << и += ; insert(<Индекс>, — добавляет новую вершину в указанную позицию; setPoint() — задает координаты для вершины с указанным индексом. Форматы метода: setPoint(<Индекс>, Можно также задать координаты путем присваивания значения по индексу: polygon = QtGui.QPolygon(3) polygon.setPoint(0, QtCore.QPoint(20, 50)) polygon.setPoint(1, 280, 50) polygon[2] = QtCore.QPoint(150, 280) point(<Индекс>) — возвращает экземпляр класса QPoint с координатами вершины, ин- декс которой указан в параметре. Получить значение можно также с помощью операции доступа по индексу: 564 Часть II. Библиотека PyQt 5 polygon = QtGui.QPolygon([20,50, 280,50, 150,280]) print(polygon.point(0)) # PyQt5.QtCore.QPoint(20, 50) print(polygon[1]) # PyQt5.QtCore.QPoint(280, 50) remove(<Индекс>[, <Количество>]) — удаляет указанное количество вершин, начиная с индекса <Индекс> . Если второй параметр не указан, удаляется одна вершина. Удалить вершину можно также с помощью оператора del по индексу или срезу; clear() — удаляет все вершины; size() и count([ — возвращают количество вершин. Если в методе count() указан параметр, возвращается только количество вершин с указанными координатами. Получить количество вершин можно также с помощью функции len() ; isEmpty() — возвращает значение True , если объект пустой (многоугольник не содержит ни одной вершины), и False — в противном случае; boundingRect() — возвращает экземпляр класса QRect с координатами и размерами пря- моугольной области, в которую вписан многоугольник. П РИМЕЧАНИЕ Класс QPolygon предназначен для работы с целыми числами. Чтобы работать с вещест- венными числами, необходимо использовать класс QPolygonF. 24.1.6. Класс QFont: шрифт Класс QFont описывает характеристики шрифта. Форматы конструктора класса: <Объект> = QFont() <Объект> = QFont(<Название шрифта>[, pointSize=-1][, weight=-1][, italic=False]) <Объект> = QFont( Первый конструктор создает объект шрифта с настройками, используемыми приложением по умолчанию. Установить шрифт приложения по умолчанию позволяет статический метод setFont() класса QApplication Второй конструктор позволяет указать основные характеристики шрифта. В первом пара- метре указывается название шрифта или семейства в виде строки. Необязательный пара- метр pointSize задает размер шрифта. В параметре weight можно указать степень жирности шрифта: число от 0 до 99 или значение атрибута Light ( 25 ), Normal ( 50 ), DemiBold ( 63 ), Bold ( 75 ) или Black ( 87 ) класса QFont . Если в параметре italic указано значение True , шрифт будет курсивным. Третий конструктор создает новый объект на основе другого объекта. Класс QFont поддерживает следующие методы (здесь приведены только основные — пол- ный их список можно найти по адресу https://doc.qt.io/qt-5/qfont.html): setFamily(<Название шрифта>) — задает название шрифта или семейства шрифтов; family() — возвращает название шрифта; setPointSize(<Размер типа int>) и setPointSizeF(<Размер типа float>) — задают размер шрифта в пунктах; pointSize() — возвращает размер шрифта в пунктах в виде целого числа или значение -1 , если размер шрифта был установлен в пикселах; Глава 24. Работа с графикой 565 pointSizeF() — возвращает размер шрифта в пунктах в виде вещественного числа или значение -1 , если размер шрифта был установлен в пикселах; setPixelSize(<Размер>) — задает размер шрифта в пикселах; pixelSize() — возвращает размер шрифта в пикселах или -1 , если размер шрифта был установлен в пунктах; setWeight(<Жирность>) — задает степень жирности шрифта (см. описание параметра weight во втором конструкторе класса QFont ); weight() — возвращает степень жирности шрифта; setBold(<Флаг>) — если в качестве параметра указано значение True , то жирность шрифта устанавливается равной значению атрибута Bold , а если False — то равной зна- чению атрибута Normal класса QFont ; bold() — возвращает значение True , если степень жирности шрифта больше значения атрибута Normal класса QFont , и False — в противном случае; setItalic(<Флаг>) — если в качестве параметра указано значение True , шрифт будет курсивным, а если False — обычного начертания; italic() — возвращает значение True , если шрифт курсивный, и False — в противном случае; setUnderline(<Флаг>) — если в качестве параметра указано значение True , текст будет подчеркнутым, а если False — не подчеркнутым; underline() — возвращает значение True , если текст подчеркнут, и False — в против- ном случае; setOverline(<Флаг>) — если в качестве параметра указано значение True , над текстом будет выводиться черта; overline() — возвращает значение True , если над текстом будет выводиться черта, и False — в противном случае; setStrikeOut(<Флаг>) — если в качестве параметра указано значение True , текст будет зачеркнутым; strikeOut() — возвращает значение True , если текст будет зачеркнутым, и False — в противном случае. Получить список всех доступных шрифтов позволяет метод families() класса QFontDatabase . Метод возвращает список строк. Отметим, что перед его вызовом следует создать экземпляр класса QApplication , в противном случае мы получим ошибку испол- нения: from PyQt5 import QtGui, QtWidgets app = QtWidgets.QApplication(list()) fdb = QtGui.QFontDatabase() print(fdb.families()) Чтобы получить список доступных стилей для указанного шрифта, следует воспользоваться методом styles(<Название шрифта>) класса QFontDatabase : print(fdb.styles("Arial")) # ['Обычный', 'Полужирный', 'Полужирный Курсив', 'Курсив'] Получить допустимые размеры для указанного стиля можно с помощью метода smoothSizes(<Название шрифта>, <Стиль>) класса QFontDatabase : 566 Часть II. Библиотека PyQt 5 print(fdb.smoothSizes("Arial", "Обычный")) # [6, 7, 8, 9, 10, 11, 12, 14, 16, 18, 20, 22, 24, 26, 28, 36, 48, 72] Очень часто необходимо произвести выравнивание выводимого текста внутри некоторой области. Чтобы это сделать, нужно знать размеры области, в которую вписан текст. Полу- чить эти значения позволяют следующие методы класса QFontMetrics : width(<Текст>[, length=-1]) — возвращает расстояние от начала текста <Текст> до по- зиции, в которой должен начаться другой текст. Параметр length позволяет ограничить количество символов; height() — возвращает высоту шрифта; boundingRect(<Текст>) — возвращает экземпляр класса QRect с координатами и разме- рами прямоугольной области, в которую вписан текст. Вот пример получения размеров области: font = QtGui.QFont("Tahoma", 16) fm = QtGui.QFontMetrics(font) print(fm.width("Строка")) # 67 print(fm.height()) # 25 print(fm.boundingRect("Строка")) # PyQt5.QtCore.QRect(0, -21, 65, 25) Обратите внимание, что значения, возвращаемые методами width() и QRect.width() , разли- чаются. П РИМЕЧАНИЕ Класс QFontMetrics предназначен для работы с целыми числами. Чтобы работать с веще- ственными числами, необходимо использовать класс QFontMetricsF. 24.2. Класс QPainter Класс QPainter содержит все необходимые средства, позволяющие выполнять рисование геометрических фигур и вывод текста на поверхности, которая реализуется классом QPaintDevice . Класс QPaintDevice наследуют классы QWidget , QPicture , QPixmap , QImage , QPagedPaintDevice и некоторые другие. Таким образом, мы можем рисовать на поверхности любого компонента, на изображении или на печатаемой странице. Форматы конструктора класса: <Объект> = QPainter() <Объект> = QPainter( Первый конструктор создает объект, который не подключен ни к одному устройству. Чтобы подключиться к устройству и захватить контекст рисования, необходимо вызвать метод begin( и передать ему ссылку на экземпляр класса, являющегося наследни- ком класса QPaintDevice . Метод возвращает значение True , если контекст успешно захва- чен, и False — в противном случае. В один момент времени только один объект может ри- совать на устройстве, поэтому после окончания рисования необходимо освободить контекст рисования с помощью метода end() . С учетом сказанного код, позволяющий рисовать на компоненте, будет выглядеть так: def paintEvent(self, e): # Компонент, на котором выполняется рисование, передается в параметре self painter = QtGui.QPainter() painter.begin(self) Глава 24. Работа с графикой 567 # Здесь производим рисование на компоненте painter.end() Второй конструктор принимает ссылку на экземпляр класса, являющегося наследником класса QPaintDevice , подключается к этому устройству и сразу захватывает контекст рисо- вания. Контекст рисования автоматически освобождается внутри деструктора класса QPainter при уничтожении объекта. Так как объект автоматически уничтожается при выхо- де из метода paintEvent() , то метод end() можно и не вызывать. Вот пример рисования на компоненте: def paintEvent(self, e): painter = QtGui.QPainter(self) # Здесь производим рисование на компоненте Проверить успешность захвата контекста рисования можно с помощью метода isActive() : он возвращает значение True , если контекст захвачен, и False — в противном случае. 24.2.1. Рисование линий и фигур После захвата контекста рисования следует установить перо и кисть. С помощью пера про- изводится рисование точек, линий и контуров фигур, а с помощью кисти — заполнение фона фигур. Установить перо позволяет метод setPen() класса QPainter . Форматы метода: setPen( Для установки кисти предназначен метод setBrush() . Форматы метода: setBrush( Устанавливать перо или кисть необходимо перед каждой операцией рисования, требующей изменения цвета или стиля. Если перо или кисть не установлены, будут использоваться объекты с настройками по умолчанию. После установки пера и кисти можно приступать к рисованию точек, линий, фигур, текста и др. Для рисования точек, линий и фигур класс QPainter предоставляет следующие наиболее часто употребляемые методы (полный их список приведен на странице https://doc.qt.io/ qt-5/qpainter.html): drawPoint() — рисует точку. Форматы метода: drawPoint( drawPoints() — рисует несколько точек. Форматы метода: drawPoints( drawLine() — рисует линию. Форматы метода: drawLine( 568 Часть II. Библиотека PyQt 5 drawLine( drawLines() — рисует несколько отдельных линий. Форматы метода: drawLines( drawPolyline() — рисует несколько линий, которые соединяют указанные точки. Пер- вая и последняя точки не соединяются. Форматы метода: drawPolyline( drawRect() — рисует прямоугольник с границей и заливкой. Чтобы убрать границу, сле- дует использовать перо со стилем NoPen , а чтобы убрать заливку — кисть со стилем NoBrush . Форматы метода: drawRect( fillRect() — рисует прямоугольник с заливкой без границы. Форматы метода: fillRect( <Заливка> может быть задана экземплярами классов , в виде стиля кисти или атрибута цвета; drawRoundedRect() — рисует прямоугольник с границей, заливкой и скругленными краями. Форматы метода: drawRoundedRect( <Скругление по горизонтали>, <Скругление по вертикали>[, mode = Qt::AbsoluteSize]) drawRoundedRect( <Скругление по вертикали>[, mode = Qt::AbsoluteSize]) drawRoundedRect( <Скругление по вертикали>[, mode = Qt::AbsoluteSize]) Параметры <Скругление по горизонтали> и <Скругление по вертикали> задают радиусы скругления углов по горизонтали и вертикали. Необязательный параметр mode указыва- ет, в каких единицах измеряются радиусы скругления углов, и задается одним из сле- дующих атрибутов класса QtCore.Qt : • AbsoluteSize — 0 — радиусы указываются в пикселах; • RelativeSize — 1 — радиусы указываются в процентах от соответствующего разме- ра рисуемого прямоугольника; Глава 24. Работа с графикой 569 drawPolygon() — рисует многоугольник с границей и заливкой. Форматы метода: drawPolygon( Необязательный параметр fillRule задает алгоритм определения, находится ли какая- либо точка внутри нарисованного прямоугольника или вне его. В качестве его значения указывается атрибут OddEvenFill ( 0 ) или WindingFill ( 1 ) класса QtCore.Qt ; drawEllipse() — рисует эллипс с границей и заливкой. Форматы метода: drawEllipse( |