ВУЗ: Не указан
Категория: Не указан
Дисциплина: Не указана
Добавлен: 05.12.2023
Просмотров: 819
Скачиваний: 3
ВНИМАНИЕ! Если данный файл нарушает Ваши авторские права, то обязательно сообщите нам.
СОДЕРЖАНИЕ
366
Часть II. Библиотека PyQt 5
setMinimumWidth(<Ширина>)
и setMinimumHeight(<Высота>)
— задают минимальную ши- рину и высоту соответственно;
setMaximumSize(<Ширина>, <Высота>)
— задает максимальный размер окна. В качестве параметра можно также указать экземпляр класса
QSize
: window.setMaximumSize(100, 70) window.setMaximumSize(QtCore.QSize(100, 70))
setMaximumWidth(<Ширина>)
и setMaximumHeight(<Высота>)
— задают максимальную ши- рину и высоту соответственно;
setBaseSize(<Ширина>, <Высота>)
— задает базовые размеры. В качестве параметра можно также указать экземпляр класса
QSize
: window.setBaseSize(500, 500) window.setBaseSize(QtCore.QSize(500, 500))
adjustSize()
— подгоняет размеры компонента под содержимое. При этом учитываются рекомендуемые размеры, возвращаемые методом sizeHint()
Получить размеры позволяют следующие методы:
width()
и height()
— возвращают текущую ширину и высоту соответственно: window.resize(50, 70) print(window.width(), window.height()) # 50 70
size()
— возвращает экземпляр класса
QSize
, содержащий текущие размеры: window.resize(50, 70) print(window.size().width(), window.size().height()) # 50 70
minimumSize()
— возвращает экземпляр класса
QSize
, содержащий минимальные размеры;
minimumWidth()
и minimumHeight()
— возвращают минимальную ширину и высоту соот- ветственно;
maximumSize()
— возвращает экземпляр класса
QSize
, содержащий максимальные размеры;
maximumWidth()
и maximumHeight()
— возвращают максимальную ширину и высоту соот- ветственно;
baseSize()
— возвращает экземпляр класса
QSize
, содержащий базовые размеры;
sizeHint()
— возвращает экземпляр класса
QSize
, содержащий рекомендуемые размеры компонента. Если таковые являются отрицательными, считается, что нет рекомендуемо- го размера;
minimumSizeHint()
— возвращает экземпляр класса
QSize
, содержащий рекомендуемый минимальный размер компонента. Если возвращаемые размеры являются отрицатель- ными, то считается, что нет рекомендуемого минимального размера;
rect()
— возвращает экземпляр класса
QRect
, содержащий координаты и размеры пря- моугольника, в который вписан компонент: window.setGeometry(QtCore.QRect(100, 100, 100, 70)) rect = window.rect() print(rect.left(), rect.top()) # 0 0 print(rect.width(), rect.height()) # 100 70
geometry()
— возвращает экземпляр класса
QRect
, содержащий координаты относитель- но родительского компонента:
Глава 18. Управление окном приложения
367 window.setGeometry(QtCore.QRect(100, 100, 100, 70)) rect = window.geometry() print(rect.left(), rect.top()) # 100 100 print(rect.width(), rect.height()) # 100 70
При изменении и получении размеров окна следует учитывать, что:
размеры не включают высоту заголовка окна и ширину границ;
размер компонентов может изменяться в зависимости от настроек стиля. Например, на разных компьютерах может быть задан шрифт разного наименования и размера, поэтому от указания фиксированных размеров лучше отказаться;
размер окна может изменяться в промежутке между получением значения и действиями, выполняющими обработку этих значений в программе. Например, сразу после получе- ния размера пользователь может изменить размеры окна с помощью мыши.
Чтобы получить размеры окна, включающие высоту заголовка и ширину границ, следует воспользоваться методом frameSize()
, который возвращает экземпляр класса
QSize
. Обра- тите внимание, что полные размеры окна доступны только после его отображения, — до этого момента они совпадают с размерами клиентской области окна, без учета высоты заго- ловка и ширины границ. Пример получения полного размера окна: window.resize(200, 70) # Задаем размеры
# ... window.show() # Отображаем окно print(window.width(), window.height()) # 200 70 print(window.frameSize().width(), window.frameSize().height()) # 208 104
Чтобы получить координаты окна с учетом высоты заголовка и ширины границ, следует воспользоваться методом frameGeometry()
. И в этом случае полные размеры окна доступны только после отображения окна. Метод возвращает экземпляр класса
QRect
: window.setGeometry(100, 100, 200, 70)
# ... window.show() # Отображаем окно rect = window.geometry() print(rect.left(), rect.top()) # 100 100 print(rect.width(), rect.height()) # 200 70 rect = window.frameGeometry() print(rect.left(), rect.top()) # 96 70 print(rect.width(), rect.height()) # 208 104 18.4. Местоположение окна на экране и управление им
Задать местоположение окна на экране монитора позволяют следующие методы:
1 ... 31 32 33 34 35 36 37 38 ... 83
move(
— задает положение компонента относительно родителя с учетом высо- ты заголовка и ширины границ. В качестве параметра можно также указать экземпляр класса
QPoint из модуля
QtCore
Пример вывода окна в левом верхнем углу экрана: window.move(0, 0) window.move(QtCore.QPoint(0, 0))
368
Часть II. Библиотека PyQt 5
setGeometry(
— изменяет одновременно положение компонента и его текущие размеры. Первые два параметра задают координаты левого верхнего угла относительно родительского компонента, а третий и четвертый парамет- ры — ширину и высоту. Обратите внимание, что метод не учитывает высоту заголовка и ширину границ, поэтому, если указать координаты
(0, 0)
, заголовок окна и левая гра- ница окажутся за пределами экрана. В качестве параметра можно также задать экземп- ляр класса
QRect из модуля
QtCore
: window.setGeometry(100, 100, 100, 70) window.setGeometry(QtCore.QRect(100, 100, 100, 70))
В
НИМАНИЕ
!
Начало координат расположено в левом верхнем углу. Положительная ось X направлена вправо, а положительная ось Y — вниз.
Получить позицию окна позволяют следующие методы:
x()
и y()
— возвращают координаты левого верхнего угла окна относительно родителя по осям
X
и
Y
соответственно. Методы учитывают высоту заголовка и ширину границ: window.move(10, 10) print(window.x(), window.y()) # 10 10
pos()
— возвращает экземпляр класса
QPoint
, содержащий координаты левого верхнего угла окна относительно родителя. Метод учитывает высоту заголовка и ширину границ: window.move(10, 10) print(window.pos().x(), window.pos().y()) # 10 10
geometry()
— возвращает экземпляр класса
QRect
, содержащий координаты относитель- но родительского компонента. Метод не учитывает высоту заголовка и ширину границ: window.resize(300, 100) window.move(10, 10) rect = window.geometry() print(rect.left(), rect.top()) # 14 40 print(rect.width(), rect.height()) # 300 100
frameGeometry()
— возвращает экземпляр класса
QRect
, содержащий координаты с уче- том высоты заголовка и ширины границ. Полные размеры окна доступны только после отображения окна: window.resize(300, 100) window.move(10, 10) rect = window.frameGeometry() print(rect.left(), rect.top()) # 10 10 print(rect.width(), rect.height()) # 308 134
Для отображения окна по центру экрана, у правой или нижней его границы необходимо знать размеры экрана. Для получения размеров экрана вначале следует вызвать статический метод
QApplication.desktop()
, который возвращает ссылку на компонент рабочего стола, представленный экземпляром класса
QDesktopWidget из модуля
QtWidgets
. Получить разме- ры экрана позволяют следующие методы этого класса:
width()
— возвращает ширину всего экрана в пикселах;
height()
— возвращает высоту всего экрана в пикселах.
Глава 18. Управление окном приложения
369
Примеры: desktop = QtGui.QApplication.desktop() print(desktop.width(), desktop.height()) # 1440 900
screenGeometry()
— возвращает экземпляр класса
QRect
, содержащий координаты всего экрана: desktop = QtGui.QApplication.desktop() rect = desktop.screenGeometry() print(rect.left(), rect.top()) # 0 0 print(rect.width(), rect.height()) # 1440 900
availableGeometry()
— возвращает экземпляр класса
QRect
, содержащий координаты только доступной части экрана (без размера панели задач): desktop = QtGui.QApplication.desktop() rect = desktop.availableGeometry() print(rect.left(), rect.top()) # 0 0 print(rect.width(), rect.height()) # 1440 818
Пример отображения окна приблизительно по центру экрана показан в листинге 18.2.
Листинг 18.2. Вывод окна приблизительно по центру экрана
# -*- coding: utf-8 -*- from PyQt5 import QtWidgets import sys app = QtWidgets.QApplication(sys.argv) window = QtWidgets.QWidget() window.setWindowTitle("Вывод окна по центру экрана") window.resize(300, 100) desktop = QtWidgets.QApplication.desktop() x = (desktop.width() - window.width()) // 2 y = (desktop.height() - window.height()) // 2 window.move(x, y) window.show() sys.exit(app.exec_())
В этом примере мы воспользовались методами width()
и height()
, которые не учитывают высоту заголовка и ширину границ. В большинстве случаев этого способа достаточно. Если же при выравнивании необходима точность, то для получения размеров окна можно вос- пользоваться методом frameSize()
. Однако этот метод возвращает корректные значения лишь после отображения окна. Если код выравнивания по центру расположить после вызо- ва метода show()
, окно вначале отобразится в одном месте экрана, а затем переместится в центр, что вызовет неприятное мелькание. Чтобы исключить такое мелькание, следует вначале отобразить окно за пределами экрана, а затем переместить его в центр экрана (лис- тинг 18.3).
Листинг 18.3. Вывод окна точно по центру экрана
# -*- coding: utf-8 -*- from PyQt5 import QtWidgets import sys
370
Часть II. Библиотека PyQt 5 app = QtWidgets.QApplication(sys.argv) window = QtWidgets.QWidget() window.setWindowTitle("Вывод окна по центру экрана") window.resize(300, 100) window.move(window.width() * -2, 0) window.show() desktop = QtWidgets.QApplication.desktop() x = (desktop.width() - window.frameSize().width()) // 2 y = (desktop.height() - window.frameSize().height()) // 2 window.move(x, y) sys.exit(app.exec_())
Этот способ можно также использовать для выравнивания окна по правому краю экрана.
Например, чтобы расположить окно в правом верхнем углу экрана, необходимо заменить код из предыдущего примера, выравнивающий окно по центру, следующим кодом: desktop = QtWidgets.QApplication.desktop() x = desktop.width() - window.frameSize().width() window.move(x, 0)
Если попробовать вывести окно в правом нижнем углу, может возникнуть проблема, по- скольку в операционной системе Windows в нижней части экрана обычно располагается панель задач, и окно частично окажется под ней. Здесь нам пригодится метод availableGeometry()
, позволяющий получить высоту панели задач, расположенной в ниж- ней части экрана, следующим образом: desktop = QtWidgets.QApplication.desktop() taskBarHeight = (desktop.screenGeometry().height() - desktop.availableGeometry().height())
Следует также заметить, что в некоторых операционных системах панель задач допускается прикреплять к любой стороне экрана. Кроме того, экран может быть разделен на несколько рабочих столов. Все это необходимо учитывать при размещении окна (за более подробной информацией обращайтесь к документации по классу
QDesktopWidget
).
18.5. Указание координат и размеров
В двух предыдущих разделах были упомянуты классы
QPoint
,
QSize и
QRect
. Класс
QPoint описывает координаты точки, класс
QSize
— размеры, а класс
QRect
— координаты и раз- меры прямоугольной области. Все эти классы определены в модуле
QtCore
. Рассмотрим их более подробно.
П
РИМЕЧАНИЕ
Классы QPoint, QSize и QRect предназначены для работы с целыми числами. Чтобы рабо- тать с вещественными числами, необходимо использовать классы QPointF, QSizeF и
QRectF
соответственно. Эти классы также определены в модуле QtCore.
18.5.1. Класс QPoint: координаты точки
Класс
QPoint описывает координаты точки. Для создания экземпляра класса предназначены следующие форматы конструкторов:
Глава 18. Управление окном приложения
371
<Объект> = QPoint()
<Объект> = QPoint(
<Объект> = QPoint(
Первый конструктор создает экземпляр класса с нулевыми координатами:
>>> from PyQt5 import QtCore
>>> p = QtCore.QPoint()
>>> p.x(), p.y()
(0, 0)
Второй конструктор позволяет явно указать координаты точки:
>>> p = QtCore.QPoint(10, 88)
>>> p.x(), p.y()
(10, 88)
Третий конструктор создает новый экземпляр на основе другого экземпляра:
>>> p = QtCore.QPoint(QtCore.QPoint(10, 88))
>>> p.x(), p.y()
(10, 88)
Через экземпляр класса доступны следующие методы:
x()
и y()
— возвращают координаты по осям
X
и
Y
соответственно;
setX(
и setY(
— задают координаты по осям
X
и
Y
соответственно;
isNull()
— возвращает
True
, если координаты равны нулю, и
False
— в противном слу- чае:
>>> p = QtCore.QPoint()
>>> p.isNull()
True
>>> p.setX(10); p.setY(88)
>>> p.x(), p.y()
(10, 88)
manhattanLength()
— возвращает сумму абсолютных значений координат:
>>> QtCore.QPoint(10, 88).manhattanLength()
98
Над двумя экземплярами класса
QPoint можно выполнять операции
+
,
+=
, – (минус),
-=
,
==
и
!=
. Для смены знака координат можно воспользоваться унарным оператором
-
. Кроме того, экземпляр класса
QPoint можно умножить или разделить на вещественное число (операто- рами
*
,
*=
,
/
и
/=
):
>>> p1 = QtCore.QPoint(10, 20); p2 = QtCore.QPoint(5, 9)
>>> p1 + p2, p1 — p2
(PyQt5.QtCore.QPoint(15, 29), PyQt5.QtCore.QPoint(5, 11))
>>> p1 * 2.5, p1 / 2.0
(PyQt5.QtCore.QPoint(25, 50), PyQt5.QtCore.QPoint(5, 10))
>>> -p1, p1 == p2, p1 != p2
(PyQt5.QtCore.QPoint(-10, -20), False, True)
372
Часть II. Библиотека PyQt 5 18.5.2. Класс QSize: размеры прямоугольной области
Класс
QSize описывает размеры прямоугольной области. Для создания экземпляра класса предназначены следующие форматы конструкторов:
<Объект> = QSize()
<Объект> = QSize(<Ширина>, <Высота>)
<Объект> = QSize(
Первый конструктор создает экземпляр класса с отрицательной шириной и высотой. Второй конструктор позволяет явно указать ширину и высоту. Третий конструктор создает новый экземпляр на основе другого экземпляра:
>>> from PyQt5 import QtCore
>>> s1=QtCore.QSize(); s2=QtCore.QSize(10, 55); s3=QtCore.QSize(s2)
>>> s1
PyQt5.QtCore.QSize(-1, -1)
>>> s2, s3
(PyQt5.QtCore.QSize(10, 55), PyQt5.QtCore.QSize(10, 55))
Через экземпляр класса доступны следующие методы:
width()
и height()
— возвращают ширину и высоту соответственно;
setWidth(<Ширина>)
и setHeight(<Высота>)
— задают ширину и высоту соответственно.
Примеры:
>>> s = QtCore.QSize()
>>> s.setWidth(10); s.setHeight(55)
>>> s.width(), s.height()
(10, 55)
isNull()
— возвращает
True
, если ширина и высота равны нулю, и
False
— в противном случае;
isValid()
— возвращает
True
, если ширина и высота больше или равны нулю, и
False
— в противном случае;
isEmpty()
— возвращает
True
, если один параметр (ширина или высота) меньше или равен нулю, и
False
— в противном случае;
scale()
— производит изменение размеров области в соответствии со значением пара- метра
<Тип преобразования>
. Метод изменяет текущий объект и ничего не возвращает.
Форматы метода: scale(
В параметре
<Тип преобразования>
могут быть указаны следующие атрибуты из класса
QtCore.Qt
:
•
IgnoreAspectRatio
—
0
— свободно изменяет размеры без сохранения пропорций сторон;
•
KeepAspectRatio
—
1
— производится попытка масштабирования старой области внутри новой области без нарушения пропорций;
•
KeepAspectRatioByExpanding
—
2
— производится попытка полностью заполнить но- вую область без нарушения пропорций старой области.
Глава 18. Управление окном приложения
373
Если новая ширина или высота имеет значение
0
, размеры изменяются непосредственно без сохранения пропорций, вне зависимости от значения параметра
<Тип преобразова- ния>
Примеры:
>>> s = QtCore.QSize(50, 20)
>>> s.scale(70, 60, QtCore.Qt.IgnoreAspectRatio); s
PyQt5.QtCore.QSize(70, 60)
>>> s = QtCore.QSize(50, 20)
>>> s.scale(70, 60, QtCore.Qt.KeepAspectRatio); s
PyQt5.QtCore.QSize(70, 28)
>>> s = QtCore.QSize(50, 20)
>>> s.scale(70, 60, QtCore.Qt.KeepAspectRatioByExpanding); s
PyQt5.QtCore.QSize(150, 60)
scaled()
— то же самое, что scale()
, но не изменяет сам объект, а возвращает новый экземпляр класса
QSize
, хранящий измененные размеры:
>>> s1 = QtCore.QSize(50, 20)
>>> s2 = s1.scaled(70, 60, QtCore.Qt.IgnoreAspectRatio)
>>> s1, s2
(PyQt5.QtCore.QSize(50, 20), PyQt5.QtCore.QSize(70, 60))
boundedTo(
— возвращает экземпляр класса
QSize
, который содержит мини- мальную ширину и высоту из текущих размеров и размеров, указанных в параметре:
>>> s = QtCore.QSize(50, 20)
>>> s.boundedTo(QtCore.QSize(400, 5))
PyQt5.QtCore.QSize(50, 5)
>>> s.boundedTo(QtCore.QSize(40, 50))
PyQt5.QtCore.QSize(40, 20)
expandedTo(
— возвращает экземпляр класса
QSize
, который содержит макси- мальную ширину и высоту из текущих размеров и размеров, указанных в параметре:
>>> s = QtCore.QSize(50, 20)
>>> s.expandedTo(QtCore.QSize(400, 5))
PyQt5.QtCore.QSize(400, 20)
>>> s.expandedTo(QtCore.QSize(40, 50))
PyQt5.QtCore.QSize(50, 50)
transpose()
— меняет значения местами. Метод изменяет текущий объект и ничего не возвращает:
>>> s = QtCore.QSize(50, 20)
>>> s.transpose(); s
PyQt5.QtCore.QSize(20, 50)
transposed()
— то же самое, что transpose()
, но не изменяет сам объект, а возвращает новый экземпляр класса
QSize с измененными размерами:
>>> s1 = QtCore.QSize(50, 20)
>>> s2 = s1.transposed()
>>> s1, s2
(PyQt5.QtCore.QSize(50, 20), PyQt5.QtCore.QSize(20, 50))
374
Часть II. Библиотека PyQt 5
Над двумя экземплярами класса
QSize можно выполнять операции
+
,
+=
, – (минус),
-=
,
==
и
!=
. Кроме того, экземпляр класса
QSize можно умножить или разделить на вещественное число (операторами
*
,
*=
,
/
и
/=
):
>>> s1 = QtCore.QSize(50, 20); s2 = QtCore.QSize(10, 5)
>>> s1 + s2, s1 — s2
(PyQt5.QtCore.QSize(60, 25), PyQt5.QtCore.QSize(40, 15))
>>> s1 * 2.5, s1 / 2
(PyQt5.QtCore.QSize(125, 50), PyQt5.QtCore.QSize(25, 10))
>>> s1 == s2, s1 != s2
(False, True)
18.5.3. Класс QRect: координаты и размеры прямоугольной области
Класс
QRect описывает координаты и размеры прямоугольной области. Для создания экзем- пляра класса предназначены следующие форматы конструктора:
<Объект> = QRect()
<Объект> = QRect(
<Объект> = QRect(<Координаты левого верхнего угла>, <Размеры>)
<Объект> = QRect(<Координаты левого верхнего угла>,
<Координаты правого нижнего угла>)
<Объект> = QRect(
Первый конструктор создает экземпляр класса со значениями по умолчанию. Второй и тре- тий конструкторы позволяют указать координаты левого верхнего угла и размеры области.
Во втором конструкторе значения указываются отдельно. В третьем конструкторе коорди- наты задаются с помощью класса
QPoint
, а размеры — с помощью класса
QSize
. Четвертый конструктор позволяет указать координаты левого верхнего угла и правого нижнего угла.
В качестве значений указываются экземпляры класса
QPoint
. Пятый конструктор создает новый экземпляр на основе другого экземпляра.
Примеры:
>>> from PyQt5 import QtCore
>>> r = QtCore.QRect()
>>> r.left(), r.top(), r.right(), r.bottom(), r.width(), r.height()
(0, 0, -1, -1, 0, 0)
>>> r = QtCore.QRect(10, 15, 400, 300)
>>> r.left(), r.top(), r.right(), r.bottom(), r.width(), r.height()
(10, 15, 409, 314, 400, 300)
>>> r = QtCore.QRect(QtCore.QPoint(10, 15), QtCore.QSize(400, 300))
>>> r.left(), r.top(), r.right(), r.bottom(), r.width(), r.height()
(10, 15, 409, 314, 400, 300)
>>> r = QtCore.QRect(QtCore.QPoint(10, 15), QtCore.QPoint(409, 314))
>>> r.left(), r.top(), r.right(), r.bottom(), r.width(), r.height()
(10, 15, 409, 314, 400, 300)
>>> QtCore.QRect(r)
PyQt5.QtCore.QRect(10, 15, 400, 300)
Изменить значения уже после создания экземпляра позволяют следующие методы:
setLeft(
, setX(
, setTop(
и setY(
— задают координаты левого верхнего угла по осям
X
и
Y
:
Глава 18. Управление окном приложения
375
>>> r = QtCore.QRect()
>>> r.setLeft(10); r.setTop(55); r
PyQt5.QtCore.QRect(10, 55, -10, -55)
>>> r.setX(12); r.setY(81); r
PyQt5.QtCore.QRect(12, 81, -12, -81)
setRight(
и setBottom(
— задают координаты правого нижнего угла по осям
X
и
Y
:
>>> r = QtCore.QRect()
>>> r.setRight(12); r.setBottom(81); r
PyQt5.QtCore.QRect(0, 0, 13, 82)
setTopLeft(
— задает координаты левого верхнего угла;
setTopRight(
— задает координаты правого верхнего угла;
setBottomLeft(
— задает координаты левого нижнего угла;
setBottomRight(
— задает координаты правого нижнего угла.
Примеры:
>>> r = QtCore.QRect()
>>> r.setTopLeft(QtCore.QPoint(10, 5))
>>> r.setBottomRight(QtCore.QPoint(39, 19)); r
PyQt5.QtCore.QRect(10, 5, 30, 15)
>>> r.setTopRight(QtCore.QPoint(39, 5))
>>> r.setBottomLeft(QtCore.QPoint(10, 19)); r
PyQt5.QtCore.QRect(10, 5, 30, 15)
setWidth(<Ширина>)
, setHeight(<Высота>)
и setSize(
— задают ширину и высо- ту области;
setRect(
— задает координаты левого верхнего угла и размеры области;
setCoords(
— задает координаты левого верхнего и правого нижнего углов.
Примеры:
>>> r = QtCore.QRect()
>>> r.setRect(10, 10, 100, 500); r
PyQt5.QtCore.QRect(10, 10, 100, 500)
>>> r.setCoords(10, 10, 109, 509); r
PyQt5.QtCore.QRect(10, 10, 100, 500)
marginsAdded(
— возвращает новый экземпляр класса
QRect
, который пред- ставляет текущую область, увеличенную на заданные величины границ. Эти границы указываются в виде экземпляра класса
1 ... 32 33 34 35 36 37 38 39 ... 83
QMargins из модуля
QtCore
, конструктор которого имеет следующий формат:
QMargins(<Граница слева>, <Граница сверху>, <Граница справа>, <Граница снизу>)
Текущая область при этом не изменяется:
>>> r1 = QtCore.QRect(10, 15, 400, 300)
>>> m = QtCore.QMargins(5, 2, 5, 2)
376
Часть II. Библиотека PyQt 5
>>> r2 = r1.marginsAdded(m)
>>> r2
PyQt5.QtCore.QRect(5, 13, 410, 304)
>>> r1
PyQt5.QtCore.QRect(10, 15, 400, 300)
marginsRemoved()
— то же самое, что marginsAdded()
, но уменьшает новую область на заданные величины границ:
>>> r1 = QtCore.QRect(10, 15, 400, 300)
>>> m = QtCore.QMargins(2, 10, 2, 10)
>>> r2 = r1.marginsRemoved(m)
>>> r2
PyQt5.QtCore.QRect(12, 25, 396, 280)
>>> r1
PyQt5.QtCore.QRect(10, 15, 400, 300)
Переместить область при изменении координат позволяют следующие методы:
moveTo(, )
, moveTo()
, moveLeft()
и moveTop()
— задают новые координаты левого верхнего угла:
>>> r = QtCore.QRect(10, 15, 400, 300)
>>> r.moveTo(0, 0); r
PyQt5.QtCore.QRect(0, 0, 400, 300)
>>> r.moveTo(QtCore.QPoint(10, 10)); r
PyQt5.QtCore.QRect(10, 10, 400, 300)
>>> r.moveLeft(5); r.moveTop(0); r
PyQt5.QtCore.QRect(5, 0, 400, 300)
moveRight()
и moveBottom()
— задают новые координаты правого нижнего угла;
moveTopLeft()
— задает новые координаты левого верхнего угла;
moveTopRight()
— задает новые координаты правого верхнего угла;
moveBottomLeft()
— задает новые координаты левого нижнего угла;
moveBottomRight()
— задает новые координаты правого нижнего угла.
Примеры:
>>> r = QtCore.QRect(10, 15, 400, 300)
>>> r.moveTopLeft(QtCore.QPoint(0, 0)); r
PyQt5.QtCore.QRect(0, 0, 400, 300)
>>> r.moveBottomRight(QtCore.QPoint(599, 499)); r
PyQt5.QtCore.QRect(200, 200, 400, 300)
moveCenter()
— задает новые координаты центра;
translate(<Сдвиг по оси X>, <Сдвиг по оси Y>)
и translate()
— задают но- вые координаты левого верхнего угла относительно текущего значения координат:
>>> r = QtCore.QRect(0, 0, 400, 300)
>>> r.translate(20, 15); r
PyQt5.QtCore.QRect(20, 15, 400, 300)
>>> r.translate(QtCore.QPoint(10, 5)); r
PyQt5.QtCore.QRect(30, 20, 400, 300)
Глава 18. Управление окном приложения
377
translated(<Сдвиг по оси X>, <Сдвиг по оси Y>)
и translated()
— анало- гичен методу translate()
, но возвращает новый экземпляр класса
QRect
, а не изменяет текущий;
adjust(, , , )
— задает новые координаты левого верхнего и правого нижнего углов относительно текущих значений координат:
>>> r = QtCore.QRect(0, 0, 400, 300)
>>> r.adjust(10, 5, 10, 5); r
PyQt5.QtCore.QRect(10, 5, 400, 300)
adjusted(, , , )
— аналогичен методу adjust()
, но возвращает но- вый экземпляр класса
QRect
, а не изменяет текущий.
Для получения параметров области предназначены следующие методы:
left()
и x()
— возвращают координату левого верхнего угла по оси
X
;
top()
и y()
— возвращают координату левого верхнего угла по оси
Y
;
right()
и bottom()
— возвращают координаты правого нижнего угла по осям
X
и
Y
соот- ветственно;
width()
и height()
— возвращают ширину и высоту соответственно;
size()
— возвращает размеры в виде экземпляра класса
QSize
Примеры:
>>> r = QtCore.QRect(10, 15, 400, 300)
>>> r.left(), r.top(), r.x(), r.y(), r.right(), r.bottom()
(10, 15, 10, 15, 409, 314)
>>> r.width(), r.height(), r.size()
(400, 300, PyQt5.QtCore.QSize(400, 300))
topLeft()
— возвращает координаты левого верхнего угла;
topRight()
— возвращает координаты правого верхнего угла;
bottomLeft()
— возвращает координаты левого нижнего угла;
bottomRight()
— возвращает координаты правого нижнего угла.
Примеры:
>>> r = QtCore.QRect(10, 15, 400, 300)
>>> r.topLeft(), r.topRight()
(PyQt5.QtCore.QPoint(10, 15), PyQt5.QtCore.QPoint(409, 15))
>>> r.bottomLeft(), r.bottomRight()
(PyQt5.QtCore.QPoint(10, 314), PyQt5.QtCore.QPoint(409, 314))
center()
— возвращает координаты центра области. Например, вывести окно по центру доступной области экрана можно так: desktop = QtWidgets.QApplication.desktop() window.move(desktop.availableGeometry().center() - window.rect().center())
getRect()
— возвращает кортеж с координатами левого верхнего угла и размерами об- ласти;
getCoords()
— возвращает кортеж с координатами левого верхнего и правого нижнего углов:
378
Часть II. Библиотека PyQt 5
>>> r = QtCore.QRect(10, 15, 400, 300)
>>> r.getRect(), r.getCoords()
((10, 15, 400, 300), (10, 15, 409, 314))
Прочие методы:
isNull()
— возвращает
True
, если ширина и высота равны нулю, и
False
— в противном случае;
isValid()
— возвращает
True
, если left() < right()
и top() < bottom()
, и
False
— в противном случае;
isEmpty()
— возвращает
True
, если left() > right()
или top() > bottom()
, и
False
— в противном случае;
normalized()
— исправляет ситуацию, при которой left() > right()
или top() > bottom()
, и возвращает новый экземпляр класса
QRect
:
>>> r = QtCore.QRect(QtCore.QPoint(409, 314), QtCore.QPoint(10, 15))
>>> r
PyQt5.QtCore.QRect(409, 314, -398, -298)
>>> r.normalized()
PyQt5.QtCore.QRect(10, 15, 400, 300)
contains([, <Флаг>])
и contains(, [, <Флаг>])
— возвращает
True
, если точка с указанными координатами расположена внутри области или на ее границе, и
False
— в противном случае. Если во втором параметре указано значение
True
, то точка должна быть расположена только внутри области, а не на ее границе. Значение парамет- ра по умолчанию —
False
:
>>> r = QtCore.QRect(0, 0, 400, 300)
>>> r.contains(0, 10), r.contains(0, 10, True)
(True, False)
contains([, <Флаг>])
— возвращает
True
, если указанная область расположена внутри текущей области или на ее краю, и
False
— в противном случае. Если во втором параметре указано значение
True
, то указанная область должна быть расположена толь- ко внутри текущей области, а не на ее краю. Значение параметра по умолчанию —
False
:
>>> r = QtCore.QRect(0, 0, 400, 300)
>>> r.contains(QtCore.QRect(0, 0, 20, 5))
True
>>> r.contains(QtCore.QRect(0, 0, 20, 5), True)
False
intersects()
— возвращает
True
, если указанная область пересекается с текущей областью, и
False
— в противном случае;
intersected()
— возвращает область, которая расположена на пересечении те- кущей и указанной областей:
>>> r = QtCore.QRect(0, 0, 20, 20)
>>> r.intersects(QtCore.QRect(10, 10, 20, 20))
True
>>> r.intersected(QtCore.QRect(10, 10, 20, 20))
PyQt5.QtCore.QRect(10, 10, 10, 10)
Глава 18. Управление окном приложения
379
united()
— возвращает область, которая охватывает текущую и указанную об- ласти:
>>> r = QtCore.QRect(0, 0, 20, 20)
>>> r.united(QtCore.QRect(30, 30, 20, 20))
PyQt5.QtCore.QRect(0, 0, 50, 50)
Над двумя экземплярами класса
QRect можно выполнять операции
&
и
&=
(пересечение),
|
и
|=
(объединение), in
(проверка на вхождение),
==
и
!=
Пример:
>>> r1, r2 = QtCore.QRect(0, 0, 20, 20), QtCore.QRect(10, 10, 20, 20)
>>> r1 & r2, r1 | r2
(PyQt5.QtCore.QRect(10, 10, 10, 10), PyQt5.QtCore.QRect(0, 0, 30, 30))
>>> r1 in r2, r1 in QtCore.QRect(0, 0, 30, 30)
(False, True)
>>> r1 == r2, r1 != r2
(False, True)
Помимо этого, поддерживаются операторы
+
и
-
, выполняющие увеличение и уменьшение области на заданные величины границ, которые должны быть заданы в виде объекта класса
QMargins
:
>>> r = QtCore.QRect(10, 15, 400, 300)
>>> m = QtCore.QMargins(5, 15, 5, 15)
>>> r + m
PyQt5.QtCore.QRect(5, 0, 410, 330)
>>> r - m
PyQt5.QtCore.QRect(15, 30, 390, 270)
18.6. Разворачивание и сворачивание окна
В заголовке окна расположены кнопки Свернуть и Развернуть, с помощью которых мож- но свернуть окно в значок на панели задач или развернуть его на весь экран. Выполнить подобные действия из программы позволяют следующие методы класса
QWidget
:
showMinimized()
— сворачивает окно на панель задач. Эквивалентно нажатию кнопки
Свернуть в заголовке окна;
showMaximized()
— разворачивает окно до максимального размера. Эквивалентно нажа- тию кнопки Развернуть в заголовке окна;
showFullScreen()
— включает полноэкранный режим отображения окна. Окно отобра- жается без заголовка и границ;
showNormal()
— отменяет сворачивание, максимальный размер и полноэкранный режим, возвращая окно к изначальным размерам;
activateWindow()
— делает окно активным (т. е. имеющим фокус ввода). В Windows, если окно было ранее свернуто в значок на панель задач, оно не будет развернуто в из- начальный вид;
setWindowState(<Флаги>)
— изменяет состояние окна в зависимости от переданных фла- гов. В качестве параметра указывается комбинация следующих атрибутов из класса
QtCore.Qt через побитовые операторы:
380
Часть II. Библиотека PyQt 5
•
WindowNoState
— нормальное состояние окна;
•
WindowMinimized
— окно свернуто;
•
WindowMaximized
— окно максимально развернуто;
•
WindowFullScreen
— полноэкранный режим;
•
WindowActive
— окно имеет фокус ввода, т. е. является активным.
Например, включить полноэкранный режим можно так: window.setWindowState((window.windowState() &
(QtCore.Qt.WindowMinimized | QtCore.Qt.WindowMaximized))
| QtCore.Qt.WindowFullScreen)
Проверить текущий статус окна позволяют следующие методы:
isMinimized()
— возвращает
True
, если окно свернуто, и
False
— в противном случае;
isMaximized()
— возвращает
True
, если окно раскрыто до максимальных размеров, и
False
— в противном случае;
isFullScreen()
— возвращает
True
, если включен полноэкранный режим, и
False
— в противном случае;
isActiveWindow()
— возвращает
True
, если окно имеет фокус ввода, и
False
— в про- тивном случае;
windowState()
— возвращает комбинацию флагов, обозначающих текущий статус окна.
Пример проверки использования полноэкранного режима: if window.windowState() & QtCore.Qt.WindowFullScreen: print("Полноэкранный режим")
Пример разворачивания и сворачивания окна приведен в листинге 18.4.
Листинг 18.4. Разворачивание и сворачивание окна
# -*- coding: utf-8 -*- from PyQt5 import QtCore, QtWidgets class MyWindow(QtWidgets.QWidget): def __init__(self, parent=None):
QtWidgets.QWidget.__init__(self, parent) self.btnMin = QtWidgets.QPushButton("Свернуть") self.btnMax = QtWidgets.QPushButton("Развернуть") self.btnFull = QtWidgets.QPushButton("Полный экран") self.btnNormal = QtWidgets.QPushButton("Нормальный размер") vbox = QtWidgets.QVBoxLayout() vbox.addWidget(self.btnMin) vbox.addWidget(self.btnMax) vbox.addWidget(self.btnFull) vbox.addWidget(self.btnNormal) self.setLayout(vbox) self.btnMin.clicked.connect(self.on_min) self.btnMax.clicked.connect(self.on_max) self.btnFull.clicked.connect(self.on_full) self.btnNormal.clicked.connect(self.on_normal)
Глава 18. Управление окном приложения
381 def on_min(self): self.showMinimized() def on_max(self): self.showMaximized() def on_full(self): self.showFullScreen() def on_normal(self): self.showNormal() if __name__ == "__main__": import sys app = QtWidgets.QApplication(sys.argv) window = MyWindow() window.setWindowTitle("Разворачивание и сворачивание окна") window.resize(300, 100) window.show() sys.exit(app.exec_())
18.7. Управление прозрачностью окна
Сделать окно полупрозрачным позволяет метод setWindowOpacity()
класса
QWidget
. Формат метода: setWindowOpacity(<Вещественное число от 0.0 до 1.0>)
Число
0.0
соответствует полностью прозрачному окну, а число
1.0
— отсутствию прозрач- ности. Для получения степени прозрачности окна из программы предназначен метод windowOpacity()
. Выведем окно со степенью прозрачности
0.5
(листинг 18.5).
Листинг 18.5. Полупрозрачное окно
# -*- coding: utf-8 -*- from PyQt5 import QtWidgets import sys app = QtWidgets.QApplication(sys.argv) window = QtWidgets.QWidget() window.setWindowTitle("Полупрозрачное окно") window.resize(300, 100) window.setWindowOpacity(0.5) window.show() print(window.windowOpacity()) # Выведет: 0.4980392156862745 sys.exit(app.exec_())
18.8. Модальные окна
Модальным называется окно, которое не позволяет взаимодействовать с другими окнами в том же приложении, — пока модальное окно не будет закрыто, сделать активным другое окно нельзя. Например, если в программе Microsoft Word выбрать пункт меню Файл | Со- хранить как, откроется модальное диалоговое окно, позволяющее выбрать путь и название
382
Часть II. Библиотека PyQt 5 файла, и, пока это окно не будет закрыто, вы не сможете взаимодействовать с главным ок- ном приложения.
Указать, что окно является модальным, позволяет метод setWindowModality(<Флаг>)
класса
QWidget
. В качестве параметра могут быть указаны следующие атрибуты из класса
QtCore.Qt
:
NonModal
—
0
— окно не является модальным (поведение по умолчанию);
WindowModal
—
1
— окно блокирует только родительские окна в пределах иерархии;
ApplicationModal
—
2
— окно блокирует все окна в приложении.
Окна, открытые из модального окна, не блокируются. Следует также учитывать, что метод setWindowModality()
должен быть вызван до отображения окна.
Получить текущее значение модальности позволяет метод windowModality()
. Проверить, является ли окно модальным, можно с помощью метода isModal()
— он возвращает
True
, если окно является модальным, и
False
— в противном случае.
Создадим два независимых окна. В первом окне разместим кнопку, по нажатию которой откроется модальное окно, — оно будет блокировать только первое окно, но не второе. При открытии модального окна отобразим его примерно по центру родительского окна (лис- тинг 18.6).
Листинг 18.6. Модальные окна
# -*- coding: utf-8 -*- from PyQt5 import QtCore, QtWidgets import sys def show_modal_window(): global modalWindow modalWindow = QtWidgets.QWidget(window1, QtCore.Qt.Window) modalWindow.setWindowTitle("Модальное окно") modalWindow.resize(200, 50) modalWindow.setWindowModality(QtCore.Qt.WindowModal) modalWindow.setAttribute(QtCore.Qt.WA_DeleteOnClose, True) modalWindow.move(window1.geometry().center() - modalWindow.rect().center() –
QtCore.QPoint(4, 30)) modalWindow.show() app = QtWidgets.QApplication(sys.argv) window1 = QtWidgets.QWidget() window1.setWindowTitle("Обычное окно") window1.resize(300, 100) button = QtWidgets.QPushButton("Открыть модальное окно") button.clicked.connect(show_modal_window) vbox = QtWidgets.QVBoxLayout() vbox.addWidget(button) window1.setLayout(vbox) window1.show() window2 = QtWidgets.QWidget() window2.setWindowTitle("Это окно не будет блокировано при WindowModal")
Глава 18. Управление окном приложения
383 window2.resize(500, 100) window2.show() sys.exit(app.exec_())
Если запустить приложение и нажать кнопку Открыть модальное окно, откроется окно, выровненное примерно по центру родительского окна (произвести точное выравнивание вы сможете самостоятельно). При этом получить доступ к родительскому окну можно только после закрытия модального окна, второе же окно блокировано не будет. Если заменить атрибут
WindowModal атрибутом
ApplicationModal
, оба окна будут блокированы.
Обратите внимание, что в конструктор модального окна мы передали ссылку на первое ок- но и атрибут
Window
. Если не указать ссылку, то окно блокировано не будет, а если не ука- зать атрибут, окно вообще не откроется. Кроме того, мы объявили переменную modalWindow глобальной, иначе при достижении конца функции переменная выйдет из области видимо- сти, и окно будет автоматически удалено. Чтобы объект окна автоматически удалялся при закрытии окна, атрибуту
WA_DeleteOnClose в методе setAttribute()
было присвоено значе- ние
True
Модальные окна в большинстве случаев являются диалоговыми. Для работы с такими окнами в PyQt предназначен класс
QDialog
, который автоматически выравнивает окно по центру экрана или родительского окна. Кроме того, этот класс предоставляет множество специальных методов, позволяющих дождаться закрытия окна, определить статус заверше- ния и выполнить другие действия. Подробно класс
QDialog мы рассмотрим в главе 26.
18.9. Смена значка в заголовке окна
По умолчанию в левом верхнем углу окна отображается стандартный значок. Отобразить другой значок позволяет метод setWindowIcon()
класса
QWidget
. В качестве параметра метод принимает экземпляр класса
QIcon из модуля
QtGui
(см. разд. 24.3.4).
Чтобы загрузить значок из файла, следует передать путь к файлу конструктору класса
QIcon
. Если указан относительный путь, поиск файла будет производиться относительно текущего рабочего каталога. Получить список поддерживаемых форматов файлов можно с помощью статического метода supportedImageFormats()
класса
QImageReader
, объявлен- ного в модуле
QtGui
. Метод возвращает список с экземплярами класса
QByteArray
. Получим список поддерживаемых форматов:
>>> from PyQt5 import QtGui
>>> for i in QtGui.QImageReader.supportedImageFormats(): print(str(i, "ascii").upper(), end=" ")
Вот результат выполнения этого примера на компьютере одного из авторов:
BMP CUR GIF ICNS ICO JPEG JPG PBM PGM PNG PPM SVG SVGZ TGA TIF TIFF WBMP WEBP XBM XPM
Если для окна не указать значок, будет использоваться значок приложения, установленный с помощью метода setWindowIcon()
класса
QApplication
. В качестве параметра метод также принимает экземпляр класса
QIcon
Вместо загрузки значка из файла можно воспользоваться одним из встроенных значков.
Загрузить стандартный значок позволяет следующий код: ico = window.style().standardIcon(QtWidgets.QStyle.SP_MessageBoxCritical) window.setWindowIcon(ico)
384
Часть II. Библиотека PyQt 5
Посмотреть список всех встроенных значков можно в документации к классу
QStyle
(см. https://doc.qt.io/qt-5/qstyle.html#StandardPixmap-enum).
В качестве примера создадим значок размером 16 на 16 пикселов в формате PNG и сохра- ним его в одном каталоге с программой, после чего установим этот значок для окна и всего приложения (листинг 18.7).
Листинг 18.7. Смена значка в заголовке окна
# -*- coding: utf-8 -*- from PyQt5 import QtGui, QtWidgets import sys app = QtWidgets.QApplication(sys.argv) window = QtWidgets.QWidget() window.setWindowTitle("Смена значка в заголовке окна") window.resize(300, 100) ico = QtGui.QIcon("icon.png") window.setWindowIcon(ico) # Значок для окна app.setWindowIcon(ico) # Значок приложения window.show() sys.exit(app.exec_())
18.10. Изменение цвета фона окна
Чтобы изменить цвет фона окна (или компонента), следует установить палитру с настроен- ной ролью
Window
(или
Background
). Цветовая палитра содержит цвета для каждой роли и состояния компонента. Указать состояние компонента позволяют следующие атрибуты из класса
QPalette
(модуль
QtGui
):
Active и
Normal
—
0
— компонент активен (окно находится в фокусе ввода);
Disabled
—
1
— компонент недоступен;
Inactive
—
2
— компонент неактивен (окно находится вне фокуса ввода).
Получить текущую палитру компонента позволяет его метод palette()
. Чтобы изменить цвет для какой-либо роли и состояния, следует воспользоваться методом setColor()
класса
QPalette
. Формат метода: setColor([<Состояние>, ]<Роль>, <Цвет>)
В параметре
<Роль>
указывается, для какого элемента изменяется цвет. Например, атрибут
Window
(или
Background
) изменяет цвет фона, а
WindowText
(или
Foreground
) — цвет текста.
Полный список атрибутов имеется в документации по классу
QPalette
(см. https://doc.qt.io/ qt-5/qpalette.html).
В параметре
<Цвет>
указывается цвет элемента. В качестве значения можно указать атрибут из класса
QtCore.Qt
(например, black
, white и т. д.) или экземпляр класса
QColor
(например,
QColor("red")
,
QColor("#ff0000")
,
QColor(255, 0, 0)
и др.).
После настройки палитры необходимо вызвать метод setPalette()
компонента и передать этому методу измененный объект палитры. Следует помнить, что компоненты-потомки по умолчанию имеют прозрачный фон и не перерисовываются автоматически. Чтобы вклю-
Глава 18. Управление окном приложения
385 чить перерисовку, необходимо передать значение
True методу setAutoFillBackground()
окна.
Изменить цвет фона также можно с помощью CSS-атрибута background-color
. Для этого следует передать таблицу стилей в метод setStyleSheet()
компонента. Таблицы стилей могут быть внешними (подключение через командную строку), установленными на уровне приложения (с помощью метода setStyleSheet()
класса
QApplication
) или установленными на уровне компонента (с помощью метода setStyleSheet()
класса
QWidget
). Атрибуты, установленные последними, обычно перекрывают значения аналогичных атрибутов, ука- занных ранее. Если вы занимались веб-программированием, то язык CSS (каскадные табли- цы стилей) вам уже знаком, а если нет, придется дополнительно его изучить.
Создадим окно с надписью. Для активного окна установим зеленый цвет, а для неактивно- го — красный. Цвет фона надписи сделаем белым. Для изменения фона окна используем палитру, а для изменения цвета фона надписи — CSS-атрибут background-color
(лис- тинг 18.8).
Листинг 18.8. Изменение цвета фона окна
# -*- coding: utf-8 -*- from PyQt5 import QtCore, QtGui, QtWidgets import sys app = QtWidgets.QApplication(sys.argv) window = QtWidgets.QWidget() window.setWindowTitle("Изменение цвета фона окна") window.resize(300, 100) pal = window.palette() pal.setColor(QtGui.QPalette.Normal, QtGui.QPalette.Window,
QtGui.QColor("#008800")) pal.setColor(QtGui.QPalette.Inactive, QtGui.QPalette.Window,
QtGui.QColor("#ff0000")) window.setPalette(pal) label = QtWidgets.QLabel("Текст надписи") label.setAlignment(QtCore.Qt.AlignHCenter) label.setStyleSheet("background-color: #ffffff;") label.setAutoFillBackground(True) vbox = QtWidgets.QVBoxLayout() vbox.addWidget(label) window.setLayout(vbox) window.show() sys.exit(app.exec_())
18.11. Вывод изображения в качестве фона
В качестве фона окна (или компонента) можно использовать изображение. Для этого необ- ходимо получить текущую палитру компонента с помощью метода palette()
, а затем вы- звать метод setBrush()
класса
QPalette
. Формат метода: setBrush([<Состояние>, ]<Роль>, )
386
Часть II. Библиотека PyQt 5
Первые два параметра аналогичны соответствующим параметрам в методе setColor()
, ко- торый мы рассматривали в предыдущем разделе. В третьем параметре указывается кисть — экземпляр класса
QBrush из модуля
QtGui
. Форматы конструктора класса:
<Объект> = QBrush(<Стиль кисти>)
<Объект> = QBrush(<Цвет>[, <Стиль кисти>=SolidPattern])
<Объект> = QBrush(<Цвет>, )
<Объект> = QBrush()
<Объект> = QBrush()
<Объект> = QBrush()
<Объект> = QBrush()
В параметре
<Стиль кисти>
указываются атрибуты из класса
QtCore.Qt
, задающие стиль кисти, — например:
NoBrush
,
SolidPattern
,
Dense1Pattern
,
Dense2Pattern
,
Dense3Pattern
,
Dense4Pattern
,
Dense5Pattern
,
Dense6Pattern
,
Dense7Pattern
,
CrossPattern и др. С по- мощью этого параметра можно сделать цвет сплошным (
SolidPattern
) или имеющим тек- стуру (например, атрибут
CrossPattern задает текстуру в виде сетки).
В параметре
<Цвет>
указывается цвет кисти. В качестве значения можно указать атрибут из класса
QtCore.Qt
(например, black
, white и т. д.) или экземпляр класса
QColor
(например,
QColor("red")
,
QColor("#ff0000")
,
QColor(255, 0, 0)
и др.). При этом установка сплошного цвета фона окна может выглядеть так: pal = window.palette() pal.setBrush(QtGui.QPalette.Normal, QtGui.QPalette.Window,
QtGui.QBrush(QtGui.QColor("#008800"), QtCore.Qt.SolidPattern)) window.setPalette(pal)
Параметры
и
позволяют передать объекты изображений. Конструкторы этих классов принимают путь к файлу — абсолютный или относительный.
Параметр
позволяет создать кисть на основе другой кисти, а параметр
— на основе градиента, представленного объектом класса
QGradient
(см. главу 24).
После настройки палитры необходимо вызвать метод setPalette()
и передать ему изме- ненный объект палитры. Следует помнить, что компоненты-потомки по умолчанию имеют прозрачный фон и не перерисовываются автоматически. Чтобы включить перерисовку, не- обходимо передать значение
True в метод setAutoFillBackground()
Указать, какое изображение используется в качестве фона, также можно с помощью CSS- атрибутов background и background-image
. С помощью CSS-атрибута background-repeat можно дополнительно указать режим повтора фонового рисунка. Он может принимать зна- чения repeat
(повтор по горизонтали и вертикали), repeat-x
(повтор только по горизонта- ли), repeat-y
(повтор только по вертикали) и no-repeat
(не повторяется).
Создадим окно с надписью. Для активного окна установим одно изображение (с помощью изменения палитры), а для надписи — другое (с помощью CSS-атрибута background-image
)
(листинг 18.9).
Листинг 18.9. Использование изображения в качестве фона
# -*- coding: utf-8 -*- from PyQt5 import QtCore, QtGui, QtWidgets import sys
Глава 18. Управление окном приложения
387 app = QtWidgets.QApplication(sys.argv) window = QtWidgets.QWidget() window.setWindowTitle("Изображение в качестве фона") window.resize(300, 100) pal = window.palette() pal.setBrush(QtGui.QPalette.Normal, QtGui.QPalette.Window,
QtGui.QBrush(QtGui.QPixmap("background1.jpg"))) window.setPalette(pal) label = QtWidgets.QLabel("Текст надписи") label.setAlignment(QtCore.Qt.AlignCenter) label.setStyleSheet("background-image: url(background2.jpg);") label.setAutoFillBackground(True) vbox = QtWidgets.QVBoxLayout() vbox.addWidget(label) window.setLayout(vbox) window.show() sys.exit(app.exec_())
18.12. Создание окна произвольной формы
Чтобы создать окно произвольной формы, нужно выполнить следующие шаги:
1. Создать изображение нужной формы с прозрачным фоном и сохранить его, например, в формате PNG.
2. Создать экземпляр класса
QPixmap
, передав конструктору класса абсолютный или отно- сительный путь к изображению.
3. Установить изображение в качестве фона окна с помощью палитры.
4. Отделить альфа-канал с помощью метода mask()
класса
QPixmap
5. Передать получившуюся маску в метод setMask()
окна.
6. Убрать рамку окна, например, передав комбинацию следующих флагов:
QtCore.Qt.Window | QtCore.Qt.FramelessWindowHint
Если для создания окна используется класс
QLabel
, то вместо установки палитры можно передать экземпляр класса
QPixmap в метод setPixmap()
, а маску — в метод setMask()
Для примера создадим круглое окно с кнопкой, с помощью которой можно закрыть окно.
Для использования в качестве маски создадим изображение в формате PNG, установим для него прозрачный фон и нарисуем белый круг, занимающий все это изображение. Окно выведем без заголовка и границ (листинг 18.10).
Листинг 18.10. Создание окна произвольной формы
# -*- coding: utf-8 -*- from PyQt5 import QtCore, QtGui, QtWidgets import sys app = QtWidgets.QApplication(sys.argv) window = QtWidgets.QWidget() window.setWindowFlags(QtCore.Qt.Window | QtCore.Qt.FramelessWindowHint)
388
Часть II. Библиотека PyQt 5 window.setWindowTitle("Создание окна произвольной формы") window.resize(300, 300) pixmap = QtGui.QPixmap("mask.png") pal = window.palette() pal.setBrush(QtGui.QPalette.Normal, QtGui.QPalette.Window,
QtGui.QBrush(pixmap)) pal.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.Window,
QtGui.QBrush(pixmap)) window.setPalette(pal) window.setMask(pixmap.mask()) button = QtWidgets.QPushButton("Закрыть окно", window) button.setFixedSize(150, 30) button.move(75, 135) button.clicked.connect(QtWidgets.qApp.quit) window.show() sys.exit(app.exec_())
Получившееся окно можно увидеть на рис. 18.1.
Рис. 18.1. Окно круглой формы
18.13. Всплывающие подсказки
При работе с программой у пользователя могут возникать вопросы о предназначении того или иного компонента. Обычно для информирования пользователя служат надписи, распо- ложенные над компонентом или левее его. Но часто либо место в окне ограничено, либо вывод этих надписей портит весь дизайн окна. В таких случаях принято выводить текст подсказки в отдельном окне без рамки при наведении указателя мыши на компонент. Под- сказка автоматически скроется после увода курсора мыши или спустя определенное время.
В PyQt нет необходимости создавать окно с подсказкой самому и следить за перемещения- ми указателя мыши — весь этот процесс автоматизирован и максимально упрощен. Чтобы создать всплывающие подсказки для окна или любого другого компонента и управлять ими, нужно воспользоваться следующими методами класса
QWidget
:
Глава 18. Управление окном приложения
389
setToolTip(<Текст>)
— задает текст всплывающей подсказки. В качестве параметра можно указать простой текст или HTML-код. Чтобы отключить вывод подсказки, доста- точно передать в этот метод пустую строку;
toolTip()
— возвращает текст всплывающей подсказки;
setToolTipDuration(<Время>)
— задает время, в течение которого всплывающая под- сказка будет присутствовать на экране. Значение должно быть указано в миллисекундах.
Если задать значение
-1
, PyQt будет сама вычислять необходимое время, основываясь на длине текста подсказки (это поведение по умолчанию);
toolTipDuration()
— возвращает время, в течение которого всплывающая подсказка будет присутствовать на экране;
setWhatsThis(<Текст>)
— задает текст справки. Обычно этот метод используется для вывода информации большего объема, чем во всплывающей подсказке. У диалоговых окон в заголовке окна есть кнопка Справка, по нажатию которой курсор принимает вид стрелки со знаком вопроса, — чтобы в таком случае отобразить текст справки, следует нажать эту кнопку и щелкнуть на компоненте. Можно также сделать компонент актив- ным и нажать комбинацию клавиш +. В качестве параметра можно указать простой текст или HTML-код. Чтобы отключить вывод подсказки, достаточно передать в этот метод пустую строку;
whatsThis()
— возвращает текст справки.
Создадим окно с кнопкой и зададим для них текст всплывающих подсказок и текст справки
(листинг 18.11).
Листинг 18.11. Всплывающие подсказки
# -*- coding: utf-8 -*- from PyQt5 import QtCore, QtWidgets import sys app = QtWidgets.QApplication(sys.argv) window = QtWidgets.QWidget(flags=QtCore.Qt.Dialog) window.setWindowTitle("Всплывающие подсказки") window.resize(300, 70) button = QtWidgets.QPushButton("Закрыть окно", window) button.setFixedSize(150, 30) button.move(75, 20) button.setToolTip("Это всплывающая подсказка для кнопки") button.setToolTipDuration(3000) window.setToolTip("Это всплывающая подсказка для окна") button.setToolTipDuration(5000) button.setWhatsThis("Это справка для кнопки") window.setWhatsThis("Это справка для окна") button.clicked.connect(QtWidgets.qApp.quit) window.show() sys.exit(app.exec_())
390
Часть II. Библиотека PyQt 5 18.14. Программное закрытие окна
В предыдущих разделах для закрытия окна мы использовали слот quit() и метод exit([returnCode=0])
объекта приложения. Однако эти методы не только закрывают теку- щее окно, но и завершают выполнение всего приложения. Чтобы закрыть только текущее окно, следует воспользоваться методом close()
класса
QWidget
. Метод возвращает значение
True
, если окно успешно закрыто, и
False
— в противном случае. Закрыть сразу все окна приложения позволяет слот closeAllWindows()
класса
QApplication
Если для окна атрибут
WA_DeleteOnClose из класса
QtCore.Qt установлен в значение
True
, после закрытия окна объект окна будет автоматически удален, в противном случае окно просто скрывается. Значение атрибута можно изменить с помощью метода setAttribute()
: window.setAttribute(QtCore.Qt.WA_DeleteOnClose, True)
После вызова метода close()
или нажатия кнопки Закрыть в заголовке окна генерируется событие
QEvent.Close
. Если внутри класса определить метод с предопределенным названи- ем closeEvent()
, это событие можно перехватить и обработать. В качестве параметра метод принимает объект класса
QCloseEvent
, который поддерживает методы accept()
(позволяет закрыть окно) и ignore()
(запрещает закрытие окна). Вызывая эти методы, можно контро- лировать процесс закрытия окна.
В качестве примера закроем окно по нажатию кнопки (листинг 18.12).
Листинг 18.12. Программное закрытие окна
# -*- coding: utf-8 -*- from PyQt5 import QtCore, QtWidgets import sys app = QtWidgets.QApplication(sys.argv) window = QtWidgets.QWidget(flags=QtCore.Qt.Dialog) window.setWindowTitle("Закрытие окна из программы") window.resize(300, 70) button = QtWidgets.QPushButton("Закрыть окно", window) button.setFixedSize(150, 30) button.move(75, 20) button.clicked.connect(window.close) window.show() sys.exit(app.exec_())
П
РИМЕЧАНИЕ
Закрыв последнее окно приложения, мы тем самым автоматически завершим и само при- ложение. Не забываем об этом.
18.15. Использование таблиц стилей CSS для оформления окон
Вернемся к методу setStyleSheet()
, упомянутому в разд. 18.10 и предназначенному для задания таблиц стилей у приложений и отдельных элементов управления. С помощью таб- лиц стилей можно задавать не только цвет фона и фоновое изображение, но и другие пара- метры оформления.
Глава 18. Управление окном приложения
391
Метод setStyleSheet()
поддерживается классами
QWidget
(и всеми его подклассами) и
QApplication
. Следовательно, его можно вызвать у:
самого приложения — тогда заданные в таблице стилей параметры оформления будут применены ко всем элементам управления всех окон приложения;
отдельного окна — тогда эти параметры будут действовать в пределах данного окна;
отдельного элемента управления — тогда они будут действовать только на этот элемент управления.
При указании таблицы стилей у приложения и окна можно использовать привычный нам по
CSS формат объявления стилей:
<Селектор> {<Определение стилей>}
<Селектор>
записывается в следующем формате:
<Основной селектор>[<Дополнительный селектор>][<Псевдокласс>][<Псевдоселектор>]
Параметр
<Основной селектор>
указывает на класс элемента управления. Его можно указать в одном из следующих форматов:
*
(звездочка) — указывает на все элементы управления (универсальный селектор). На- пример, так можно задать для всех элементов управления зеленый цвет текста:
* {color: green;}
<Класс>
— указывает на элементы управления, относящиеся к заданному
<Классу>
и его подклассам. Задание красного цвета текста для всех элементов управления, относящихся к классу
QAbstractButton и его подклассам, т. е. для командных кнопок, флажков и пе- реключателей, осуществляется так:
QAbstractButton {color: red;}
.<Класс>
— указывает только на элементы управления, относящиеся к заданному
<Классу>
, но не к его подклассам. Указание полужирного шрифта для всех элементов управления, относящихся к классу
QPushButton
(командных кнопок), но не для его под- классов, осуществляется так:
.QPushButton {font-weight: bold;}
Параметр
<Дополнительный селектор>
задает дополнительные параметры элемента управле- ния. Его форматы:
[<Свойство>="<Значение>"]
— указанное
<Свойство>
элемента управления должно иметь заданное
<Значение>
. Так мы задаем полужирный шрифт для кнопки, чье свойство default имеет значение true
, т. е. для кнопки по умолчанию:
QPushButton[default="true"] {font-weight: bold;}
#<Имя>
— указывает на элемент управления, для которого было задано
<Имя>
<Имя>
можно задать вызовом у элемента управления метода setObjectName(<Имя>)
, а полу- чить — вызовом метода objectName()
. Так выполняется указание красного цвета текста для кнопки с именем btnRed
:
QPushButton#btnRed {color: red;}
Параметр
<Псевдокласс>
указывает на отдельную составную часть сложного элемента управления. Он записывается в формате
::<Обозначение составной части>
. Вот пример ука- зания графического изображения для кнопки разворачивания раскрывающегося списка
(обозначение этой составной части — down-arrow
):
QComboBox::down-arrow {image: url(arrow.png);}
392
Часть II. Библиотека PyQt 5
Параметр
<Псевдоселектор>
указывает на состояние элемента управления (должна ли быть кнопка нажата, должен ли флажок быть установленным и т. п.). Он может быть записан в двух форматах:
:<Обозначение состояния>
— элемент управления должен находиться в указанном со- стоянии. Вот пример указания белого цвета фона для кнопки, когда она нажата (это состояние имеет обозначение pressed
):
QPushButton:pressed {background-color: white;}
:!<Обозначение состояния>
— элемент управления должен находиться в любом состоя- нии, кроме указанного. Вот пример указания желтого цвета фона для кнопки, когда она не нажата:
QPushButton:!pressed {background-color: yellow;}
Можно указать сразу несколько псевдоселекторов, расположив их непосредственно друг за другом — тогда селектор будет указывать на элемент управления, находящийся одно- временно во всех состояниях, которые обозначены этими селекторами. Вот пример ука- зания черного цвета фона и белого цвета текста для кнопки, которая нажата и над кото- рой находится курсор мыши (обозначение — hover
):
QPushButton:pressed:hover {color: white; background-color: black;}
Если нужно указать стиль для элемента управления, вложенного в другой элемент управле- ния, применяется следующий формат указания селектора:
<Селектор "внешнего" элемента><Разделитель><Селектор вложенного элемента>
Поддерживаются два варианта параметра
<Разделитель>
:
пробел —
<Вложенный элемент>
не обязательно должен быть вложен непосредственно во
<"Внешний">
. Так мы указываем зеленый цвет фона для всех надписей (
QLabel
), вложен- ных в группу (
QGroupBox
) и вложенные в нее элементы:
QGroupBox QLabel {background-color: green;}
>
—
<Вложенный элемент>
обязательно должен быть вложен непосредственно во
<"Внеш- ний">
. Так мы укажем синий цвет текста для всех надписей, непосредственно вложенных в группу:
QGroupBox>QLabel {color: blue;}
В стиле можно указать сразу несколько селекторов, записав их через запятую — тогда стиль будет применен к элементам управления, на которые указывают эти селекторы. Вот пример задания зеленого цвета фона для кнопок и надписей:
QLabel, QPushButton {background-color: green;}
В CSS элементы страницы наследуют параметры оформления от их родителей. Но в PyQt это не так. Скажем, если мы укажем для группы красный цвет текста: app.setStyleSheet("QGroupBox {color: red;}") вложенные в эту группу элементы не унаследуют его и будут иметь цвет текста, заданный по умолчанию. Нам придется задать для них нужный цвет явно: app.setStyleSheet("QGroupBox, QGroupBox * {color: red;}")
Начиная с версии PyQt 5.7, поддерживается возможность указать библиотеке, что все элементы-потомки должны наследовать параметры оформления у родителя. Для этого дос- таточно вызвать у класса
QCoreApplication статический метод setAttribute
, передав ему
Глава 18. Управление окном приложения
393 в качестве первого параметра значение атрибута
AA_UseStyleSheetPropagationInWidgetStyles класса
QtCore.Qt
, а в качестве второго параметра — значение
True
:
QtCore.QCoreApplication.setAttribute(
QtCore.Qt.AA_UseStyleSheetPropagationInWidgetStyles, True)
Чтобы отключить такую возможность, достаточно вызвать этот метод еще раз, указав в нем вторым параметром
False
И, наконец, при вызове метода setStyleSheet()
у элемента управления, для которого следу- ет задать таблицу стилей, в последней не указываются ни селектор, ни фигурные скобки — они просто не нужны.
Отметим, что в случае PyQt, как и в CSS, также действуют правила каскадности. Так, таб- лица стилей, заданная для окна, имеет больший приоритет, нежели таковая, указанная для приложения, а стиль, что был задан для элемента управления, имеет наивысший приоритет.
Помимо этого, более специфические стили имеют больший приоритет, чем менее специфи- ческие; так, стиль с селектором, в чей состав входит имя элемента управления, перекроет стиль с селектором любого другого типа.
За более подробным описанием поддерживаемых PyQt псевдоклассов, псевдоселекторов и особенностей указания стилей для отдельных классов элементов управления обращайтесь по интернет-адресу https://doc.qt.io/qt-5/stylesheet-reference.html.
Листинг 18.13 показывает пример задания таблиц стилей для элементов управления разны- ми способами. Результат выполнения приведенного в нем кода можно увидеть на рис. 18.2.
Листинг 18.13. Использование таблиц стилей для указания оформления
# -*- coding: utf-8 -*- from PyQt5 import QtWidgets import sys app = QtWidgets.QApplication(sys.argv)
# На уровне приложения задаем синий цвет текста для надписей, вложенных в группы,
# и курсивное начертание текста кнопок app.setStyleSheet("QGroupBox QLabel {color: blue;}
QPushButton {font-style: italic}") window = QtWidgets.QWidget() window.setWindowTitle("Таблицы стилей")
# На уровне окна задаем зеленый цвет текста для надписи с именем first и
# красный цвет текста для надписи, на которую наведен курсор мыши window.setStyleSheet("QLabel#first {color: green;} QLabel:hover {color: red;}") window.resize(200, 150)
# Создаем три надписи lbl1 = QtWidgets.QLabel("Зеленый текст")
# Указываем для первой надписи имя first lbl1.setObjectName("first") lbl2 = QtWidgets.QLabel("Полужирный текст")
# Для второй надписи указываем полужирный шрифт lbl2.setStyleSheet("font-weight: bold") lbl3 = QtWidgets.QLabel("Синий текст")
# Создаем кнопку btn = QtWidgets.QPushButton("Курсивный текст")
394
Часть II. Библиотека PyQt 5
# Создаем группу box = QtWidgets.QGroupBox("Группа")
# Создаем контейнер, помещаем в него третью надпись и вставляем в группу bbox = QtWidgets.QVBoxLayout() bbox.addWidget(lbl3) box.setLayout(bbox)
# Создаем еще один контейнер, помещаем в него две первые надписи, группу и кнопку и
# вставляем в окно mainbox = QtWidgets.QVBoxLayout() mainbox.addWidget(lbl1) mainbox.addWidget(lbl2) mainbox.addWidget(box) mainbox.addWidget(btn) window.setLayout(mainbox)
# Выводим окно и запускаем приложение window.show() sys.exit(app.exec_())
Рис. 18.2. Пример использования таблиц стилей
ГЛ А В А
19
Обработка сигналов и событий
При взаимодействии пользователя с окном возникают события — своего рода извещения о том, что пользователь выполнил какое-либо действие или в самой системе возникло неко- торое условие. В ответ на события система генерирует определенные сигналы, которые можно рассматривать как представления системных событий внутри библиотеки PyQt.
Сигналы являются важнейшей составляющей приложения с графическим интерфейсом, поэтому необходимо знать, как назначить обработчик сигнала, как удалить его, а также уметь правильно обработать событие. Сигналы, которые генерирует тот или иной компо- нент, мы будем рассматривать при изучении конкретного компонента.
19.1. Назначение обработчиков сигналов
Чтобы обработать какой-либо сигнал, необходимо сопоставить ему функцию или метод класса, который будет вызван при возникновении события и станет его обработчиком.
Каждый сигнал, поддерживаемый классом, соответствует одноименному атрибуту этого класса (отметим, что это именно атрибуты класса, а не атрибуты экземпляра). Так, сигнал clicked
, генерируемый при щелчке мышью, соответствует атрибуту clicked
. Каждый такой атрибут хранит экземпляр особого класса, представляющего соответствующий сигнал.
Чтобы назначить сигналу обработчик, следует использовать метод connect()
, унаследован- ный от класса
QObject
. Форматы вызова этого метода таковы:
<Компонент>.<Сигнал>.connect(<Обработчик>[, <Тип соединения>])
<Компонент>.<Сигнал>[<Тип>].connect(<Обработчик>[, <Тип соединения>])
Здесь мы назначаем
<Обработчик>
для параметра
<Сигнал>
, генерируемого параметром
<Компонент>
. В качестве обработчика можно указать:
ссылку на пользовательскую функцию;
ссылку на метод класса;
ссылку на экземпляр класса, в котором определен метод
__call__()
;
анонимную функцию;
ссылку на слот класса.
Вот пример назначения функции on_clicked_button()
в качестве обработчика сигнала clicked кнопки button
: button.clicked.connect(on_clicked_button)
396
Часть II. Библиотека PyQt 5
Сигналы могут принимать произвольное число параметров, каждый из которых может от- носиться к любому типу данных. При этом бывает и так, что в классе существуют два сиг- нала с одинаковыми наименованиями, но разными наборами параметров. Тогда следует дополнительно в квадратных скобках указать
<Тип>
данных, принимаемых сигналом, — либо просто написав его наименование, либо задав его в виде строки. Например, оба сле- дующих выражения назначают обработчик сигнала, принимающего один параметр логиче- ского типа: button.clicked[bool].connect(on_clicked_button) button.clicked["bool"].connect(on_clicked_button)
Одному и тому же сигналу можно назначить произвольное количество обработчиков. Это иллюстрирует код из листинга 19.1, где сигналу clicked кнопки назначены сразу четыре обработчика.
Листинг 19.1. Назначение сигналу нескольких обработчиков
# -*- coding: utf-8 -*- from PyQt5 import QtWidgets import sys def on_clicked(): print("Кнопка нажата. Функция on_clicked()") class MyClass(): def __init__(self, x=0): self.x = x def __call__(self): print("Кнопка нажата. Метод MyClass.__call__()") print("x =", self.x) def on_clicked(self): print("Кнопка нажата. Метод MyClass.on_clicked()") obj = MyClass() app = QtWidgets.QApplication(sys.argv) button = QtWidgets.QPushButton("Нажми меня")
# Назначаем обработчиком функцию button.clicked.connect(on_clicked)
# Назначаем обработчиком метод класса button.clicked.connect(obj.on_clicked)
# Назначаем обработчиком ссылку на экземпляр класса button.clicked.connect(MyClass(10))
# Назначаем обработчиком анонимную функцию button.clicked.connect(lambda: MyClass(5)()) button.show() sys.exit(app.exec_())
В четвертом обработчике мы использовали анонимную функцию. В ней мы сначала создаем объект класса
MyClass
, передав ему в качестве параметра число
5
, после чего сразу же вы- зываем его как функцию, указав после конструктора пустые скобки: button.clicked.connect(lambda: MyClass(5)())
Глава 19. Обработка сигналов и событий
397
Результат выполнения в окне консоли при щелчке на кнопке:
Кнопка нажата. Функция on_clicked()
Кнопка нажата. Метод MyClass.on_clicked()
Кнопка нажата. Метод MyClass.__call__() x = 10
Кнопка нажата. Метод MyClass.__call__() x = 5
Классы PyQt 5 поддерживают ряд методов, специально предназначенных для использова- ния в качестве обработчиков сигналов. Такие методы называются слотами. Например, класс
QApplication поддерживает слот quit()
, завершающий текущее приложение. В лис- тинге 19.2 показан код, использующий этот слот.
Листинг 19.2. Использование слота
# -*- coding: utf-8 -*- from PyQt5 import QtWidgets import sys app = QtWidgets.QApplication(sys.argv) button = QtWidgets.QPushButton("Завершить работу") button.clicked.connect(app.quit) button.show() sys.exit(app.exec_())
Любой пользовательский метод можно сделать слотом, для чего необходимо перед его оп- ределением вставить декоратор
@pyqtSlot()
. Формат декоратора:
@QtCore.pyqtSlot(*<Типы данных>, name=None, result=None)
В параметре
<Типы данных>
через запятую указываются типы данных параметров, прини- маемых слотом, — например: bool или int
. При задании типа данных C++ его название не- обходимо указать в виде строки. Если метод не принимает параметров, параметр
<Типы дан- ных>
не указывается. В именованном параметре name можно передать название слота в виде строки — это название станет использоваться вместо названия метода, а если параметр name не задан, название слота будет совпадать с названием метода. Именованный параметр result предназначен для указания типа данных, возвращаемых методом, — если параметр не задан, то метод ничего не возвращает. Чтобы создать перегруженную версию слота, декоратор указывается последовательно несколько раз с разными типами данных. Пример использования декоратора
@pyqtSlot()
приведен в листинге 19.3.
Листинг 19.3. Использование декоратора
@pyqtSlot()
# -*- coding: utf-8 -*- from PyQt5 import QtCore, QtWidgets import sys class MyClass(QtCore.QObject): def __init__(self):
QtCore.QObject.__init__(self)
398
Часть II. Библиотека PyQt 5
@QtCore.pyqtSlot() def on_clicked(self): print("Кнопка нажата. Слот on_clicked()")
@QtCore.pyqtSlot(bool, name="myslot") def on_clicked2(self, status): print("Кнопка нажата. Слот myslot(bool)", status) obj = MyClass() app = QtWidgets.QApplication(sys.argv) button = QtWidgets.QPushButton("Нажми меня") button.clicked.connect(obj.on_clicked) button.clicked.connect(obj.myslot) button.show() sys.exit(app.exec_())
PyQt не требует обязательного превращения в слот метода, который будет использоваться как обработчик сигнала. Однако это рекомендуется сделать, т. к. вызов слота в этом случае выполняется быстрее, чем вызов обычного метода.
Необязательный параметр
<Тип соединения>
метода connect()
определяет тип соединения между сигналом и обработчиком. На этот параметр следует обратить особое внимание при использовании в приложении нескольких потоков, т. к. изменять GUI-поток из другого потока нельзя. В параметре можно указать один из следующих атрибутов класса
QtCore.Qt
:
AutoConnection
—
0
— значение по умолчанию. Если источник сигнала и обработчик находятся в одном потоке, то оно эквивалентно значению
DirectConnection
, а если в разных потоках, то —
QueuedConnection
;
DirectConnection
—
1
— обработчик вызывается сразу после генерации сигнала и вы- полняется в потоке его источника;
QueuedConnection
—
2
— сигнал помещается в очередь обработки событий, а его обра- ботчик выполняется в потоке приемника сигнала;
BlockingQueuedConnection
—
4
— аналогично значению
QueuedConnection за тем исклю- чением, что поток блокируется на время обработки сигнала. Обратите внимание, что ис- точник и обработчик сигнала обязательно должны быть расположены в разных потоках;
UniqueConnection
—
0x80
— указывает, что обработчик можно назначить только один раз. Этот атрибут с помощью оператора
| может быть объединен с любым из представ- ленных ранее флагов:
# Эти два обработчика будут успешно назначены и выполнены button.clicked.connect(on_clicked) button.clicked.connect(on_clicked)
# А эти два обработчика назначены не будут button.clicked.connect(on_clicked, Qt.Core.Qt.AutoConnection |
QtCore.Qt.UniqueConnection) button.clicked.connect(on_clicked, Qt.Core.Qt.AutoConnection |
QtCore.Qt.UniqueConnection)
# Тем не менее, эти два обработчика будут назначены, поскольку они разные button.clicked.connect(on_clicked, Qt.Core.Qt.AutoConnection |
QtCore.Qt.UniqueConnection) button.clicked.connect(obj.on_clicked, Qt.Core.Qt.AutoConnection |
QtCore.Qt.UniqueConnection)
Глава 19. Обработка сигналов и событий
399 19.2. Блокировка и удаление обработчика
Для блокировки и удаления обработчиков предназначены следующие методы класса
QObject
:
blockSignals(<Флаг>)
— временно блокирует прием сигналов, если параметр имеет зна- чение
True
, и снимает блокировку, если параметр имеет значение
False
. Метод возвра- щает логическое представление предыдущего состояния соединения;
signalsBlocked()
— возвращает значение
True
, если блокировка сигналов установлена, и
False
— в противном случае;
disconnect()
— удаляет обработчик. Форматы метода:
<Компонент>.<Сигнал>.disconnect([<Обработчик>])
<Компонент>.<Сигнал>[<Тип>].disconnect([<Обработчик>])
Если параметр
<Обработчик>
не указан, удаляются все обработчики, назначенные ранее, в противном случае удаляется только указанный обработчик. Параметр
<Тип>
указывает- ся лишь в том случае, если существуют сигналы с одинаковыми именами, но прини- мающие разные параметры: button.clicked.disconnect() button.clicked[bool].disconnect(on_clicked_button) button.clicked["bool"].disconnect(on_clicked_button)
Создадим окно с четырьмя кнопками (листинг 19.4). Для кнопки Нажми меня назначим обработчик сигнала clicked
. Чтобы информировать о нажатии кнопки, выведем сообщение в окно консоли. Для кнопок Блокировать, Разблокировать и Удалить обработчик созда- дим обработчики, которые будут изменять статус обработчика для кнопки Нажми меня.
Листинг 19.4. Блокировка и удаление обработчика
# -*- coding: utf-8 -*- from PyQt5 import QtCore, QtWidgets class MyWindow(QtWidgets.QWidget): def __init__(self, parent=None):
QtWidgets.QWidget.__init__(self, parent) self.setWindowTitle("Блокировка и удаление обработчика") self.resize(300, 150) self.button1 = QtWidgets.QPushButton("Нажми меня") self.button2 = QtWidgets.QPushButton("Блокировать") self.button3 = QtWidgets.QPushButton("Разблокировать") self.button4 = QtWidgets.QPushButton("Удалить обработчик") self.button3.setEnabled(False) vbox = QtWidgets.QVBoxLayout() vbox.addWidget(self.button1) vbox.addWidget(self.button2) vbox.addWidget(self.button3) vbox.addWidget(self.button4) self.setLayout(vbox) self.button1.clicked.connect(self.on_clicked_button1)
400
Часть II. Библиотека PyQt 5 self.button2.clicked.connect(self.on_clicked_button2) self.button3.clicked.connect(self.on_clicked_button3) self.button4.clicked.connect(self.on_clicked_button4)
@QtCore.pyqtSlot() def on_clicked_button1(self): print("Нажата кнопка button1")
@QtCore.pyqtSlot() def on_clicked_button2(self): self.button1.blockSignals(True) self.button2.setEnabled(False) self.button3.setEnabled(True)
@QtCore.pyqtSlot() def on_clicked_button3(self): self.button1.blockSignals(False) self.button2.setEnabled(True) self.button3.setEnabled(False)
@QtCore.pyqtSlot() def on_clicked_button4(self): self.button1.clicked.disconnect(self.on_clicked_button1) self.button2.setEnabled(False) self.button3.setEnabled(False) self.button4.setEnabled(False) if __name__ == "__main__": import sys app = QtWidgets.QApplication(sys.argv) window = MyWindow() window.show() sys.exit(app.exec_())
Если нажать кнопку Нажми меня, в окно консоли будет выведена строка
Нажата кнопка button1
. Нажатие кнопки Блокировать производит блокировку обработчика — теперь при нажатии кнопки Нажми меня никаких сообщений в окно консоли не выводится. Отменить блокировку можно с помощью кнопки Разблокировать. Нажатие кнопки Удалить обра- ботчик производит полное удаление обработчика — в этом случае, чтобы обрабатывать нажатие кнопки Нажми меня, необходимо заново назначить обработчик.
Также можно отключить генерацию сигнала, сделав компонент недоступным с помощью следующих методов из класса
QWidget
:
setEnabled(<Флаг>)
— если в параметре указано значение
False
, компонент станет не- доступным. Чтобы сделать компонент опять доступным, следует передать значение
True
;
setDisabled(<Флаг>)
— если в параметре указано значение
True
, компонент станет не- доступным. Чтобы сделать компонент опять доступным, следует передать значение
False
Проверить, доступен компонент или нет, позволяет метод isEnabled()
. Он возвращает зна- чение
True
, если компонент доступен, и
False
— в противном случае.
Глава 19. Обработка сигналов и событий
401 19.3. Генерация сигналов
В некоторых случаях необходимо сгенерировать сигнал программно. Например, при запол- нении последнего текстового поля и нажатии клавиши можно имитировать нажатие кнопки ОK и тем самым выполнить подтверждение ввода пользователя. Осуществить гене- рацию сигнала из программы позволяет метод emit()
класса
QObject
. Форматы этого ме- тода:
<Компонент>.<Сигнал>.emit([<Данные>])
<Компонент>.<Сигнал>[<Тип>].emit([<Данные>])
Метод emit()
всегда вызывается у объекта, которому посылается сигнал: button.clicked.emit()
Сигналу и, соответственно, его обработчику можно передать данные, указав их в вызове метода emit()
: button.clicked[bool].emit(False) button.clicked["bool"].emit(False)
Также мы можем создавать свои собственные сигналы. Для этого следует определить в классе атрибут, чье имя совпадет с наименованием сигнала. Отметим, что это должен быть атрибут класса, а не экземпляра. Далее мы присвоим вновь созданному атрибуту результат, возвращенный функцией pyqtSignal()
из модуля
QtCore
. Формат функции:
<Объект сигнала> = pyqtSignal(*<Типы данных>[, name=<Имя сигнала>])
В параметре
<Типы данных>
через запятую указываются названия типов данных, передавае- мых сигналу, — например: bool или int
: mysignal1 = QtCore.pyqtSignal(int) mysignal2 = QtCore.pyqtSignal(int, str)
При использовании типа данных C++ его название необходимо указать в виде строки: mysignal3 = QtCore.pyqtSignal("QDate")
Если сигнал не принимает параметров, параметр
<Типы данных>
не указывается.
Сигнал может иметь несколько перегруженных версий, различающихся количеством и типом принимаемых параметров. В этом случае типы параметров указываются внутри квадратных скобок. Вот пример сигнала, передающего данные типа int или str
: mysignal4 = QtCore.pyqtSignal([int], [str])
По умолчанию название создаваемого сигнала будет совпадать с названием атрибута клас- са. Однако мы можем указать для сигнала другое название, после чего он будет доступен под двумя названиями: совпадающим с именем атрибута класса и заданным нами. Для ука- зания названия сигнала применяется параметр name
: mysignal = QtCore.pyqtSignal(int, name="mySignal")
В качестве примера создадим окно с двумя кнопками (листинг 19.5), которым назначим обработчики сигнала clicked
(нажатие кнопки). Внутри обработчика щелчка на первой кнопке сгенерируем два сигнала: первый будет имитировать нажатие второй кнопки, а вто- рой станет пользовательским, привязанным к окну. Внутри обработчиков выведем сообще- ния в окно консоли.
402
Часть II. Библиотека PyQt 5
Листинг 19.5. Генерация сигнала из программы
# -*- coding: utf-8 -*- from PyQt5 import QtCore, QtWidgets class MyWindow(QtWidgets.QWidget): mysignal = QtCore.pyqtSignal(int, int) def __init__(self, parent=None):
QtWidgets.QWidget.__init__(self, parent) self.setWindowTitle("Генерация сигнала из программы") self.resize(300, 100) self.button1 = QtWidgets.QPushButton("Нажми меня") self.button2 = QtWidgets.QPushButton("Кнопка 2") vbox = QtWidgets.QVBoxLayout() vbox.addWidget(self.button1) vbox.addWidget(self.button2) self.setLayout(vbox) self.button1.clicked.connect(self.on_clicked_button1) self.button2.clicked.connect(self.on_clicked_button2) self.mysignal.connect(self.on_mysignal) def on_clicked_button1(self): print("Нажата кнопка button1")
# Генерируем сигналы self.button2.clicked[bool].emit(False) self.mysignal.emit(10, 20) def on_clicked_button2(self): print("Нажата кнопка button2") def on_mysignal(self, x, y): print("Обработан пользовательский сигнал mysignal()") print("x =", x, "y =", y) if __name__ == "__main__": import sys app = QtWidgets.QApplication(sys.argv) window = MyWindow() window.show() sys.exit(app.exec_())
Результат выполнения после нажатия первой кнопки:
Нажата кнопка button1
Нажата кнопка button2
Обработан пользовательский сигнал mysignal() x = 10 y = 20
Вместо конкретного типа принимаемого сигналом параметра можно указать тип
QVariant из модуля
QtCore
. В этом случае при генерации сигнала допускается передавать ему данные любого типа. Вот пример создания и генерирования сигнала, принимающего параметр любого типа: mysignal = QtCore.pyqtSignal(QtCore.QVariant) self.mysignal.emit(20)
Глава 19. Обработка сигналов и событий
403 self.mysignal.emit("Привет!") self.mysignal.emit([1, "2"])
Сгенерировать сигнал можно не только с помощью метода emit()
. Некоторые компоненты предоставляют методы, которые посылают сигнал. Например, у кнопок существует метод click()
. Используя этот метод, инструкцию: button.clicked.emit() можно записать следующим образом: button.click()
Более подробно такие методы мы будем рассматривать при изучении конкретных компо- нентов.
19.4. Передача данных в обработчик
При назначении обработчика в метод connect()
передается ссылка на функцию или метод.
Если после названия функции (метода) указать внутри круглых скобок какой-либо пара- метр, то это приведет к вызову функции (метода) и вместо ссылки будет передан результат ее выполнения, что вызовет ошибку. Передать данные в обработчик можно следующими способами:
создать анонимную функцию и внутри ее выполнить вызов обработчика с параметрами.
Вот пример передачи обработчику числа
10
: self.button1.clicked.connect(lambda : self.on_clicked_button1(10))
Если передаваемое обработчику значение вычисляется в процессе выполнения кода, переменную, хранящую это значение, следует указывать в анонимной функции как зна- чение по умолчанию, иначе функции будет передана ссылка на это значение, а не оно само: y = 10 self.button1.clicked.connect(lambda x=y: self.on_clicked_button1(x))
передать ссылку на экземпляр класса, внутри которого определен метод
__call__()
Передаваемое значение указывается в качестве параметра конструктора этого класса: class MyClass(): def __init__(self, x=0): self.x = x def __call__(self): print("x =", self.x) self.button1.clicked.connect(MyClass(10))
передать ссылку на обработчик и данные в функцию partial()
из модуля functools
Формат функции: partial(<Функция>[, *<Неименованные параметры>][, **<Именованные параметры>])
Пример передачи параметра в обработчик: from functools import partial self.button1.clicked.connect(partial(self.on_clicked_button1, 10))
404
Часть II. Библиотека PyQt 5
Если при генерации сигнала передается предопределенное значение, то оно будет дос- тупно в обработчике после остальных параметров. Назначим обработчик сигнала clicked
, принимающего логический параметр, и дополнительно передадим число: self.button1.clicked.connect(partial(self.on_clicked_button1, 10))
Обработчик будет иметь следующий вид: def on_clicked_button1(self, x, status): print("Нажата кнопка button1", x, status)
Результат выполнения:
Нажата кнопка button1 10 False
19.5. Использование таймеров
Таймеры позволяют через заданный интервал времени выполнять метод с предопределен- ным названием timerEvent()
. Для назначения таймера используется метод startTimer()
класса
QObject
. Формат метода:
1 ... 33 34 35 36 37 38 39 40 ... 83
376
Часть II. Библиотека PyQt 5
>>> r2 = r1.marginsAdded(m)
>>> r2
PyQt5.QtCore.QRect(5, 13, 410, 304)
>>> r1
PyQt5.QtCore.QRect(10, 15, 400, 300)
marginsRemoved()
— то же самое, что marginsAdded()
, но уменьшает новую область на заданные величины границ:
>>> r1 = QtCore.QRect(10, 15, 400, 300)
>>> m = QtCore.QMargins(2, 10, 2, 10)
>>> r2 = r1.marginsRemoved(m)
>>> r2
PyQt5.QtCore.QRect(12, 25, 396, 280)
>>> r1
PyQt5.QtCore.QRect(10, 15, 400, 300)
Переместить область при изменении координат позволяют следующие методы:
moveTo(
, moveTo(
, moveLeft(
и moveTop(
— задают новые координаты левого верхнего угла:
>>> r = QtCore.QRect(10, 15, 400, 300)
>>> r.moveTo(0, 0); r
PyQt5.QtCore.QRect(0, 0, 400, 300)
>>> r.moveTo(QtCore.QPoint(10, 10)); r
PyQt5.QtCore.QRect(10, 10, 400, 300)
>>> r.moveLeft(5); r.moveTop(0); r
PyQt5.QtCore.QRect(5, 0, 400, 300)
moveRight(
и moveBottom(
— задают новые координаты правого нижнего угла;
moveTopLeft(
— задает новые координаты левого верхнего угла;
moveTopRight(
— задает новые координаты правого верхнего угла;
moveBottomLeft(
— задает новые координаты левого нижнего угла;
moveBottomRight(
— задает новые координаты правого нижнего угла.
Примеры:
>>> r = QtCore.QRect(10, 15, 400, 300)
>>> r.moveTopLeft(QtCore.QPoint(0, 0)); r
PyQt5.QtCore.QRect(0, 0, 400, 300)
>>> r.moveBottomRight(QtCore.QPoint(599, 499)); r
PyQt5.QtCore.QRect(200, 200, 400, 300)
moveCenter(
— задает новые координаты центра;
translate(<Сдвиг по оси X>, <Сдвиг по оси Y>)
и translate(
— задают но- вые координаты левого верхнего угла относительно текущего значения координат:
>>> r = QtCore.QRect(0, 0, 400, 300)
>>> r.translate(20, 15); r
PyQt5.QtCore.QRect(20, 15, 400, 300)
>>> r.translate(QtCore.QPoint(10, 5)); r
PyQt5.QtCore.QRect(30, 20, 400, 300)
Глава 18. Управление окном приложения
377
translated(<Сдвиг по оси X>, <Сдвиг по оси Y>)
и translated(
— анало- гичен методу translate()
, но возвращает новый экземпляр класса
QRect
, а не изменяет текущий;
adjust(
— задает новые координаты левого верхнего и правого нижнего углов относительно текущих значений координат:
>>> r = QtCore.QRect(0, 0, 400, 300)
>>> r.adjust(10, 5, 10, 5); r
PyQt5.QtCore.QRect(10, 5, 400, 300)
adjusted(
— аналогичен методу adjust()
, но возвращает но- вый экземпляр класса
QRect
, а не изменяет текущий.
Для получения параметров области предназначены следующие методы:
left()
и x()
— возвращают координату левого верхнего угла по оси
X
;
top()
и y()
— возвращают координату левого верхнего угла по оси
Y
;
right()
и bottom()
— возвращают координаты правого нижнего угла по осям
X
и
Y
соот- ветственно;
width()
и height()
— возвращают ширину и высоту соответственно;
size()
— возвращает размеры в виде экземпляра класса
QSize
Примеры:
>>> r = QtCore.QRect(10, 15, 400, 300)
>>> r.left(), r.top(), r.x(), r.y(), r.right(), r.bottom()
(10, 15, 10, 15, 409, 314)
>>> r.width(), r.height(), r.size()
(400, 300, PyQt5.QtCore.QSize(400, 300))
topLeft()
— возвращает координаты левого верхнего угла;
topRight()
— возвращает координаты правого верхнего угла;
bottomLeft()
— возвращает координаты левого нижнего угла;
bottomRight()
— возвращает координаты правого нижнего угла.
Примеры:
>>> r = QtCore.QRect(10, 15, 400, 300)
>>> r.topLeft(), r.topRight()
(PyQt5.QtCore.QPoint(10, 15), PyQt5.QtCore.QPoint(409, 15))
>>> r.bottomLeft(), r.bottomRight()
(PyQt5.QtCore.QPoint(10, 314), PyQt5.QtCore.QPoint(409, 314))
center()
— возвращает координаты центра области. Например, вывести окно по центру доступной области экрана можно так: desktop = QtWidgets.QApplication.desktop() window.move(desktop.availableGeometry().center() - window.rect().center())
getRect()
— возвращает кортеж с координатами левого верхнего угла и размерами об- ласти;
getCoords()
— возвращает кортеж с координатами левого верхнего и правого нижнего углов:
378
Часть II. Библиотека PyQt 5
>>> r = QtCore.QRect(10, 15, 400, 300)
>>> r.getRect(), r.getCoords()
((10, 15, 400, 300), (10, 15, 409, 314))
Прочие методы:
isNull()
— возвращает
True
, если ширина и высота равны нулю, и
False
— в противном случае;
isValid()
— возвращает
True
, если left() < right()
и top() < bottom()
, и
False
— в противном случае;
isEmpty()
— возвращает
True
, если left() > right()
или top() > bottom()
, и
False
— в противном случае;
normalized()
— исправляет ситуацию, при которой left() > right()
или top() > bottom()
, и возвращает новый экземпляр класса
QRect
:
>>> r = QtCore.QRect(QtCore.QPoint(409, 314), QtCore.QPoint(10, 15))
>>> r
PyQt5.QtCore.QRect(409, 314, -398, -298)
>>> r.normalized()
PyQt5.QtCore.QRect(10, 15, 400, 300)
contains(
и contains(
— возвращает
True
, если точка с указанными координатами расположена внутри области или на ее границе, и
False
— в противном случае. Если во втором параметре указано значение
True
, то точка должна быть расположена только внутри области, а не на ее границе. Значение парамет- ра по умолчанию —
False
:
>>> r = QtCore.QRect(0, 0, 400, 300)
>>> r.contains(0, 10), r.contains(0, 10, True)
(True, False)
contains(
— возвращает
True
, если указанная область расположена внутри текущей области или на ее краю, и
False
— в противном случае. Если во втором параметре указано значение
True
, то указанная область должна быть расположена толь- ко внутри текущей области, а не на ее краю. Значение параметра по умолчанию —
False
:
>>> r = QtCore.QRect(0, 0, 400, 300)
>>> r.contains(QtCore.QRect(0, 0, 20, 5))
True
>>> r.contains(QtCore.QRect(0, 0, 20, 5), True)
False
intersects(
— возвращает
True
, если указанная область пересекается с текущей областью, и
False
— в противном случае;
intersected(
— возвращает область, которая расположена на пересечении те- кущей и указанной областей:
>>> r = QtCore.QRect(0, 0, 20, 20)
>>> r.intersects(QtCore.QRect(10, 10, 20, 20))
True
>>> r.intersected(QtCore.QRect(10, 10, 20, 20))
PyQt5.QtCore.QRect(10, 10, 10, 10)
Глава 18. Управление окном приложения
379
united(
— возвращает область, которая охватывает текущую и указанную об- ласти:
>>> r = QtCore.QRect(0, 0, 20, 20)
>>> r.united(QtCore.QRect(30, 30, 20, 20))
PyQt5.QtCore.QRect(0, 0, 50, 50)
Над двумя экземплярами класса
QRect можно выполнять операции
&
и
&=
(пересечение),
|
и
|=
(объединение), in
(проверка на вхождение),
==
и
!=
Пример:
>>> r1, r2 = QtCore.QRect(0, 0, 20, 20), QtCore.QRect(10, 10, 20, 20)
>>> r1 & r2, r1 | r2
(PyQt5.QtCore.QRect(10, 10, 10, 10), PyQt5.QtCore.QRect(0, 0, 30, 30))
>>> r1 in r2, r1 in QtCore.QRect(0, 0, 30, 30)
(False, True)
>>> r1 == r2, r1 != r2
(False, True)
Помимо этого, поддерживаются операторы
+
и
-
, выполняющие увеличение и уменьшение области на заданные величины границ, которые должны быть заданы в виде объекта класса
QMargins
:
>>> r = QtCore.QRect(10, 15, 400, 300)
>>> m = QtCore.QMargins(5, 15, 5, 15)
>>> r + m
PyQt5.QtCore.QRect(5, 0, 410, 330)
>>> r - m
PyQt5.QtCore.QRect(15, 30, 390, 270)
18.6. Разворачивание и сворачивание окна
В заголовке окна расположены кнопки Свернуть и Развернуть, с помощью которых мож- но свернуть окно в значок на панели задач или развернуть его на весь экран. Выполнить подобные действия из программы позволяют следующие методы класса
QWidget
:
showMinimized()
— сворачивает окно на панель задач. Эквивалентно нажатию кнопки
Свернуть в заголовке окна;
showMaximized()
— разворачивает окно до максимального размера. Эквивалентно нажа- тию кнопки Развернуть в заголовке окна;
showFullScreen()
— включает полноэкранный режим отображения окна. Окно отобра- жается без заголовка и границ;
showNormal()
— отменяет сворачивание, максимальный размер и полноэкранный режим, возвращая окно к изначальным размерам;
activateWindow()
— делает окно активным (т. е. имеющим фокус ввода). В Windows, если окно было ранее свернуто в значок на панель задач, оно не будет развернуто в из- начальный вид;
setWindowState(<Флаги>)
— изменяет состояние окна в зависимости от переданных фла- гов. В качестве параметра указывается комбинация следующих атрибутов из класса
QtCore.Qt через побитовые операторы:
380
Часть II. Библиотека PyQt 5
•
WindowNoState
— нормальное состояние окна;
•
WindowMinimized
— окно свернуто;
•
WindowMaximized
— окно максимально развернуто;
•
WindowFullScreen
— полноэкранный режим;
•
WindowActive
— окно имеет фокус ввода, т. е. является активным.
Например, включить полноэкранный режим можно так: window.setWindowState((window.windowState() &
(QtCore.Qt.WindowMinimized | QtCore.Qt.WindowMaximized))
| QtCore.Qt.WindowFullScreen)
Проверить текущий статус окна позволяют следующие методы:
isMinimized()
— возвращает
True
, если окно свернуто, и
False
— в противном случае;
isMaximized()
— возвращает
True
, если окно раскрыто до максимальных размеров, и
False
— в противном случае;
isFullScreen()
— возвращает
True
, если включен полноэкранный режим, и
False
— в противном случае;
isActiveWindow()
— возвращает
True
, если окно имеет фокус ввода, и
False
— в про- тивном случае;
windowState()
— возвращает комбинацию флагов, обозначающих текущий статус окна.
Пример проверки использования полноэкранного режима: if window.windowState() & QtCore.Qt.WindowFullScreen: print("Полноэкранный режим")
Пример разворачивания и сворачивания окна приведен в листинге 18.4.
Листинг 18.4. Разворачивание и сворачивание окна
# -*- coding: utf-8 -*- from PyQt5 import QtCore, QtWidgets class MyWindow(QtWidgets.QWidget): def __init__(self, parent=None):
QtWidgets.QWidget.__init__(self, parent) self.btnMin = QtWidgets.QPushButton("Свернуть") self.btnMax = QtWidgets.QPushButton("Развернуть") self.btnFull = QtWidgets.QPushButton("Полный экран") self.btnNormal = QtWidgets.QPushButton("Нормальный размер") vbox = QtWidgets.QVBoxLayout() vbox.addWidget(self.btnMin) vbox.addWidget(self.btnMax) vbox.addWidget(self.btnFull) vbox.addWidget(self.btnNormal) self.setLayout(vbox) self.btnMin.clicked.connect(self.on_min) self.btnMax.clicked.connect(self.on_max) self.btnFull.clicked.connect(self.on_full) self.btnNormal.clicked.connect(self.on_normal)
Глава 18. Управление окном приложения
381 def on_min(self): self.showMinimized() def on_max(self): self.showMaximized() def on_full(self): self.showFullScreen() def on_normal(self): self.showNormal() if __name__ == "__main__": import sys app = QtWidgets.QApplication(sys.argv) window = MyWindow() window.setWindowTitle("Разворачивание и сворачивание окна") window.resize(300, 100) window.show() sys.exit(app.exec_())
18.7. Управление прозрачностью окна
Сделать окно полупрозрачным позволяет метод setWindowOpacity()
класса
QWidget
. Формат метода: setWindowOpacity(<Вещественное число от 0.0 до 1.0>)
Число
0.0
соответствует полностью прозрачному окну, а число
1.0
— отсутствию прозрач- ности. Для получения степени прозрачности окна из программы предназначен метод windowOpacity()
. Выведем окно со степенью прозрачности
0.5
(листинг 18.5).
Листинг 18.5. Полупрозрачное окно
# -*- coding: utf-8 -*- from PyQt5 import QtWidgets import sys app = QtWidgets.QApplication(sys.argv) window = QtWidgets.QWidget() window.setWindowTitle("Полупрозрачное окно") window.resize(300, 100) window.setWindowOpacity(0.5) window.show() print(window.windowOpacity()) # Выведет: 0.4980392156862745 sys.exit(app.exec_())
18.8. Модальные окна
Модальным называется окно, которое не позволяет взаимодействовать с другими окнами в том же приложении, — пока модальное окно не будет закрыто, сделать активным другое окно нельзя. Например, если в программе Microsoft Word выбрать пункт меню Файл | Со- хранить как, откроется модальное диалоговое окно, позволяющее выбрать путь и название
382
Часть II. Библиотека PyQt 5 файла, и, пока это окно не будет закрыто, вы не сможете взаимодействовать с главным ок- ном приложения.
Указать, что окно является модальным, позволяет метод setWindowModality(<Флаг>)
класса
QWidget
. В качестве параметра могут быть указаны следующие атрибуты из класса
QtCore.Qt
:
NonModal
—
0
— окно не является модальным (поведение по умолчанию);
WindowModal
—
1
— окно блокирует только родительские окна в пределах иерархии;
ApplicationModal
—
2
— окно блокирует все окна в приложении.
Окна, открытые из модального окна, не блокируются. Следует также учитывать, что метод setWindowModality()
должен быть вызван до отображения окна.
Получить текущее значение модальности позволяет метод windowModality()
. Проверить, является ли окно модальным, можно с помощью метода isModal()
— он возвращает
True
, если окно является модальным, и
False
— в противном случае.
Создадим два независимых окна. В первом окне разместим кнопку, по нажатию которой откроется модальное окно, — оно будет блокировать только первое окно, но не второе. При открытии модального окна отобразим его примерно по центру родительского окна (лис- тинг 18.6).
Листинг 18.6. Модальные окна
# -*- coding: utf-8 -*- from PyQt5 import QtCore, QtWidgets import sys def show_modal_window(): global modalWindow modalWindow = QtWidgets.QWidget(window1, QtCore.Qt.Window) modalWindow.setWindowTitle("Модальное окно") modalWindow.resize(200, 50) modalWindow.setWindowModality(QtCore.Qt.WindowModal) modalWindow.setAttribute(QtCore.Qt.WA_DeleteOnClose, True) modalWindow.move(window1.geometry().center() - modalWindow.rect().center() –
QtCore.QPoint(4, 30)) modalWindow.show() app = QtWidgets.QApplication(sys.argv) window1 = QtWidgets.QWidget() window1.setWindowTitle("Обычное окно") window1.resize(300, 100) button = QtWidgets.QPushButton("Открыть модальное окно") button.clicked.connect(show_modal_window) vbox = QtWidgets.QVBoxLayout() vbox.addWidget(button) window1.setLayout(vbox) window1.show() window2 = QtWidgets.QWidget() window2.setWindowTitle("Это окно не будет блокировано при WindowModal")
Глава 18. Управление окном приложения
383 window2.resize(500, 100) window2.show() sys.exit(app.exec_())
Если запустить приложение и нажать кнопку Открыть модальное окно, откроется окно, выровненное примерно по центру родительского окна (произвести точное выравнивание вы сможете самостоятельно). При этом получить доступ к родительскому окну можно только после закрытия модального окна, второе же окно блокировано не будет. Если заменить атрибут
WindowModal атрибутом
ApplicationModal
, оба окна будут блокированы.
Обратите внимание, что в конструктор модального окна мы передали ссылку на первое ок- но и атрибут
Window
. Если не указать ссылку, то окно блокировано не будет, а если не ука- зать атрибут, окно вообще не откроется. Кроме того, мы объявили переменную modalWindow глобальной, иначе при достижении конца функции переменная выйдет из области видимо- сти, и окно будет автоматически удалено. Чтобы объект окна автоматически удалялся при закрытии окна, атрибуту
WA_DeleteOnClose в методе setAttribute()
было присвоено значе- ние
True
Модальные окна в большинстве случаев являются диалоговыми. Для работы с такими окнами в PyQt предназначен класс
QDialog
, который автоматически выравнивает окно по центру экрана или родительского окна. Кроме того, этот класс предоставляет множество специальных методов, позволяющих дождаться закрытия окна, определить статус заверше- ния и выполнить другие действия. Подробно класс
QDialog мы рассмотрим в главе 26.
18.9. Смена значка в заголовке окна
По умолчанию в левом верхнем углу окна отображается стандартный значок. Отобразить другой значок позволяет метод setWindowIcon()
класса
QWidget
. В качестве параметра метод принимает экземпляр класса
QIcon из модуля
QtGui
(см. разд. 24.3.4).
Чтобы загрузить значок из файла, следует передать путь к файлу конструктору класса
QIcon
. Если указан относительный путь, поиск файла будет производиться относительно текущего рабочего каталога. Получить список поддерживаемых форматов файлов можно с помощью статического метода supportedImageFormats()
класса
QImageReader
, объявлен- ного в модуле
QtGui
. Метод возвращает список с экземплярами класса
QByteArray
. Получим список поддерживаемых форматов:
>>> from PyQt5 import QtGui
>>> for i in QtGui.QImageReader.supportedImageFormats(): print(str(i, "ascii").upper(), end=" ")
Вот результат выполнения этого примера на компьютере одного из авторов:
BMP CUR GIF ICNS ICO JPEG JPG PBM PGM PNG PPM SVG SVGZ TGA TIF TIFF WBMP WEBP XBM XPM
Если для окна не указать значок, будет использоваться значок приложения, установленный с помощью метода setWindowIcon()
класса
QApplication
. В качестве параметра метод также принимает экземпляр класса
QIcon
Вместо загрузки значка из файла можно воспользоваться одним из встроенных значков.
Загрузить стандартный значок позволяет следующий код: ico = window.style().standardIcon(QtWidgets.QStyle.SP_MessageBoxCritical) window.setWindowIcon(ico)
384
Часть II. Библиотека PyQt 5
Посмотреть список всех встроенных значков можно в документации к классу
QStyle
(см. https://doc.qt.io/qt-5/qstyle.html#StandardPixmap-enum).
В качестве примера создадим значок размером 16 на 16 пикселов в формате PNG и сохра- ним его в одном каталоге с программой, после чего установим этот значок для окна и всего приложения (листинг 18.7).
Листинг 18.7. Смена значка в заголовке окна
# -*- coding: utf-8 -*- from PyQt5 import QtGui, QtWidgets import sys app = QtWidgets.QApplication(sys.argv) window = QtWidgets.QWidget() window.setWindowTitle("Смена значка в заголовке окна") window.resize(300, 100) ico = QtGui.QIcon("icon.png") window.setWindowIcon(ico) # Значок для окна app.setWindowIcon(ico) # Значок приложения window.show() sys.exit(app.exec_())
18.10. Изменение цвета фона окна
Чтобы изменить цвет фона окна (или компонента), следует установить палитру с настроен- ной ролью
Window
(или
Background
). Цветовая палитра содержит цвета для каждой роли и состояния компонента. Указать состояние компонента позволяют следующие атрибуты из класса
QPalette
(модуль
QtGui
):
Active и
Normal
—
0
— компонент активен (окно находится в фокусе ввода);
Disabled
—
1
— компонент недоступен;
Inactive
—
2
— компонент неактивен (окно находится вне фокуса ввода).
Получить текущую палитру компонента позволяет его метод palette()
. Чтобы изменить цвет для какой-либо роли и состояния, следует воспользоваться методом setColor()
класса
QPalette
. Формат метода: setColor([<Состояние>, ]<Роль>, <Цвет>)
В параметре
<Роль>
указывается, для какого элемента изменяется цвет. Например, атрибут
Window
(или
Background
) изменяет цвет фона, а
WindowText
(или
Foreground
) — цвет текста.
Полный список атрибутов имеется в документации по классу
QPalette
(см. https://doc.qt.io/ qt-5/qpalette.html).
В параметре
<Цвет>
указывается цвет элемента. В качестве значения можно указать атрибут из класса
QtCore.Qt
(например, black
, white и т. д.) или экземпляр класса
QColor
(например,
QColor("red")
,
QColor("#ff0000")
,
QColor(255, 0, 0)
и др.).
После настройки палитры необходимо вызвать метод setPalette()
компонента и передать этому методу измененный объект палитры. Следует помнить, что компоненты-потомки по умолчанию имеют прозрачный фон и не перерисовываются автоматически. Чтобы вклю-
Глава 18. Управление окном приложения
385 чить перерисовку, необходимо передать значение
True методу setAutoFillBackground()
окна.
Изменить цвет фона также можно с помощью CSS-атрибута background-color
. Для этого следует передать таблицу стилей в метод setStyleSheet()
компонента. Таблицы стилей могут быть внешними (подключение через командную строку), установленными на уровне приложения (с помощью метода setStyleSheet()
класса
QApplication
) или установленными на уровне компонента (с помощью метода setStyleSheet()
класса
QWidget
). Атрибуты, установленные последними, обычно перекрывают значения аналогичных атрибутов, ука- занных ранее. Если вы занимались веб-программированием, то язык CSS (каскадные табли- цы стилей) вам уже знаком, а если нет, придется дополнительно его изучить.
Создадим окно с надписью. Для активного окна установим зеленый цвет, а для неактивно- го — красный. Цвет фона надписи сделаем белым. Для изменения фона окна используем палитру, а для изменения цвета фона надписи — CSS-атрибут background-color
(лис- тинг 18.8).
Листинг 18.8. Изменение цвета фона окна
# -*- coding: utf-8 -*- from PyQt5 import QtCore, QtGui, QtWidgets import sys app = QtWidgets.QApplication(sys.argv) window = QtWidgets.QWidget() window.setWindowTitle("Изменение цвета фона окна") window.resize(300, 100) pal = window.palette() pal.setColor(QtGui.QPalette.Normal, QtGui.QPalette.Window,
QtGui.QColor("#008800")) pal.setColor(QtGui.QPalette.Inactive, QtGui.QPalette.Window,
QtGui.QColor("#ff0000")) window.setPalette(pal) label = QtWidgets.QLabel("Текст надписи") label.setAlignment(QtCore.Qt.AlignHCenter) label.setStyleSheet("background-color: #ffffff;") label.setAutoFillBackground(True) vbox = QtWidgets.QVBoxLayout() vbox.addWidget(label) window.setLayout(vbox) window.show() sys.exit(app.exec_())
18.11. Вывод изображения в качестве фона
В качестве фона окна (или компонента) можно использовать изображение. Для этого необ- ходимо получить текущую палитру компонента с помощью метода palette()
, а затем вы- звать метод setBrush()
класса
QPalette
. Формат метода: setBrush([<Состояние>, ]<Роль>,
386
Часть II. Библиотека PyQt 5
Первые два параметра аналогичны соответствующим параметрам в методе setColor()
, ко- торый мы рассматривали в предыдущем разделе. В третьем параметре указывается кисть — экземпляр класса
QBrush из модуля
QtGui
. Форматы конструктора класса:
<Объект> = QBrush(<Стиль кисти>)
<Объект> = QBrush(<Цвет>[, <Стиль кисти>=SolidPattern])
<Объект> = QBrush(<Цвет>,
<Объект> = QBrush(
<Объект> = QBrush(
<Объект> = QBrush(
<Объект> = QBrush(
В параметре
<Стиль кисти>
указываются атрибуты из класса
QtCore.Qt
, задающие стиль кисти, — например:
NoBrush
,
SolidPattern
,
Dense1Pattern
,
Dense2Pattern
,
Dense3Pattern
,
Dense4Pattern
,
Dense5Pattern
,
Dense6Pattern
,
Dense7Pattern
,
CrossPattern и др. С по- мощью этого параметра можно сделать цвет сплошным (
SolidPattern
) или имеющим тек- стуру (например, атрибут
CrossPattern задает текстуру в виде сетки).
В параметре
<Цвет>
указывается цвет кисти. В качестве значения можно указать атрибут из класса
QtCore.Qt
(например, black
, white и т. д.) или экземпляр класса
QColor
(например,
QColor("red")
,
QColor("#ff0000")
,
QColor(255, 0, 0)
и др.). При этом установка сплошного цвета фона окна может выглядеть так: pal = window.palette() pal.setBrush(QtGui.QPalette.Normal, QtGui.QPalette.Window,
QtGui.QBrush(QtGui.QColor("#008800"), QtCore.Qt.SolidPattern)) window.setPalette(pal)
Параметры
и
позволяют передать объекты изображений. Конструкторы этих классов принимают путь к файлу — абсолютный или относительный.
Параметр
позволяет создать кисть на основе другой кисти, а параметр
— на основе градиента, представленного объектом класса
QGradient
(см. главу 24).
После настройки палитры необходимо вызвать метод setPalette()
и передать ему изме- ненный объект палитры. Следует помнить, что компоненты-потомки по умолчанию имеют прозрачный фон и не перерисовываются автоматически. Чтобы включить перерисовку, не- обходимо передать значение
True в метод setAutoFillBackground()
Указать, какое изображение используется в качестве фона, также можно с помощью CSS- атрибутов background и background-image
. С помощью CSS-атрибута background-repeat можно дополнительно указать режим повтора фонового рисунка. Он может принимать зна- чения repeat
(повтор по горизонтали и вертикали), repeat-x
(повтор только по горизонта- ли), repeat-y
(повтор только по вертикали) и no-repeat
(не повторяется).
Создадим окно с надписью. Для активного окна установим одно изображение (с помощью изменения палитры), а для надписи — другое (с помощью CSS-атрибута background-image
)
(листинг 18.9).
Листинг 18.9. Использование изображения в качестве фона
# -*- coding: utf-8 -*- from PyQt5 import QtCore, QtGui, QtWidgets import sys
Глава 18. Управление окном приложения
387 app = QtWidgets.QApplication(sys.argv) window = QtWidgets.QWidget() window.setWindowTitle("Изображение в качестве фона") window.resize(300, 100) pal = window.palette() pal.setBrush(QtGui.QPalette.Normal, QtGui.QPalette.Window,
QtGui.QBrush(QtGui.QPixmap("background1.jpg"))) window.setPalette(pal) label = QtWidgets.QLabel("Текст надписи") label.setAlignment(QtCore.Qt.AlignCenter) label.setStyleSheet("background-image: url(background2.jpg);") label.setAutoFillBackground(True) vbox = QtWidgets.QVBoxLayout() vbox.addWidget(label) window.setLayout(vbox) window.show() sys.exit(app.exec_())
18.12. Создание окна произвольной формы
Чтобы создать окно произвольной формы, нужно выполнить следующие шаги:
1. Создать изображение нужной формы с прозрачным фоном и сохранить его, например, в формате PNG.
2. Создать экземпляр класса
QPixmap
, передав конструктору класса абсолютный или отно- сительный путь к изображению.
3. Установить изображение в качестве фона окна с помощью палитры.
4. Отделить альфа-канал с помощью метода mask()
класса
QPixmap
5. Передать получившуюся маску в метод setMask()
окна.
6. Убрать рамку окна, например, передав комбинацию следующих флагов:
QtCore.Qt.Window | QtCore.Qt.FramelessWindowHint
Если для создания окна используется класс
QLabel
, то вместо установки палитры можно передать экземпляр класса
QPixmap в метод setPixmap()
, а маску — в метод setMask()
Для примера создадим круглое окно с кнопкой, с помощью которой можно закрыть окно.
Для использования в качестве маски создадим изображение в формате PNG, установим для него прозрачный фон и нарисуем белый круг, занимающий все это изображение. Окно выведем без заголовка и границ (листинг 18.10).
Листинг 18.10. Создание окна произвольной формы
# -*- coding: utf-8 -*- from PyQt5 import QtCore, QtGui, QtWidgets import sys app = QtWidgets.QApplication(sys.argv) window = QtWidgets.QWidget() window.setWindowFlags(QtCore.Qt.Window | QtCore.Qt.FramelessWindowHint)
388
Часть II. Библиотека PyQt 5 window.setWindowTitle("Создание окна произвольной формы") window.resize(300, 300) pixmap = QtGui.QPixmap("mask.png") pal = window.palette() pal.setBrush(QtGui.QPalette.Normal, QtGui.QPalette.Window,
QtGui.QBrush(pixmap)) pal.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.Window,
QtGui.QBrush(pixmap)) window.setPalette(pal) window.setMask(pixmap.mask()) button = QtWidgets.QPushButton("Закрыть окно", window) button.setFixedSize(150, 30) button.move(75, 135) button.clicked.connect(QtWidgets.qApp.quit) window.show() sys.exit(app.exec_())
Получившееся окно можно увидеть на рис. 18.1.
Рис. 18.1. Окно круглой формы
18.13. Всплывающие подсказки
При работе с программой у пользователя могут возникать вопросы о предназначении того или иного компонента. Обычно для информирования пользователя служат надписи, распо- ложенные над компонентом или левее его. Но часто либо место в окне ограничено, либо вывод этих надписей портит весь дизайн окна. В таких случаях принято выводить текст подсказки в отдельном окне без рамки при наведении указателя мыши на компонент. Под- сказка автоматически скроется после увода курсора мыши или спустя определенное время.
В PyQt нет необходимости создавать окно с подсказкой самому и следить за перемещения- ми указателя мыши — весь этот процесс автоматизирован и максимально упрощен. Чтобы создать всплывающие подсказки для окна или любого другого компонента и управлять ими, нужно воспользоваться следующими методами класса
QWidget
:
Глава 18. Управление окном приложения
389
setToolTip(<Текст>)
— задает текст всплывающей подсказки. В качестве параметра можно указать простой текст или HTML-код. Чтобы отключить вывод подсказки, доста- точно передать в этот метод пустую строку;
toolTip()
— возвращает текст всплывающей подсказки;
setToolTipDuration(<Время>)
— задает время, в течение которого всплывающая под- сказка будет присутствовать на экране. Значение должно быть указано в миллисекундах.
Если задать значение
-1
, PyQt будет сама вычислять необходимое время, основываясь на длине текста подсказки (это поведение по умолчанию);
toolTipDuration()
— возвращает время, в течение которого всплывающая подсказка будет присутствовать на экране;
setWhatsThis(<Текст>)
— задает текст справки. Обычно этот метод используется для вывода информации большего объема, чем во всплывающей подсказке. У диалоговых окон в заголовке окна есть кнопка Справка, по нажатию которой курсор принимает вид стрелки со знаком вопроса, — чтобы в таком случае отобразить текст справки, следует нажать эту кнопку и щелкнуть на компоненте. Можно также сделать компонент актив- ным и нажать комбинацию клавиш
whatsThis()
— возвращает текст справки.
Создадим окно с кнопкой и зададим для них текст всплывающих подсказок и текст справки
(листинг 18.11).
Листинг 18.11. Всплывающие подсказки
# -*- coding: utf-8 -*- from PyQt5 import QtCore, QtWidgets import sys app = QtWidgets.QApplication(sys.argv) window = QtWidgets.QWidget(flags=QtCore.Qt.Dialog) window.setWindowTitle("Всплывающие подсказки") window.resize(300, 70) button = QtWidgets.QPushButton("Закрыть окно", window) button.setFixedSize(150, 30) button.move(75, 20) button.setToolTip("Это всплывающая подсказка для кнопки") button.setToolTipDuration(3000) window.setToolTip("Это всплывающая подсказка для окна") button.setToolTipDuration(5000) button.setWhatsThis("Это справка для кнопки") window.setWhatsThis("Это справка для окна") button.clicked.connect(QtWidgets.qApp.quit) window.show() sys.exit(app.exec_())
390
Часть II. Библиотека PyQt 5 18.14. Программное закрытие окна
В предыдущих разделах для закрытия окна мы использовали слот quit() и метод exit([returnCode=0])
объекта приложения. Однако эти методы не только закрывают теку- щее окно, но и завершают выполнение всего приложения. Чтобы закрыть только текущее окно, следует воспользоваться методом close()
класса
QWidget
. Метод возвращает значение
True
, если окно успешно закрыто, и
False
— в противном случае. Закрыть сразу все окна приложения позволяет слот closeAllWindows()
класса
QApplication
Если для окна атрибут
WA_DeleteOnClose из класса
QtCore.Qt установлен в значение
True
, после закрытия окна объект окна будет автоматически удален, в противном случае окно просто скрывается. Значение атрибута можно изменить с помощью метода setAttribute()
: window.setAttribute(QtCore.Qt.WA_DeleteOnClose, True)
После вызова метода close()
или нажатия кнопки Закрыть в заголовке окна генерируется событие
QEvent.Close
. Если внутри класса определить метод с предопределенным названи- ем closeEvent()
, это событие можно перехватить и обработать. В качестве параметра метод принимает объект класса
QCloseEvent
, который поддерживает методы accept()
(позволяет закрыть окно) и ignore()
(запрещает закрытие окна). Вызывая эти методы, можно контро- лировать процесс закрытия окна.
В качестве примера закроем окно по нажатию кнопки (листинг 18.12).
Листинг 18.12. Программное закрытие окна
# -*- coding: utf-8 -*- from PyQt5 import QtCore, QtWidgets import sys app = QtWidgets.QApplication(sys.argv) window = QtWidgets.QWidget(flags=QtCore.Qt.Dialog) window.setWindowTitle("Закрытие окна из программы") window.resize(300, 70) button = QtWidgets.QPushButton("Закрыть окно", window) button.setFixedSize(150, 30) button.move(75, 20) button.clicked.connect(window.close) window.show() sys.exit(app.exec_())
П
РИМЕЧАНИЕ
Закрыв последнее окно приложения, мы тем самым автоматически завершим и само при- ложение. Не забываем об этом.
18.15. Использование таблиц стилей CSS для оформления окон
Вернемся к методу setStyleSheet()
, упомянутому в разд. 18.10 и предназначенному для задания таблиц стилей у приложений и отдельных элементов управления. С помощью таб- лиц стилей можно задавать не только цвет фона и фоновое изображение, но и другие пара- метры оформления.
Глава 18. Управление окном приложения
391
Метод setStyleSheet()
поддерживается классами
QWidget
(и всеми его подклассами) и
QApplication
. Следовательно, его можно вызвать у:
самого приложения — тогда заданные в таблице стилей параметры оформления будут применены ко всем элементам управления всех окон приложения;
отдельного окна — тогда эти параметры будут действовать в пределах данного окна;
отдельного элемента управления — тогда они будут действовать только на этот элемент управления.
При указании таблицы стилей у приложения и окна можно использовать привычный нам по
CSS формат объявления стилей:
<Селектор> {<Определение стилей>}
<Селектор>
записывается в следующем формате:
<Основной селектор>[<Дополнительный селектор>][<Псевдокласс>][<Псевдоселектор>]
Параметр
<Основной селектор>
указывает на класс элемента управления. Его можно указать в одном из следующих форматов:
*
(звездочка) — указывает на все элементы управления (универсальный селектор). На- пример, так можно задать для всех элементов управления зеленый цвет текста:
* {color: green;}
<Класс>
— указывает на элементы управления, относящиеся к заданному
<Классу>
и его подклассам. Задание красного цвета текста для всех элементов управления, относящихся к классу
QAbstractButton и его подклассам, т. е. для командных кнопок, флажков и пе- реключателей, осуществляется так:
QAbstractButton {color: red;}
.<Класс>
— указывает только на элементы управления, относящиеся к заданному
<Классу>
, но не к его подклассам. Указание полужирного шрифта для всех элементов управления, относящихся к классу
QPushButton
(командных кнопок), но не для его под- классов, осуществляется так:
.QPushButton {font-weight: bold;}
Параметр
<Дополнительный селектор>
задает дополнительные параметры элемента управле- ния. Его форматы:
[<Свойство>="<Значение>"]
— указанное
<Свойство>
элемента управления должно иметь заданное
<Значение>
. Так мы задаем полужирный шрифт для кнопки, чье свойство default имеет значение true
, т. е. для кнопки по умолчанию:
QPushButton[default="true"] {font-weight: bold;}
#<Имя>
— указывает на элемент управления, для которого было задано
<Имя>
<Имя>
можно задать вызовом у элемента управления метода setObjectName(<Имя>)
, а полу- чить — вызовом метода objectName()
. Так выполняется указание красного цвета текста для кнопки с именем btnRed
:
QPushButton#btnRed {color: red;}
Параметр
<Псевдокласс>
указывает на отдельную составную часть сложного элемента управления. Он записывается в формате
::<Обозначение составной части>
. Вот пример ука- зания графического изображения для кнопки разворачивания раскрывающегося списка
(обозначение этой составной части — down-arrow
):
QComboBox::down-arrow {image: url(arrow.png);}
392
Часть II. Библиотека PyQt 5
Параметр
<Псевдоселектор>
указывает на состояние элемента управления (должна ли быть кнопка нажата, должен ли флажок быть установленным и т. п.). Он может быть записан в двух форматах:
:<Обозначение состояния>
— элемент управления должен находиться в указанном со- стоянии. Вот пример указания белого цвета фона для кнопки, когда она нажата (это состояние имеет обозначение pressed
):
QPushButton:pressed {background-color: white;}
:!<Обозначение состояния>
— элемент управления должен находиться в любом состоя- нии, кроме указанного. Вот пример указания желтого цвета фона для кнопки, когда она не нажата:
QPushButton:!pressed {background-color: yellow;}
Можно указать сразу несколько псевдоселекторов, расположив их непосредственно друг за другом — тогда селектор будет указывать на элемент управления, находящийся одно- временно во всех состояниях, которые обозначены этими селекторами. Вот пример ука- зания черного цвета фона и белого цвета текста для кнопки, которая нажата и над кото- рой находится курсор мыши (обозначение — hover
):
QPushButton:pressed:hover {color: white; background-color: black;}
Если нужно указать стиль для элемента управления, вложенного в другой элемент управле- ния, применяется следующий формат указания селектора:
<Селектор "внешнего" элемента><Разделитель><Селектор вложенного элемента>
Поддерживаются два варианта параметра
<Разделитель>
:
пробел —
<Вложенный элемент>
не обязательно должен быть вложен непосредственно во
<"Внешний">
. Так мы указываем зеленый цвет фона для всех надписей (
QLabel
), вложен- ных в группу (
QGroupBox
) и вложенные в нее элементы:
QGroupBox QLabel {background-color: green;}
>
—
<Вложенный элемент>
обязательно должен быть вложен непосредственно во
<"Внеш- ний">
. Так мы укажем синий цвет текста для всех надписей, непосредственно вложенных в группу:
QGroupBox>QLabel {color: blue;}
В стиле можно указать сразу несколько селекторов, записав их через запятую — тогда стиль будет применен к элементам управления, на которые указывают эти селекторы. Вот пример задания зеленого цвета фона для кнопок и надписей:
QLabel, QPushButton {background-color: green;}
В CSS элементы страницы наследуют параметры оформления от их родителей. Но в PyQt это не так. Скажем, если мы укажем для группы красный цвет текста: app.setStyleSheet("QGroupBox {color: red;}") вложенные в эту группу элементы не унаследуют его и будут иметь цвет текста, заданный по умолчанию. Нам придется задать для них нужный цвет явно: app.setStyleSheet("QGroupBox, QGroupBox * {color: red;}")
Начиная с версии PyQt 5.7, поддерживается возможность указать библиотеке, что все элементы-потомки должны наследовать параметры оформления у родителя. Для этого дос- таточно вызвать у класса
QCoreApplication статический метод setAttribute
, передав ему
Глава 18. Управление окном приложения
393 в качестве первого параметра значение атрибута
AA_UseStyleSheetPropagationInWidgetStyles класса
QtCore.Qt
, а в качестве второго параметра — значение
True
:
QtCore.QCoreApplication.setAttribute(
QtCore.Qt.AA_UseStyleSheetPropagationInWidgetStyles, True)
Чтобы отключить такую возможность, достаточно вызвать этот метод еще раз, указав в нем вторым параметром
False
И, наконец, при вызове метода setStyleSheet()
у элемента управления, для которого следу- ет задать таблицу стилей, в последней не указываются ни селектор, ни фигурные скобки — они просто не нужны.
Отметим, что в случае PyQt, как и в CSS, также действуют правила каскадности. Так, таб- лица стилей, заданная для окна, имеет больший приоритет, нежели таковая, указанная для приложения, а стиль, что был задан для элемента управления, имеет наивысший приоритет.
Помимо этого, более специфические стили имеют больший приоритет, чем менее специфи- ческие; так, стиль с селектором, в чей состав входит имя элемента управления, перекроет стиль с селектором любого другого типа.
За более подробным описанием поддерживаемых PyQt псевдоклассов, псевдоселекторов и особенностей указания стилей для отдельных классов элементов управления обращайтесь по интернет-адресу https://doc.qt.io/qt-5/stylesheet-reference.html.
Листинг 18.13 показывает пример задания таблиц стилей для элементов управления разны- ми способами. Результат выполнения приведенного в нем кода можно увидеть на рис. 18.2.
Листинг 18.13. Использование таблиц стилей для указания оформления
# -*- coding: utf-8 -*- from PyQt5 import QtWidgets import sys app = QtWidgets.QApplication(sys.argv)
# На уровне приложения задаем синий цвет текста для надписей, вложенных в группы,
# и курсивное начертание текста кнопок app.setStyleSheet("QGroupBox QLabel {color: blue;}
QPushButton {font-style: italic}") window = QtWidgets.QWidget() window.setWindowTitle("Таблицы стилей")
# На уровне окна задаем зеленый цвет текста для надписи с именем first и
# красный цвет текста для надписи, на которую наведен курсор мыши window.setStyleSheet("QLabel#first {color: green;} QLabel:hover {color: red;}") window.resize(200, 150)
# Создаем три надписи lbl1 = QtWidgets.QLabel("Зеленый текст")
# Указываем для первой надписи имя first lbl1.setObjectName("first") lbl2 = QtWidgets.QLabel("Полужирный текст")
# Для второй надписи указываем полужирный шрифт lbl2.setStyleSheet("font-weight: bold") lbl3 = QtWidgets.QLabel("Синий текст")
# Создаем кнопку btn = QtWidgets.QPushButton("Курсивный текст")
394
Часть II. Библиотека PyQt 5
# Создаем группу box = QtWidgets.QGroupBox("Группа")
# Создаем контейнер, помещаем в него третью надпись и вставляем в группу bbox = QtWidgets.QVBoxLayout() bbox.addWidget(lbl3) box.setLayout(bbox)
# Создаем еще один контейнер, помещаем в него две первые надписи, группу и кнопку и
# вставляем в окно mainbox = QtWidgets.QVBoxLayout() mainbox.addWidget(lbl1) mainbox.addWidget(lbl2) mainbox.addWidget(box) mainbox.addWidget(btn) window.setLayout(mainbox)
# Выводим окно и запускаем приложение window.show() sys.exit(app.exec_())
Рис. 18.2. Пример использования таблиц стилей
ГЛ А В А
19
Обработка сигналов и событий
При взаимодействии пользователя с окном возникают события — своего рода извещения о том, что пользователь выполнил какое-либо действие или в самой системе возникло неко- торое условие. В ответ на события система генерирует определенные сигналы, которые можно рассматривать как представления системных событий внутри библиотеки PyQt.
Сигналы являются важнейшей составляющей приложения с графическим интерфейсом, поэтому необходимо знать, как назначить обработчик сигнала, как удалить его, а также уметь правильно обработать событие. Сигналы, которые генерирует тот или иной компо- нент, мы будем рассматривать при изучении конкретного компонента.
19.1. Назначение обработчиков сигналов
Чтобы обработать какой-либо сигнал, необходимо сопоставить ему функцию или метод класса, который будет вызван при возникновении события и станет его обработчиком.
Каждый сигнал, поддерживаемый классом, соответствует одноименному атрибуту этого класса (отметим, что это именно атрибуты класса, а не атрибуты экземпляра). Так, сигнал clicked
, генерируемый при щелчке мышью, соответствует атрибуту clicked
. Каждый такой атрибут хранит экземпляр особого класса, представляющего соответствующий сигнал.
Чтобы назначить сигналу обработчик, следует использовать метод connect()
, унаследован- ный от класса
QObject
. Форматы вызова этого метода таковы:
<Компонент>.<Сигнал>.connect(<Обработчик>[, <Тип соединения>])
<Компонент>.<Сигнал>[<Тип>].connect(<Обработчик>[, <Тип соединения>])
Здесь мы назначаем
<Обработчик>
для параметра
<Сигнал>
, генерируемого параметром
<Компонент>
. В качестве обработчика можно указать:
ссылку на пользовательскую функцию;
ссылку на метод класса;
ссылку на экземпляр класса, в котором определен метод
__call__()
;
анонимную функцию;
ссылку на слот класса.
Вот пример назначения функции on_clicked_button()
в качестве обработчика сигнала clicked кнопки button
: button.clicked.connect(on_clicked_button)
396
Часть II. Библиотека PyQt 5
Сигналы могут принимать произвольное число параметров, каждый из которых может от- носиться к любому типу данных. При этом бывает и так, что в классе существуют два сиг- нала с одинаковыми наименованиями, но разными наборами параметров. Тогда следует дополнительно в квадратных скобках указать
<Тип>
данных, принимаемых сигналом, — либо просто написав его наименование, либо задав его в виде строки. Например, оба сле- дующих выражения назначают обработчик сигнала, принимающего один параметр логиче- ского типа: button.clicked[bool].connect(on_clicked_button) button.clicked["bool"].connect(on_clicked_button)
Одному и тому же сигналу можно назначить произвольное количество обработчиков. Это иллюстрирует код из листинга 19.1, где сигналу clicked кнопки назначены сразу четыре обработчика.
Листинг 19.1. Назначение сигналу нескольких обработчиков
# -*- coding: utf-8 -*- from PyQt5 import QtWidgets import sys def on_clicked(): print("Кнопка нажата. Функция on_clicked()") class MyClass(): def __init__(self, x=0): self.x = x def __call__(self): print("Кнопка нажата. Метод MyClass.__call__()") print("x =", self.x) def on_clicked(self): print("Кнопка нажата. Метод MyClass.on_clicked()") obj = MyClass() app = QtWidgets.QApplication(sys.argv) button = QtWidgets.QPushButton("Нажми меня")
# Назначаем обработчиком функцию button.clicked.connect(on_clicked)
# Назначаем обработчиком метод класса button.clicked.connect(obj.on_clicked)
# Назначаем обработчиком ссылку на экземпляр класса button.clicked.connect(MyClass(10))
# Назначаем обработчиком анонимную функцию button.clicked.connect(lambda: MyClass(5)()) button.show() sys.exit(app.exec_())
В четвертом обработчике мы использовали анонимную функцию. В ней мы сначала создаем объект класса
MyClass
, передав ему в качестве параметра число
5
, после чего сразу же вы- зываем его как функцию, указав после конструктора пустые скобки: button.clicked.connect(lambda: MyClass(5)())
Глава 19. Обработка сигналов и событий
397
Результат выполнения в окне консоли при щелчке на кнопке:
Кнопка нажата. Функция on_clicked()
Кнопка нажата. Метод MyClass.on_clicked()
Кнопка нажата. Метод MyClass.__call__() x = 10
Кнопка нажата. Метод MyClass.__call__() x = 5
Классы PyQt 5 поддерживают ряд методов, специально предназначенных для использова- ния в качестве обработчиков сигналов. Такие методы называются слотами. Например, класс
QApplication поддерживает слот quit()
, завершающий текущее приложение. В лис- тинге 19.2 показан код, использующий этот слот.
Листинг 19.2. Использование слота
# -*- coding: utf-8 -*- from PyQt5 import QtWidgets import sys app = QtWidgets.QApplication(sys.argv) button = QtWidgets.QPushButton("Завершить работу") button.clicked.connect(app.quit) button.show() sys.exit(app.exec_())
Любой пользовательский метод можно сделать слотом, для чего необходимо перед его оп- ределением вставить декоратор
@pyqtSlot()
. Формат декоратора:
@QtCore.pyqtSlot(*<Типы данных>, name=None, result=None)
В параметре
<Типы данных>
через запятую указываются типы данных параметров, прини- маемых слотом, — например: bool или int
. При задании типа данных C++ его название не- обходимо указать в виде строки. Если метод не принимает параметров, параметр
<Типы дан- ных>
не указывается. В именованном параметре name можно передать название слота в виде строки — это название станет использоваться вместо названия метода, а если параметр name не задан, название слота будет совпадать с названием метода. Именованный параметр result предназначен для указания типа данных, возвращаемых методом, — если параметр не задан, то метод ничего не возвращает. Чтобы создать перегруженную версию слота, декоратор указывается последовательно несколько раз с разными типами данных. Пример использования декоратора
@pyqtSlot()
приведен в листинге 19.3.
Листинг 19.3. Использование декоратора
@pyqtSlot()
# -*- coding: utf-8 -*- from PyQt5 import QtCore, QtWidgets import sys class MyClass(QtCore.QObject): def __init__(self):
QtCore.QObject.__init__(self)
398
Часть II. Библиотека PyQt 5
@QtCore.pyqtSlot() def on_clicked(self): print("Кнопка нажата. Слот on_clicked()")
@QtCore.pyqtSlot(bool, name="myslot") def on_clicked2(self, status): print("Кнопка нажата. Слот myslot(bool)", status) obj = MyClass() app = QtWidgets.QApplication(sys.argv) button = QtWidgets.QPushButton("Нажми меня") button.clicked.connect(obj.on_clicked) button.clicked.connect(obj.myslot) button.show() sys.exit(app.exec_())
PyQt не требует обязательного превращения в слот метода, который будет использоваться как обработчик сигнала. Однако это рекомендуется сделать, т. к. вызов слота в этом случае выполняется быстрее, чем вызов обычного метода.
Необязательный параметр
<Тип соединения>
метода connect()
определяет тип соединения между сигналом и обработчиком. На этот параметр следует обратить особое внимание при использовании в приложении нескольких потоков, т. к. изменять GUI-поток из другого потока нельзя. В параметре можно указать один из следующих атрибутов класса
QtCore.Qt
:
AutoConnection
—
0
— значение по умолчанию. Если источник сигнала и обработчик находятся в одном потоке, то оно эквивалентно значению
DirectConnection
, а если в разных потоках, то —
QueuedConnection
;
DirectConnection
—
1
— обработчик вызывается сразу после генерации сигнала и вы- полняется в потоке его источника;
QueuedConnection
—
2
— сигнал помещается в очередь обработки событий, а его обра- ботчик выполняется в потоке приемника сигнала;
BlockingQueuedConnection
—
4
— аналогично значению
QueuedConnection за тем исклю- чением, что поток блокируется на время обработки сигнала. Обратите внимание, что ис- точник и обработчик сигнала обязательно должны быть расположены в разных потоках;
UniqueConnection
—
0x80
— указывает, что обработчик можно назначить только один раз. Этот атрибут с помощью оператора
| может быть объединен с любым из представ- ленных ранее флагов:
# Эти два обработчика будут успешно назначены и выполнены button.clicked.connect(on_clicked) button.clicked.connect(on_clicked)
# А эти два обработчика назначены не будут button.clicked.connect(on_clicked, Qt.Core.Qt.AutoConnection |
QtCore.Qt.UniqueConnection) button.clicked.connect(on_clicked, Qt.Core.Qt.AutoConnection |
QtCore.Qt.UniqueConnection)
# Тем не менее, эти два обработчика будут назначены, поскольку они разные button.clicked.connect(on_clicked, Qt.Core.Qt.AutoConnection |
QtCore.Qt.UniqueConnection) button.clicked.connect(obj.on_clicked, Qt.Core.Qt.AutoConnection |
QtCore.Qt.UniqueConnection)
Глава 19. Обработка сигналов и событий
399 19.2. Блокировка и удаление обработчика
Для блокировки и удаления обработчиков предназначены следующие методы класса
QObject
:
blockSignals(<Флаг>)
— временно блокирует прием сигналов, если параметр имеет зна- чение
True
, и снимает блокировку, если параметр имеет значение
False
. Метод возвра- щает логическое представление предыдущего состояния соединения;
signalsBlocked()
— возвращает значение
True
, если блокировка сигналов установлена, и
False
— в противном случае;
disconnect()
— удаляет обработчик. Форматы метода:
<Компонент>.<Сигнал>.disconnect([<Обработчик>])
<Компонент>.<Сигнал>[<Тип>].disconnect([<Обработчик>])
Если параметр
<Обработчик>
не указан, удаляются все обработчики, назначенные ранее, в противном случае удаляется только указанный обработчик. Параметр
<Тип>
указывает- ся лишь в том случае, если существуют сигналы с одинаковыми именами, но прини- мающие разные параметры: button.clicked.disconnect() button.clicked[bool].disconnect(on_clicked_button) button.clicked["bool"].disconnect(on_clicked_button)
Создадим окно с четырьмя кнопками (листинг 19.4). Для кнопки Нажми меня назначим обработчик сигнала clicked
. Чтобы информировать о нажатии кнопки, выведем сообщение в окно консоли. Для кнопок Блокировать, Разблокировать и Удалить обработчик созда- дим обработчики, которые будут изменять статус обработчика для кнопки Нажми меня.
Листинг 19.4. Блокировка и удаление обработчика
# -*- coding: utf-8 -*- from PyQt5 import QtCore, QtWidgets class MyWindow(QtWidgets.QWidget): def __init__(self, parent=None):
QtWidgets.QWidget.__init__(self, parent) self.setWindowTitle("Блокировка и удаление обработчика") self.resize(300, 150) self.button1 = QtWidgets.QPushButton("Нажми меня") self.button2 = QtWidgets.QPushButton("Блокировать") self.button3 = QtWidgets.QPushButton("Разблокировать") self.button4 = QtWidgets.QPushButton("Удалить обработчик") self.button3.setEnabled(False) vbox = QtWidgets.QVBoxLayout() vbox.addWidget(self.button1) vbox.addWidget(self.button2) vbox.addWidget(self.button3) vbox.addWidget(self.button4) self.setLayout(vbox) self.button1.clicked.connect(self.on_clicked_button1)
400
Часть II. Библиотека PyQt 5 self.button2.clicked.connect(self.on_clicked_button2) self.button3.clicked.connect(self.on_clicked_button3) self.button4.clicked.connect(self.on_clicked_button4)
@QtCore.pyqtSlot() def on_clicked_button1(self): print("Нажата кнопка button1")
@QtCore.pyqtSlot() def on_clicked_button2(self): self.button1.blockSignals(True) self.button2.setEnabled(False) self.button3.setEnabled(True)
@QtCore.pyqtSlot() def on_clicked_button3(self): self.button1.blockSignals(False) self.button2.setEnabled(True) self.button3.setEnabled(False)
@QtCore.pyqtSlot() def on_clicked_button4(self): self.button1.clicked.disconnect(self.on_clicked_button1) self.button2.setEnabled(False) self.button3.setEnabled(False) self.button4.setEnabled(False) if __name__ == "__main__": import sys app = QtWidgets.QApplication(sys.argv) window = MyWindow() window.show() sys.exit(app.exec_())
Если нажать кнопку Нажми меня, в окно консоли будет выведена строка
Нажата кнопка button1
. Нажатие кнопки Блокировать производит блокировку обработчика — теперь при нажатии кнопки Нажми меня никаких сообщений в окно консоли не выводится. Отменить блокировку можно с помощью кнопки Разблокировать. Нажатие кнопки Удалить обра- ботчик производит полное удаление обработчика — в этом случае, чтобы обрабатывать нажатие кнопки Нажми меня, необходимо заново назначить обработчик.
Также можно отключить генерацию сигнала, сделав компонент недоступным с помощью следующих методов из класса
QWidget
:
setEnabled(<Флаг>)
— если в параметре указано значение
False
, компонент станет не- доступным. Чтобы сделать компонент опять доступным, следует передать значение
True
;
setDisabled(<Флаг>)
— если в параметре указано значение
True
, компонент станет не- доступным. Чтобы сделать компонент опять доступным, следует передать значение
False
Проверить, доступен компонент или нет, позволяет метод isEnabled()
. Он возвращает зна- чение
True
, если компонент доступен, и
False
— в противном случае.
Глава 19. Обработка сигналов и событий
401 19.3. Генерация сигналов
В некоторых случаях необходимо сгенерировать сигнал программно. Например, при запол- нении последнего текстового поля и нажатии клавиши
класса
QObject
. Форматы этого ме- тода:
<Компонент>.<Сигнал>.emit([<Данные>])
<Компонент>.<Сигнал>[<Тип>].emit([<Данные>])
Метод emit()
всегда вызывается у объекта, которому посылается сигнал: button.clicked.emit()
Сигналу и, соответственно, его обработчику можно передать данные, указав их в вызове метода emit()
: button.clicked[bool].emit(False) button.clicked["bool"].emit(False)
Также мы можем создавать свои собственные сигналы. Для этого следует определить в классе атрибут, чье имя совпадет с наименованием сигнала. Отметим, что это должен быть атрибут класса, а не экземпляра. Далее мы присвоим вновь созданному атрибуту результат, возвращенный функцией pyqtSignal()
из модуля
QtCore
. Формат функции:
<Объект сигнала> = pyqtSignal(*<Типы данных>[, name=<Имя сигнала>])
В параметре
<Типы данных>
через запятую указываются названия типов данных, передавае- мых сигналу, — например: bool или int
: mysignal1 = QtCore.pyqtSignal(int) mysignal2 = QtCore.pyqtSignal(int, str)
При использовании типа данных C++ его название необходимо указать в виде строки: mysignal3 = QtCore.pyqtSignal("QDate")
Если сигнал не принимает параметров, параметр
<Типы данных>
не указывается.
Сигнал может иметь несколько перегруженных версий, различающихся количеством и типом принимаемых параметров. В этом случае типы параметров указываются внутри квадратных скобок. Вот пример сигнала, передающего данные типа int или str
: mysignal4 = QtCore.pyqtSignal([int], [str])
По умолчанию название создаваемого сигнала будет совпадать с названием атрибута клас- са. Однако мы можем указать для сигнала другое название, после чего он будет доступен под двумя названиями: совпадающим с именем атрибута класса и заданным нами. Для ука- зания названия сигнала применяется параметр name
: mysignal = QtCore.pyqtSignal(int, name="mySignal")
В качестве примера создадим окно с двумя кнопками (листинг 19.5), которым назначим обработчики сигнала clicked
(нажатие кнопки). Внутри обработчика щелчка на первой кнопке сгенерируем два сигнала: первый будет имитировать нажатие второй кнопки, а вто- рой станет пользовательским, привязанным к окну. Внутри обработчиков выведем сообще- ния в окно консоли.
402
Часть II. Библиотека PyQt 5
Листинг 19.5. Генерация сигнала из программы
# -*- coding: utf-8 -*- from PyQt5 import QtCore, QtWidgets class MyWindow(QtWidgets.QWidget): mysignal = QtCore.pyqtSignal(int, int) def __init__(self, parent=None):
QtWidgets.QWidget.__init__(self, parent) self.setWindowTitle("Генерация сигнала из программы") self.resize(300, 100) self.button1 = QtWidgets.QPushButton("Нажми меня") self.button2 = QtWidgets.QPushButton("Кнопка 2") vbox = QtWidgets.QVBoxLayout() vbox.addWidget(self.button1) vbox.addWidget(self.button2) self.setLayout(vbox) self.button1.clicked.connect(self.on_clicked_button1) self.button2.clicked.connect(self.on_clicked_button2) self.mysignal.connect(self.on_mysignal) def on_clicked_button1(self): print("Нажата кнопка button1")
# Генерируем сигналы self.button2.clicked[bool].emit(False) self.mysignal.emit(10, 20) def on_clicked_button2(self): print("Нажата кнопка button2") def on_mysignal(self, x, y): print("Обработан пользовательский сигнал mysignal()") print("x =", x, "y =", y) if __name__ == "__main__": import sys app = QtWidgets.QApplication(sys.argv) window = MyWindow() window.show() sys.exit(app.exec_())
Результат выполнения после нажатия первой кнопки:
Нажата кнопка button1
Нажата кнопка button2
Обработан пользовательский сигнал mysignal() x = 10 y = 20
Вместо конкретного типа принимаемого сигналом параметра можно указать тип
QVariant из модуля
QtCore
. В этом случае при генерации сигнала допускается передавать ему данные любого типа. Вот пример создания и генерирования сигнала, принимающего параметр любого типа: mysignal = QtCore.pyqtSignal(QtCore.QVariant) self.mysignal.emit(20)
Глава 19. Обработка сигналов и событий
403 self.mysignal.emit("Привет!") self.mysignal.emit([1, "2"])
Сгенерировать сигнал можно не только с помощью метода emit()
. Некоторые компоненты предоставляют методы, которые посылают сигнал. Например, у кнопок существует метод click()
. Используя этот метод, инструкцию: button.clicked.emit() можно записать следующим образом: button.click()
Более подробно такие методы мы будем рассматривать при изучении конкретных компо- нентов.
19.4. Передача данных в обработчик
При назначении обработчика в метод connect()
передается ссылка на функцию или метод.
Если после названия функции (метода) указать внутри круглых скобок какой-либо пара- метр, то это приведет к вызову функции (метода) и вместо ссылки будет передан результат ее выполнения, что вызовет ошибку. Передать данные в обработчик можно следующими способами:
создать анонимную функцию и внутри ее выполнить вызов обработчика с параметрами.
Вот пример передачи обработчику числа
10
: self.button1.clicked.connect(lambda : self.on_clicked_button1(10))
Если передаваемое обработчику значение вычисляется в процессе выполнения кода, переменную, хранящую это значение, следует указывать в анонимной функции как зна- чение по умолчанию, иначе функции будет передана ссылка на это значение, а не оно само: y = 10 self.button1.clicked.connect(lambda x=y: self.on_clicked_button1(x))
передать ссылку на экземпляр класса, внутри которого определен метод
__call__()
Передаваемое значение указывается в качестве параметра конструктора этого класса: class MyClass(): def __init__(self, x=0): self.x = x def __call__(self): print("x =", self.x) self.button1.clicked.connect(MyClass(10))
передать ссылку на обработчик и данные в функцию partial()
из модуля functools
Формат функции: partial(<Функция>[, *<Неименованные параметры>][, **<Именованные параметры>])
Пример передачи параметра в обработчик: from functools import partial self.button1.clicked.connect(partial(self.on_clicked_button1, 10))
404
Часть II. Библиотека PyQt 5
Если при генерации сигнала передается предопределенное значение, то оно будет дос- тупно в обработчике после остальных параметров. Назначим обработчик сигнала clicked
, принимающего логический параметр, и дополнительно передадим число: self.button1.clicked.connect(partial(self.on_clicked_button1, 10))
Обработчик будет иметь следующий вид: def on_clicked_button1(self, x, status): print("Нажата кнопка button1", x, status)
Результат выполнения:
Нажата кнопка button1 10 False
19.5. Использование таймеров
Таймеры позволяют через заданный интервал времени выполнять метод с предопределен- ным названием timerEvent()
. Для назначения таймера используется метод startTimer()
класса
QObject
. Формат метода:
1 ... 33 34 35 36 37 38 39 40 ... 83
Параметр
<Интервал>
задает промежуток времени в миллисекундах, по истечении которого выполняется метод timerEvent()
. Минимальное значение интервала зависит от операцион- ной системы. Если в параметре
<Интервал>
указать значение
0
, таймер будет срабатывать много раз при отсутствии других необработанных событий.
Необязательный параметр timerType позволяет указать тип таймера в виде одного из атри- бутов класса
QtCore.Qt
:
PreciseTimer
— точный таймер, обеспечивающий точность до миллисекунд;
CoarseTimer
— таймер, обеспечивающий точность в пределах 5% от заданного интерва- ла (значение по умолчанию);
VeryCoarseTimer
— «приблизительный» таймер, обеспечивающий точность до секунд.
Метод startTimer()
возвращает идентификатор таймера, с помощью которого впоследст- вии можно остановить таймер.
Формат метода timerEvent()
: timerEvent(self, <Объект класса QTimerEvent>)
Внутри него можно получить идентификатор таймера с помощью метода timerId()
объекта класса
QTimerEvent
Чтобы остановить таймер, необходимо воспользоваться методом killTimer()
класса
QObject
. Формат метода:
<Объект>.killTimer(
В качестве параметра указывается идентификатор, возвращаемый методом startTimer()
Создадим в окне часы, которые будут отображать текущее системное время с точностью до секунды, и добавим возможность запуска и остановки часов с помощью соответствующих кнопок (листинг 19.6).
Глава 19. Обработка сигналов и событий
405
Листинг 19.6. Вывод времени в окне с точностью до секунды
# -*- coding: utf-8 -*- from PyQt5 import QtCore, QtWidgets import time class MyWindow(QtWidgets.QWidget): def __init__(self, parent=None):
QtWidgets.QWidget.__init__(self, parent) self.setWindowTitle("Часы в окне") self.resize(200, 100) self.timer_id = 0 self.label = QtWidgets.QLabel("") self.label.setAlignment(QtCore.Qt.AlignHCenter) self.button1 = QtWidgets.QPushButton("Запустить") self.button2 = QtWidgets.QPushButton("Остановить") self.button2.setEnabled(False) vbox = QtWidgets.QVBoxLayout() vbox.addWidget(self.label) vbox.addWidget(self.button1) vbox.addWidget(self.button2) self.setLayout(vbox) self.button1.clicked.connect(self.on_clicked_button1) self.button2.clicked.connect(self.on_clicked_button2) def on_clicked_button1(self):
# Задаем интервал в 1 секунду и "приближенный" таймер self.timer_id = self.startTimer(1000, timerType = QtCore.Qt.VeryCoarseTimer) self.button1.setEnabled(False) self.button2.setEnabled(True) def on_clicked_button2(self): if self.timer_id: self.killTimer(self.timer_id) self.timer_id = 0 self.button1.setEnabled(True) self.button2.setEnabled(False) def timerEvent(self, event): self.label.setText(time.strftime("%H:%M:%S")) if __name__ == "__main__": import sys app = QtWidgets.QApplication(sys.argv) window = MyWindow() window.show() sys.exit(app.exec_())
Вместо методов startTimer()
и killTimer()
класса
QObject можно воспользоваться классом
QTimer из модуля
QtCore
. Конструктор класса имеет следующий формат:
<Объект> = QTimer([parent=None])
406
Часть II. Библиотека PyQt 5
Методы класса:
setInterval(<Интервал>)
— задает промежуток времени в миллисекундах, по истечении которого генерируется сигнал timeout
. Минимальное значение интервала зависит от операционной системы. Если в параметре
<Интервал>
указать значение
0
, таймер будет срабатывать много раз при отсутствии других необработанных сигналов;
start([<Интервал>])
— запускает таймер. В необязательном параметре можно указать промежуток времени в миллисекундах. Если параметр не указан, используется значение, заданное в вызове метода setInterval()
;
stop()
— останавливает таймер;
isActive()
— возвращает значение
True
, если таймер запущен, и
False
— в противном случае;
timerId()
— возвращает идентификатор таймера, если он запущен, и значение
-1
— в противном случае;
remainingTime()
— возвращает время, оставшееся до очередного срабатывания таймера, в миллисекундах;
interval()
— возвращает установленный интервал;
setSingleShot(<Флаг>)
— если в параметре указано значение
True
, таймер сработает только один раз, в противном случае — будет срабатывать многократно;
isSingleShot()
— возвращает значение
True
, если таймер будет срабатывать только один раз, и
False
— в противном случае;
setTimerType(<Тип таймера>)
— задает тип таймера, который указывается в том же виде, что и в случае вызова метода startTimer()
;
timerType()
— возвращает тип таймера.
Переделаем предыдущий пример и используем класс
QTimer вместо методов startTimer()
и killTimer()
(листинг 19.7).
Листинг 19.7. Использование класса
QTimer
# -*- coding: utf-8 -*- from PyQt5 import QtCore, QtWidgets import time class MyWindow(QtWidgets.QWidget): def __init__(self, parent=None):
QtWidgets.QWidget.__init__(self, parent) self.setWindowTitle("Использование класса QTimer") self.resize(200, 100) self.label = QtWidgets.QLabel("") self.label.setAlignment(QtCore.Qt.AlignHCenter) self.button1 = QtWidgets.QPushButton("Запустить") self.button2 = QtWidgets.QPushButton("Остановить") self.button2.setEnabled(False) vbox = QtWidgets.QVBoxLayout() vbox.addWidget(self.label) vbox.addWidget(self.button1)
Глава 19. Обработка сигналов и событий
407 vbox.addWidget(self.button2) self.setLayout(vbox) self.button1.clicked.connect(self.on_clicked_button1) self.button2.clicked.connect(self.on_clicked_button2) self.timer = QtCore.QTimer() self.timer.timeout.connect(self.on_timeout); def on_clicked_button1(self): self.timer.start(1000) # 1 секунда self.button1.setEnabled(False) self.button2.setEnabled(True) def on_clicked_button2(self): self.timer.stop() self.button1.setEnabled(True) self.button2.setEnabled(False) def on_timeout(self): self.label.setText(time.strftime("%H:%M:%S")) if __name__ == "__main__": import sys app = QtWidgets.QApplication(sys.argv) window = MyWindow() window.show() sys.exit(app.exec_())
Статический метод singleShot()
класса
QTimer запускает таймер, настраивает его для одно- кратного срабатывания и указывает функцию или метод, который будет вызван по истече- нии заданного интервала. Формат вызова этого метода следующий: singleShot(<Интервал>[, <Тип таймера>], <Функция или метод>)
Примеры использования этого статического метода:
QtCore.QTimer.singleShot(1000, self.on_timeout)
QtCore.QTimer.singleShot(1000, QtWidgets.qApp.quit)
19.6. Перехват всех событий
В предыдущих разделах мы рассмотрели обработку сигналов, которые позволяют обмени- ваться сообщениями между компонентами. Обработка внешних событий — например, на- жатий клавиш, — осуществляется несколько иначе. Чтобы обработать событие, необходимо наследовать класс и переопределить в нем метод со специальным названием, — так, чтобы обработать нажатие клавиши, следует переопределить метод keyPressEvent()
. Специальные методы принимают объект, содержащий детальную информацию о событии, — например, код нажатой клавиши. Все эти объекты являются наследниками класса
QEvent и наследуют следующие методы:
accept()
— устанавливает флаг, разрешающий дальнейшую обработку события. Ска- жем, если в методе closeEvent()
вызвать метод accept()
через объект события, окно будет закрыто. Этот флаг обычно установлен по умолчанию;
ignore()
— сбрасывает флаг, разрешающий дальнейшую обработку события. Так, если в методе closeEvent()
вызвать метод ignore()
через объект события, окно закрыто не будет;
408
Часть II. Библиотека PyQt 5
setAccepted(<Флаг>)
— если в качестве параметра указано значение
True
, флаг, разре- шающий дальнейшую обработку события, будет установлен (аналогично вызову метода accept()
), а если
False
— сброшен (аналогично вызову метода ignore()
);
isAccepted()
— возвращает текущее состояние флага, разрешающего дальнейшую об- работку события;
spontaneous()
— возвращает
True
, если событие сгенерировано системой, и
False
— если внутри программы;
type()
— возвращает тип события. Приведем основные типы событий (полный их спи- сок содержится в документации по классу
QEvent на странице https://doc.qt.io/qt-
5/qevent.html):
•
0
— нет события;
•
1
—
Timer
— событие таймера;
•
2
—
MouseButtonPress
— нажата кнопка мыши;
•
3
—
MouseButtonRelease
— отпущена кнопка мыши;
•
4
—
MouseButtonDblClick
— двойной щелчок мышью;
•
5
—
MouseMove
— перемещение мыши;
•
6
—
KeyPress
— клавиша на клавиатуре нажата;
•
7
—
KeyRelease
— клавиша на клавиатуре отпущена;
•
8
—
FocusIn
— получен фокус ввода с клавиатуры;
•
9
—
FocusOut
— потерян фокус ввода с клавиатуры;
•
10
—
Enter
— указатель мыши входит в область компонента;
•
11
—
Leave
— указатель мыши покидает область компонента;
•
12
—
Paint
— перерисовка компонента;
•
13
—
Move
— позиция компонента изменилась;
•
14
—
Resize
— изменился размер компонента;
•
17
—
Show
— компонент отображен;
•
18
—
Hide
— компонент скрыт;
•
19
—
Close
— окно закрыто;
•
24
—
WindowActivate
— окно стало активным;
•
25
—
WindowDeactivate
— окно стало неактивным;
•
26
—
ShowToParent
— дочерний компонент отображен;
•
27
—
HideToParent
— дочерний компонент скрыт;
•
31
—
Wheel
— прокручено колесико мыши;
•
40
—
Clipboard
— содержимое буфера обмена изменено;
•
60
—
DragEnter
— указатель мыши входит в область компонента при операции пере- таскивания;
•
61
—
DragMove
— производится операция перетаскивания;
•
62
—
DragLeave
— указатель мыши покидает область компонента при операции пере- таскивания;
Глава 19. Обработка сигналов и событий
409
•
63
—
Drop
— операция перетаскивания завершена;
•
68
—
ChildAdded
— добавлен дочерний компонент;
•
69
—
ChildPolished
— производится настройка дочернего компонента;
•
71
—
ChildRemoved
— удален дочерний компонент;
•
74
—
PolishRequest
— компонент настроен;
•
75
—
Polish
— производится настройка компонента;
•
82
—
ContextMenu
— событие контекстного меню;
•
99
—
ActivationChange
— изменился статус активности окна верхнего уровня;
•
103
—
WindowBlocked
— окно блокировано модальным окном;
•
104
—
WindowUnblocked
— текущее окно разблокировано после закрытия модального окна;
•
105
—
WindowStateChange
— статус окна изменился;
•
121
—
ApplicationActivate
— приложение стало доступно пользователю;
•
122
—
ApplicationDeactivate
— приложение стало недоступно пользователю;
•
1000
—
User
— пользовательское событие;
•
65535
—
MaxUser
— максимальный идентификатор пользовательского события.
Статический метод registerEventType([<Число>])
позволяет зарегистрировать пользова- тельский тип события, возвращая идентификатор зарегистрированного события. В качестве параметра можно указать значение в пределах от
QEvent.User
(
1000
) до
QEvent.MaxUser
(
65535
).
Перехват всех событий осуществляется с помощью метода с предопределенным названием event(self,
. Через параметр
доступен объект с дополнительной инфор- мацией о событии. Этот объект различен для разных типов событий — например, для собы- тия
MouseButtonPress объект будет экземпляром класса
QMouseEvent
, а для события
KeyPress
— экземпляром класса
QKeyEvent
. Методы, поддерживаемые всеми этими класса- ми, мы рассмотрим в следующих разделах.
Из метода event()
следует вернуть в качестве результата значение
True
, если событие было обработано, и
False
— в противном случае. Если возвращается значение
True
, то родитель- ский компонент не получит событие. Чтобы продолжить распространение события, необхо- димо вызвать метод event()
базового класса и передать ему текущий объект события.
Обычно это делается так: return QtWidgets.QWidget.event(self, e)
В этом случае пользовательский класс является наследником класса
QWidget и переопреде- ляет метод event()
. Если вы наследуете другой класс, следует вызывать метод именно этого класса. Например, при наследовании класса
QLabel инструкция будет выглядеть так: return QtWidgets.QLabel.event(self, e)
Пример перехвата нажатия клавиши, щелчка мышью и закрытия окна показан в листин- ге 19.8.
Листинг 19.8. Перехват всех событий
# -*- coding: utf-8 -*- from PyQt5 import QtCore, QtWidgets
410
Часть II. Библиотека PyQt 5 class MyWindow(QtWidgets.QWidget): def __init__(self, parent=None):
QtWidgets.QWidget.__init__(self, parent) self.resize(300, 100) def event(self, e): if e.type() == QtCore.QEvent.KeyPress: print("Нажата клавиша на клавиатуре") print("Код:", e.key(), ", текст:", e.text()) elif e.type() == QtCore.QEvent.Close: print("Окно закрыто") elif e.type() == QtCore.QEvent.MouseButtonPress: print("Щелчок мышью. Координаты:", e.x(), e.y()) return QtWidgets.QWidget.event(self, e) # Отправляем дальше if __name__ == "__main__": import sys app = QtWidgets.QApplication(sys.argv) window = MyWindow() window.show() sys.exit(app.exec_())
19.7. События окна
Перехватывать все события следует только в самом крайнем случае. В обычных ситуациях нужно использовать методы, предназначенные для обработки определенного события, — например, чтобы обработать закрытие окна, достаточно переопределить метод closeEvent()
. Методы, которые требуется переопределять для обработки событий окна, мы сейчас и рассмотрим.
19.7.1. Изменение состояния окна
Отследить изменение состояния окна (сворачивание, разворачивание, скрытие и отображе- ние) позволяют следующие методы:
changeEvent(self,
— вызывается при изменении состояния окна, приложения или компонента, заголовка окна, его палитры, статуса активности окна верхнего уров- ня, языка, локали и др. (полный список смотрите в документации). При обработке со- бытия
WindowStateChange через параметр
доступен экземпляр класса
QWindowStateChangeEvent
. Этот класс поддерживает только метод oldState()
, с по- мощью которого можно получить предыдущее состояние окна;
showEvent(self,
— вызывается при отображении компонента. Через параметр
доступен экземпляр класса
QShowEvent
;
hideEvent(self,
— вызывается при скрытии компонента. Через параметр
доступен экземпляр класса
QHideEvent
Для примера выведем в консоль текущее состояние окна при его сворачивании, разворачи- вании, скрытии и отображении (листинг 19.9).
Глава 19. Обработка сигналов и событий
411
Листинг 19.9. Отслеживание состояния окна
# -*- coding: utf-8 -*- from PyQt5 import QtCore, QtWidgets class MyWindow(QtWidgets.QWidget): def __init__(self, parent=None):
QtWidgets.QWidget.__init__(self, parent) self.resize(300, 100) def changeEvent(self, e): if e.type() == QtCore.QEvent.WindowStateChange: if self.isMinimized(): print("Окно свернуто") elif self.isMaximized(): print("Окно раскрыто до максимальных размеров") elif self.isFullScreen(): print("Полноэкранный режим") elif self.isActiveWindow(): print("Окно находится в фокусе ввода")
QtWidgets.QWidget.changeEvent(self, e) # Отправляем дальше def showEvent(self, e): print("Окно отображено")
QtWidgets.QWidget.showEvent(self, e) # Отправляем дальше def hideEvent(self, e): print("Окно скрыто")
QtWidgets.QWidget.hideEvent(self, e) # Отправляем дальше if __name__ == "__main__": import sys app = QtWidgets.QApplication(sys.argv) window = MyWindow() window.show() sys.exit(app.exec_())
19.7.2. Изменение положения и размеров окна
При перемещении и изменении размеров окна вызываются следующие методы:
moveEvent(self,
— непрерывно вызывается при перемещении окна. Через параметр
доступен экземпляр класса
QMoveEvent
. Получить координаты окна по- зволяют следующие методы этого класса:
• pos()
— возвращает экземпляр класса
QPoint с текущими координатами;
• oldPos()
— возвращает экземпляр класса
QPoint с предыдущими координатами;
resizeEvent(self,
— непрерывно вызывается при изменении размеров окна.
Через параметр
доступен экземпляр класса
QResizeEvent
. Получить размеры ок- на позволяют следующие методы этого класса:
• size()
— возвращает экземпляр класса
QSize с текущими размерами;
• oldSize()
— возвращает экземпляр класса
QSize с предыдущими размерами.
412
Часть II. Библиотека PyQt 5
Пример обработки изменения положения окна и его размера показан в листинге 19.10.
Листинг 19.10. Отслеживание смены положения и размеров окна
# -*- coding: utf-8 -*- from PyQt5 import QtWidgets class MyWindow(QtWidgets.QWidget): def __init__(self, parent=None):
QtWidgets.QWidget.__init__(self, parent) self.resize(300, 100) def moveEvent(self, e): print("x = {0}; y = {1}".format(e.pos().x(), e.pos().y()))
QtWidgets.QWidget.moveEvent(self, e) # Отправляем дальше def resizeEvent(self, e): print("w = {0}; h = {1}".format(e.size().width(), e.size().height()))
QtWidgets.QWidget.resizeEvent(self, e) # Отправляем дальше if __name__ == "__main__": import sys app = QtWidgets.QApplication(sys.argv) window = MyWindow() window.show() sys.exit(app.exec_())
19.7.3. Перерисовка окна или его части
Когда компонент (или часть компонента) становится видимым, требуется выполнить его перерисовку. В этом случае вызывается метод с названием paintEvent(self,
Через параметр
доступен экземпляр класса
QPaintEvent
, который поддерживает следующие методы:
rect()
— возвращает экземпляр класса
QRect с координатами и размерами прямоуголь- ной области, которую требуется перерисовать;
region()
— возвращает экземпляр класса
QRegion с регионом, требующим перерисовки.
С помощью этих методов можно получить координаты области, которая, например, была ранее перекрыта другим окном и теперь вновь оказалась в зоне видимости. Перерисовывая только область, а не весь компонент, можно заметно повысить быстродействие приложения.
Следует также заметить, что в целях эффективности последовательность событий перери- совки может быть объединена в одно событие с общей областью перерисовки.
В некоторых случаях перерисовку окна необходимо выполнить вне зависимости от внеш- них действий системы или пользователя — например, при изменении каких-либо значений требуется обновить график. Вызвать событие перерисовки компонента позволяют следую- щие методы класса
QWidget
:
repaint()
— немедленно вызывает метод paintEvent()
для перерисовки компонента при условии, что таковой не скрыт, и обновление не было запрещено вызовом метода setUpdatesEnabled()
. Форматы метода:
Глава 19. Обработка сигналов и событий
413 repaint() repaint(
Первый формат вызова выполняет перерисовку всего компонента, а остальные — только области с указанными координатами;
update()
— посылает сообщение о необходимости перерисовки компонента при усло- вии, что компонент не скрыт и обновление не запрещено. Событие будет обработано на следующей итерации основного цикла приложения. Если посылаются сразу несколько сообщений, они объединяются в одно, благодаря чему можно избежать неприятного мерцания. Рекомендуется использовать этот метод вместо метода repaint()
. Форматы вызова: update() update(
19.7.4. Предотвращение закрытия окна
При закрытии окна нажатием кнопки Закрыть в его заголовке или вызовом метода close()
в коде выполняется метод closeEvent(self,
. Через параметр
доступен экземпляр класса
QCloseEvent
. Чтобы предотвратить закрытие окна, у объекта события сле- дует вызвать метод ignore()
, в противном случае — метод accept()
В качестве примера по нажатию кнопки Закрыть выведем стандартное диалоговое окно с запросом подтверждения закрытия окна (листинг 19.11). Если пользователь нажмет кноп- ку Да, закроем окно, а если щелкнет кнопку Нет или просто закроет диалоговое окно, не будем его закрывать.
Листинг 19.11. Обработка закрытия окна
# -*- coding: utf-8 -*- from PyQt5 import QtWidgets class MyWindow(QtWidgets.QWidget): def __init__(self, parent=None):
QtWidgets.QWidget.__init__(self, parent) self.resize(300, 100) def closeEvent(self, e): result = QtWidgets.QMessageBox.question(self,
"Подтверждение закрытия окна",
"Вы действительно хотите закрыть окно?",
QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No,
QtWidgets.QMessageBox.No) if result == QtWidgets.QMessageBox.Yes: e.accept()
QtWidgets.QWidget.closeEvent(self, e) else: e.ignore()
414
Часть II. Библиотека PyQt 5 if __name__ == "__main__": import sys app = QtWidgets.QApplication(sys.argv) window = MyWindow() window.show() sys.exit(app.exec_())
19.8. События клавиатуры
События клавиатуры обрабатываются очень часто. Например, при нажатии клавиши
19.8.1. Установка фокуса ввода
В текущий момент времени только один компонент (или вообще ни одного) может иметь фокус ввода. Для управления фокусом ввода предназначены следующие методы класса
QWidget
:
setFocus([<Причина>])
— устанавливает фокус ввода, если компонент находится в ак- тивном окне. В параметре
<Причина>
можно указать причину изменения фокуса ввода в виде одного из следующих атрибутов класса
QtCore.Qt
:
•
MouseFocusReason
—
0
— фокус изменен с помощью мыши;
•
TabFocusReason
—
1
— нажата клавиша
•
BacktabFocusReason
—
2
— нажата комбинация клавиш
•
ActiveWindowFocusReason
—
3
— окно стало активным или неактивным;
•
PopupFocusReason
—
4
— открыто или закрыто всплывающее окно;
•
ShortcutFocusReason
—
5
— нажата комбинация клавиш быстрого доступа;
•
MenuBarFocusReason
—
6
— фокус изменился из-за меню;
•
OtherFocusReason
—
7
— другая причина;
clearFocus()
— убирает фокус ввода с компонента;
hasFocus()
— возвращает значение
True
, если компонент имеет фокус ввода, и
False
— в противном случае;
focusWidget()
— возвращает ссылку на последний компонент, для которого вызывался метод setFocus()
. Для компонентов верхнего уровня возвращается ссылка на компо- нент, который получит фокус после того, как окно станет активным;
setFocusProxy(
— позволяет указать ссылку на компонент, который будет получать фокус ввода вместо текущего компонента;
focusProxy()
— возвращает ссылку на компонент, который обрабатывает фокус ввода вместо текущего компонента. Если такого компонента нет, метод возвращает значение
None
;
focusNextChild()
— находит следующий компонент, которому можно передать фокус, и передает фокус ему. Фактически работает аналогично нажатию клавиши
True
, если компонент найден, и
False
— в противном случае;
Глава 19. Обработка сигналов и событий
415
focusPreviousChild()
— находит предыдущий компонент, которому можно передать фокус, и передает фокус ему. Работает аналогично нажатию комбинации клавиш
True
, если компонент найден, и
False
— в против- ном случае;
focusNextPrevChild(<Флаг>)
— если в параметре указано значение
True
, работает ана- логично методу focusNextChild()
, если указано
False
— аналогично методу focusPreviousChild()
. Возвращает значение
True
, если компонент найден, и
False
— в противном случае;
setTabOrder(<Компонент1>, <Компонент2>)
— позволяет задать последовательность сме- ны фокуса при нажатии клавиши
<Компонент2>
указывается ссылка на компонент, на который переместится фокус с ком- понента
<Компонент1>
. Если компонентов много, метод вызывается несколько раз. Вот пример указания цепочки перехода widget1 -> widget2 -> widget3 -> widget4
:
QtWidgets.QWidget.setTabOrder(widget1, widget2)
QtWidgets.QWidget.setTabOrder(widget2, widget3)
QtWidgets.QWidget.setTabOrder(widget3, widget4)
setFocusPolicy(<Способ>)
— задает способ получения фокуса компонентом в виде одного из следующих атрибутов класса
QtCore.Qt
:
•
NoFocus
—
0
— компонент не может получать фокус;
•
TabFocus
—
1
— получает фокус с помощью клавиши
•
ClickFocus
—
2
— получает фокус с помощью щелчка мышью;
•
StrongFocus
—
11
— получает фокус с помощью клавиши
•
WheelFocus
—
15
— получает фокус с помощью клавиши
focusPolicy()
— возвращает текущий способ получения фокуса;
grabKeyboard()
— захватывает ввод с клавиатуры. Другие компоненты не будут полу- чать события клавиатуры, пока не будет вызван метод releaseKeyboard()
;
releaseKeyboard()
— освобождает захваченный ранее ввод с клавиатуры.
Получить ссылку на компонент, находящийся в фокусе ввода, позволяет статический метод focusWidget()
класса
QApplication
. Если ни один компонент не имеет фокуса ввода, метод возвращает значение
None
. Не путайте этот метод с одноименным методом из класса
QWidget
Обработать получение и потерю фокуса ввода позволяют следующие методы класса
QWidget
:
focusInEvent(self,
— вызывается при получении фокуса ввода;
focusOutEvent(self,
— вызывается при потере фокуса ввода.
Через параметр
доступен экземпляр класса
QFocusEvent
, который поддерживает следующие методы:
gotFocus()
— возвращает значение
True
, если тип события
QEvent.FocusIn
(получение фокуса ввода), и
False
— в противном случае;
lostFocus()
— возвращает значение
True
, если тип события
QEvent.FocusOut
(потеря фокуса ввода), и
False
— в противном случае;
416
Часть II. Библиотека PyQt 5
reason()
— возвращает причину установки фокуса. Значение аналогично значению па- раметра
<Причина>
в методе setFocus()
Создадим окно с кнопкой и двумя однострочными полями ввода (листинг 19.12). Для полей ввода обработаем получение и потерю фокуса ввода, а по нажатию кнопки установим фокус ввода на второе поле. Кроме того, зададим последовательность перехода при нажатии кла- виши
Листинг 19.12. Установка фокуса ввода
# -*- coding: utf-8 -*- from PyQt5 import QtWidgets class MyLineEdit(QtWidgets.QLineEdit): def __init__(self, id, parent=None):
QtWidgets.QLineEdit.__init__(self, parent) self.id = id def focusInEvent(self, e): print("Получен фокус полем", self.id)
QtWidgets.QLineEdit.focusInEvent(self, e) def focusOutEvent(self, e): print("Потерян фокус полем", self.id)
QtWidgets.QLineEdit.focusOutEvent(self, e) class MyWindow(QtWidgets.QWidget): def __init__(self, parent=None):
QtWidgets.QWidget.__init__(self, parent) self.resize(300, 100) self.button = QtWidgets.QPushButton("Установить фокус на поле 2") self.line1 = MyLineEdit(1) self.line2 = MyLineEdit(2) self.vbox = QtWidgets.QVBoxLayout() self.vbox.addWidget(self.button) self.vbox.addWidget(self.line1) self.vbox.addWidget(self.line2) self.setLayout(self.vbox) self.button.clicked.connect(self.on_clicked)
# Задаем порядок обхода с помощью клавиши
QtWidgets.QWidget.setTabOrder(self.line1, self.line2)
QtWidgets.QWidget.setTabOrder(self.line2, self.button) def on_clicked(self): self.line2.setFocus() if __name__ == "__main__": import sys app = QtWidgets.QApplication(sys.argv) window = MyWindow() window.show() sys.exit(app.exec_())
Глава 19. Обработка сигналов и событий
417 19.8.2. Назначение клавиш быстрого доступа
Клавиши быстрого доступа (иногда их также называют «горячими» клавишами) позволяют установить фокус ввода с помощью нажатия специальной (например,
Чтобы задать клавиши быстрого доступа, следует в тексте надписи указать символ
&
перед буквой. В этом случае буква, перед которой указан символ
&
, будет — в качестве подсказки пользователю — подчеркнута. При одновременном нажатии клавиши
класса
QLabel
. Если же создание надписи не представляется воз- можным, можно воспользоваться следующими методами класса
QWidget
:
grabShortcut(<Клавиши>[, <Контекст>])
— регистрирует клавиши быстрого доступа и возвращает идентификатор, с помощью которого можно управлять ими в дальнейшем.
В параметре
<Клавиши>
указывается экземпляр класса
QKeySequence из модуля
QtGui
Создать экземпляр этого класса для комбинации клавиш
QtGui.QKeySequence.mnemonic("&e")
QtGui.QKeySequence("Alt+e")
QtGui.QKeySequence(QtCore.Qt.ALT + QtCore.Qt.Key_E)
В параметре
<Контекст>
можно указать атрибуты
WidgetShortcut
,
WidgetWithChildren-
Shortcut
,
WindowShortcut
(значение по умолчанию) и
ApplicationShortcut класса
QtCore.Qt
;
releaseShortcut(
— удаляет комбинацию с идентификатором
;
setShortcutEnabled(
— если в качестве параметра
<Флаг>
указано
True
(значение по умолчанию), клавиша быстрого доступа с идентификатором
разреше- на. Значение
False запрещает использование клавиши быстрого доступа.
При нажатии клавиш быстрого доступа генерируется событие
QEvent
Shortcut
, которое можно обработать в методе event(self,
. Через параметр
доступен экзем- пляр класса
QShortcutEvent
, поддерживающий следующие методы:
shortcutId()
— возвращает идентификатор комбинации клавиш;
isAmbiguous()
— возвращает значение
True
, если событие отправлено сразу нескольким компонентам, и
False
— в противном случае;
key()
— возвращает экземпляр класса
QKeySequence
, представляющий нажатую клавишу быстрого доступа.
Создадим окно с надписью, двумя однострочными текстовыми полями и кнопкой (лис- тинг 19.13). Для первого текстового поля назначим комбинацию клавиш
Для кнопки назначим комбинацию клавиш
Листинг 19.13. Назначение клавиш быстрого доступа разными способами
# -*- coding: utf-8 -*- from PyQt5 import QtCore, QtGui, QtWidgets
418
Часть II. Библиотека PyQt 5 class MyLineEdit(QtWidgets.QLineEdit): def __init__(self, parent=None):
QtWidgets.QLineEdit.__init__(self, parent) self.id = None def event(self, e): if e.type() == QtCore.QEvent.Shortcut: if self.id == e.shortcutId(): self.setFocus(QtCore.Qt.ShortcutFocusReason) return True return QtWidgets.QLineEdit.event(self, e) class MyWindow(QtWidgets.QWidget): def __init__(self, parent=None):
QtWidgets.QWidget.__init__(self, parent) self.resize(300, 100) self.label = QtWidgets.QLabel("Устано&вить фокус на поле 1") self.lineEdit1 = QtWidgets.QLineEdit() self.label.setBuddy(self.lineEdit1) self.lineEdit2 = MyLineEdit() self.lineEdit2.id = self.lineEdit2.grabShortcut(
QtGui.QKeySequence.mnemonic("&е")) self.button = QtWidgets.QPushButton("&Убрать фокус с поля 1") self.vbox = QtWidgets.QVBoxLayout() self.vbox.addWidget(self.label) self.vbox.addWidget(self.lineEdit1) self.vbox.addWidget(self.lineEdit2) self.vbox.addWidget(self.button) self.setLayout(self.vbox) self.button.clicked.connect(self.on_clicked) def on_clicked(self): self.lineEdit1.clearFocus() if __name__ == "__main__": import sys app = QtWidgets.QApplication(sys.argv) window = MyWindow() window.show() sys.exit(app.exec_())
Помимо рассмотренных способов, для назначения клавиш быстрого доступа можно вос- пользоваться классом
QShortcut из модуля
QtWidgets
. В этом случае назначение клавиш для второго текстового поля будет выглядеть так: self.lineEdit2 = QtWidgets.QLineEdit() self.shc = QtWidgets.QShortcut(QtGui.QKeySequence.mnemonic("&е"), self) self.shc.setContext(QtCore.Qt.WindowShortcut) self.shc.activated.connect(self.lineEdit2.setFocus)
Назначить комбинацию быстрых клавиш также позволяет класс
QAction из модуля
QtWidgets
. Назначение клавиш для второго текстового поля выглядит следующим образом:
Глава 19. Обработка сигналов и событий
419 self.lineEdit2 = QtWidgets.QLineEdit() self.act = QtWidgets.QAction(self) self.act.setShortcut(QtGui.QKeySequence.mnemonic("&е")) self.act.triggered.connect(self.lineEdit2.setFocus) self.addAction(self.act)
19.8.3. Нажатие и отпускание клавиши на клавиатуре
При нажатии и отпускании клавиши вызываются следующие методы:
keyPressEvent(self,
— вызывается при нажатии клавиши на клавиатуре. Если клавишу удерживать нажатой, метод будет вызываться многократно, пока клавишу не отпустят;
keyReleaseEvent(self,
— вызывается при отпускании нажатой ранее клавиши.
Через параметр
доступен экземпляр класса
QKeyEvent
, хранящий дополнительную информацию о событии. Он поддерживает следующие полезные для нас методы (полный их список приведен в документации по классу
QKeyEvent на странице https://doc.qt.io/ qt-5/qkeyevent.html):
key()
— возвращает код нажатой клавиши. Пример определения клавиши: if e.key() == QtCore.Qt.Key_B: print("Нажата клавиша
1 ... 34 35 36 37 38 39 40 41 ... 83
")
text()
— возвращает текстовое представление введенного символа в кодировке Unicode или пустую строку, если была нажата специальная клавиша;
modifiers()
— позволяет определить, какие клавиши-модификаторы (
QtCore.Qt или их комбинацию:
•
NoModifier
— модификаторы не были нажаты;
•
ShiftModifier
— была нажата клавиша
•
ControlModifier
— была нажата клавиша
•
AltModifier
— была нажата клавиша
•
MetaModifier
— была нажата клавиша ;
•
KeypadModifier
— была нажата любая клавиша на дополнительной клавиатуре;
•
GroupSwitchModifier
— была нажата клавиша
Вот пример определения, была ли нажата клавиша-модификатор
isAutoRepeat()
— возвращает
True
, если событие было вызвано удержанием клавиши нажатой, и
False
— в противном случае;
matches(
— возвращает значение
True
, если была нажата специальная комбинация клавиш, соответствующая указанному значению, и
False
— в противном случае. В качестве значения указываются атрибуты из класса
QKeySequence
— например,
QKeySequence.Copy для комбинации клавиш
420
Часть II. Библиотека PyQt 5 if e.matches(QtGui.QKeySequence.Copy): print("Нажата комбинация
Полный список атрибутов содержится в документации по классу
QKeySequence
(см. https:// doc.qt.io/qt-5/qkeysequence.html#StandardKey-enum).
При обработке нажатия клавиш следует учитывать, что:
компонент должен иметь возможность принимать фокус ввода. Некоторые компоненты по умолчанию не могут принимать фокус ввода — например, надпись. Чтобы изменить способ получения фокуса, следует воспользоваться методом setFocusPolicy(<Способ>)
, который мы рассматривали в разд. 19.8.1;
чтобы захватить эксклюзивный ввод с клавиатуры, следует воспользоваться методом grabKeyboard()
, а чтобы освободить ввод — методом releaseKeyboard()
;
можно перехватить нажатие любых клавиш, кроме клавиши
;
если событие обработано, следует вызвать метод accept()
объекта события. Чтобы родительский компонент смог получить событие, вместо метода accept()
необходимо вызвать метод ignore()
19.9. События мыши
События мыши обрабатываются не реже, чем события клавиатуры. С помощью специаль- ных методов можно обработать нажатие и отпускание кнопки мыши, перемещение указате- ля, вхождение указателя в область компонента и выхода из этой области. В зависимости от ситуации можно изменить вид указателя — например, при выполнении длительной опера- ции отобразить указатель в виде песочных часов. В этом разделе мы рассмотрим изменение вида указателя мыши как для отдельного компонента, так и для всего приложения.
19.9.1. Нажатие и отпускание кнопки мыши
При нажатии и отпускании кнопки мыши вызываются следующие методы:
mousePressEvent(self,
— вызывается при нажатии кнопки мыши;
mouseReleaseEvent(self,
— вызывается при отпускании ранее нажатой кнопки мыши;
mouseDoubleClickEvent(self,
— вызывается при двойном щелчке мышью в области компонента. Следует учитывать, что двойному щелчку предшествуют другие события. Последовательность событий при двойном щелчке выглядит так:
MouseButtonPress
MouseButtonRelease
MouseButtonDblClick
MouseButtonPress
MouseButtonRelease
Задать интервал двойного щелчка позволяет метод setDoubleClickInterval()
класса
QApplication
, а получить его текущее значение — метод doubleClickInterval()
того же класса.
Глава 19. Обработка сигналов и событий
421
Через параметр
доступен экземпляр класса
QMouseEvent
, хранящий дополнительную информацию о событии. Он поддерживает такие методы:
x()
и y()
— возвращают координаты по осям
X
и
Y
соответственно в пределах области компонента;
pos()
— возвращает экземпляр класса
QPoint с целочисленными координатами в преде- лах области компонента;
localPos()
— возвращает экземпляр класса
QPointF
с вещественными координатами в пределах области компонента;
globalX()
и globalY()
— возвращают координаты по осям
X
и
Y
соответственно в преде- лах экрана;
globalPos()
— возвращает экземпляр класса
QPoint с координатами в пределах экрана;
windowPos()
— возвращает экземпляр класса
QPointF
с вещественными координатами в пределах окна;
screenPos()
— возвращает экземпляр класса
QPointF
с вещественными координатами в пределах экрана;
button()
— позволяет определить, щелчок какой кнопкой мыши вызвал событие. Воз- вращает значение одного из следующих атрибутов класса
QtCore.Qt
(здесь указаны не все атрибуты, полный их список приведен на странице https://doc.qt.io/qt-5/qt.html#
MouseButton-enum):
•
NoButton
—
0
— кнопки не нажаты. Это значение возвращается методом button()
при перемещении указателя мыши;
•
LeftButton
—
1
— нажата левая кнопка мыши;
•
RightButton
—
2
— нажата правая кнопка мыши;
•
MidButton и
MiddleButton
—
4
— нажата средняя кнопка мыши;
•
XButton1
,
ExtraButton1
и
BackButton
—
8
— нажата первая из дополнительных кно- пок мыши;
•
XButton2
,
ExtraButton2
и
ForwardButton
—
16
— нажата вторая из дополнительных кнопок мыши;
buttons()
— позволяет определить все кнопки, которые нажаты одновременно. Возвра- щает комбинацию упомянутых ранее атрибутов. Вот пример определения нажатой кнопки мыши: if e.buttons() & QtCore.Qt.LeftButton: print("Нажата левая кнопка мыши")
modifiers()
— позволяет определить, какие клавиши-модификаторы (
timestamp()
— возвращает в виде числа отметку системного времени, в которое возник- ло событие.
Если событие было успешно обработано, следует вызвать метод accept()
объекта события.
Чтобы родительский компонент мог получить событие, вместо метода accept()
нужно вы- звать метод ignore()
422
Часть II. Библиотека PyQt 5
Если у компонента атрибут
WA_NoMousePropagation класса
QtCore.Qt установлен в
True
, со- бытие мыши не будет передаваться родительскому компоненту. Значение атрибута можно изменить с помощью метода setAttribute()
, вызванного у этого компонента: button.setAttribute(QtCore.Qt.WA_NoMousePropagation, True)
По умолчанию событие мыши перехватывает компонент, над которым был произведен щелчок мышью. Чтобы перехватывать нажатие и отпускание мыши вне компонента, следу- ет захватить мышь вызовом метода grabMouse()
. Освободить захваченную ранее мышь по- зволяет метод releaseMouse()
19.9.2. Перемещение указателя мыши
Чтобы обработать перемещение указателя мыши, необходимо переопределить метод mouseMoveEvent(self,
. Через параметр
доступен экземпляр класса
QMouseEvent
, содержащий дополнительную информацию о событии. Методы этого класса мы уже рассматривали в предыдущем разделе. Следует учитывать, что метод button()
при перемещении мыши возвращает значение
QtCore.Qt.NoButton
По умолчанию метод mouseMoveEvent()
вызывается только в том случае, если при переме- щении удерживается нажатой какая-либо кнопка мыши. Это сделано специально, чтобы не создавать лишних событий при обычном перемещении указателя мыши. Если необходимо обрабатывать любые перемещения указателя в пределах компонента, следует вызвать у это- го компонента метод setMouseTracking()
, которому передать значение
True
. Чтобы обрабо- тать все перемещения внутри окна, нужно дополнительно захватить мышь вызовом метода grabMouse()
Метод pos()
объекта события возвращает позицию точки в системе координат текущего компонента. Чтобы преобразовать координаты точки в систему координат родительского компонента или в глобальную систему координат, нужно воспользоваться следующими методами класса
QWidget
:
mapToGlobal(
— преобразует координаты точки из системы координат компо- нента в глобальную систему координат. Возвращает экземпляр класса
QPoint
;
mapFromGlobal(
— преобразует координаты точки из глобальной в систему координат компонента. Возвращает экземпляр класса
QPoint
;
mapToParent(
— преобразует координаты точки из системы координат компо- нента в систему координат родительского компонента. Если компонент не имеет роди- теля, действует как метод mapToGlobal()
. Возвращает экземпляр класса
QPoint
;
mapFromParent(
— преобразует координаты точки из системы координат роди- тельского компонента в систему координат текущего компонента. Если компонент не имеет родителя, работает подобно методу mapFromGlobal()
. Возвращает экземпляр клас- са
QPoint
;
mapTo(
— преобразует координаты точки из системы координат текущего компонента в систему координат родительского компонента
. Воз- вращает экземпляр класса
QPoint
;
mapFrom(
— преобразует координаты точки из системы координат родительского компонента
в систему координат текущего компонента. Воз- вращает экземпляр класса
QPoint
Глава 19. Обработка сигналов и событий
423 19.9.3. Наведение и увод указателя
Обработать наведение указателя мыши на компонент и увод его с компонента позволяют следующие методы:
enterEvent(self,
— вызывается при наведении указателя мыши на область компонента;
leaveEvent(self,
— вызывается, когда указатель мыши покидает область компонента.
Через параметр
доступен экземпляр класса
QEvent
, не несущий никакой дополни- тельной информации. Вполне достаточно знать, что указатель попал в область компонента или покинул ее.
19.9.4. Прокрутка колесика мыши
Все современные мыши комплектуются колесиком, обычно используемым для прокрутки содержимого компонента. Обработать поворот колесика позволяет метод wheelEvent(self,
. Через параметр
доступен экземпляр класса
QWheelEvent
, который позво- ляет получить дополнительную информацию о событии.
Класс
QWheelEvent поддерживает методы:
angleDelta()
— возвращает угол поворота колесика в градусах, умноженный на 8, в ви- де экземпляра класса
QPoint
. Это значение может быть положительным или отрицатель- ным — в зависимости от направления поворота. Вот пример определения угла поворота колесика: angle = e.angleDelta() / 8 angleX = angle.x() angleY = angle.y()
pixelDelta()
— возвращает величину поворота колесика в пикселах в виде экземпляра класса
QPoint
. Это значение может быть положительным или отрицательным — в зави- симости от направления поворота;
x()
и y()
— возвращают координаты указателя в момент события по осям
X
и
Y
соответ- ственно в пределах области компонента;
pos()
— возвращает экземпляр класса
QPoint с целочисленными координатами указате- ля в момент события в пределах области компонента;
posF()
— возвращает экземпляр класса
QPointF
с вещественными координатами указа- теля в момент события в пределах области компонента;
globalX()
и globalY()
— возвращают координаты указателя в момент события по осям
X
и
Y
соответственно в пределах экрана;
globalPos()
— возвращает экземпляр класса
QPoint с целочисленными координатами указателя в момент события в пределах экрана;
globalPosF()
— возвращает экземпляр класса
QPointF
с вещественными координатами указателя в момент события в пределах экрана;
buttons()
— позволяет определить кнопки, которые нажаты одновременно с поворотом колесика. Возвращает комбинацию значений атрибутов, указанных в описании метода buttons()
(см. разд. 19.9.1). Вот пример определения нажатой кнопки мыши:
424
Часть II. Библиотека PyQt 5 if e.buttons() & QtCore.Qt.LeftButton: print("Нажата левая кнопка мыши")
modifiers()
— позволяет определить, какие клавиши-модификаторы (
timestamp()
— возвращает в виде числа отметку системного времени, в которое возник- ло событие.
Если событие было успешно обработано, необходимо вызвать метод accept()
объекта события. Чтобы родительский компонент мог получить событие, вместо метода accept()
необходимо вызвать метод ignore()
19.9.5. Изменение внешнего вида указателя мыши
Для изменения внешнего вида указателя мыши при вхождении его в область компонента предназначены следующие методы класса
QWidget
:
setCursor(<Курсор>)
— задает внешний вид указателя мыши для компонента. В качест- ве параметра указывается экземпляр класса
QCursor или следующие атрибуты из класса
QtCore.Qt
:
ArrowCursor
(стандартная стрелка),
UpArrowCursor
(стрелка, направленная вверх),
CrossCursor
(крестообразный указатель),
WaitCursor
(песочные часы),
IBeamCursor
(I-образный указатель),
SizeVerCursor
(стрелки, направленные вверх и вниз),
SizeHorCursor
(стрелки, направленные влево и вправо),
SizeBDiagCursor
(стрелки, направленные в правый верхний угол и левый нижний угол),
SizeFDiagCursor
(стрелки, направленные в левый верхний угол и правый нижний угол),
SizeAllCursor
(стрелки, направленные вверх, вниз, влево и вправо),
SplitVCursor
(указатель изменения высоты),
SplitHCursor
(указатель изменения ширины),
PointingHandCursor
(указатель в виде руки),
ForbiddenCursor
(перечеркнутый круг),
OpenHandCursor
(разжатая рука),
ClosedHandCursor
(сжатая рука),
WhatsThisCursor
(стрелка с вопросительным знаком) и
BusyCursor
(стрелка с песочными часами): self.setCursor(QtCore.Qt.WaitCursor)
unsetCursor()
— отменяет установку указателя для компонента. В результате внешний вид указателя мыши будет наследоваться от родительского компонента;
cursor()
— возвращает экземпляр класса
QCursor
, представляющий текущий указатель.
Управлять видом указателя для всего приложения сразу можно с помощью следующих ста- тических методов из класса
QApplication
:
setOverrideCursor(<Курсор>)
— задает внешний вид указателя мыши для всего прило- жения. В качестве параметра указывается экземпляр класса
QCursor или один из ранее упомянутых специальных атрибутов класса
QtCore.Qt
. Для отмены установки необхо- димо вызвать метод restoreOverrideCursor()
;
restoreOverrideCursor()
— отменяет изменение внешнего вида указателя мыши для всего приложения:
QtWidgets.QApplication.setOverrideCursor(QtCore.Qt.WaitCursor)
# Выполняем длительную операцию
QtWidgets.QApplication.restoreOverrideCursor()
changeOverrideCursor(<Курсор>)
— изменяет внешний вид указателя мыши для всего приложения. Если до вызова этого метода не вызывался метод setOverrideCursor()
,
Глава 19. Обработка сигналов и событий
425 значение будет проигнорировано. В качестве параметра указывается экземпляр класса
QCursor или один из специальных атрибутов класса
QtCore.Qt
;
overrideCursor()
— возвращает экземпляр класса
QCursor
, представляющий текущий указатель, или значение
None
, если таковой не был изменен.
Изменять внешний вид указателя мыши для всего приложения принято на небольшой промежуток времени — обычно на время выполнения какой-либо операции, в процессе которой приложение не может нормально реагировать на действия пользователя. Чтобы информировать об этом пользователя, указатель принято выводить в виде песочных часов
(атрибут
WaitCursor
) или стрелки с песочными часами (атрибут
BusyCursor
).
Метод setOverrideCursor()
может быть вызван несколько раз. В этом случае курсоры помещаются в стек. Каждый вызов метода restoreOverrideCursor()
удаляет последний курсор, добавленный в стек. Для нормальной работы приложения необходимо вызывать методы setOverrideCursor()
и restoreOverrideCursor()
одинаковое количество раз.
Класс
QCursor позволяет создать объект курсора с изображением любой формы. Чтобы за- грузить изображение, следует передать конструктору класса
QPixmap путь к файлу изобра- жения. Для создания объекта курсора необходимо передать конструктору класса
QCursor в первом параметре экземпляр класса
QPixmap
, а во втором и третьем параметрах — коорди- наты «горячей» точки будущего курсора. Вот пример создания и установки пользователь- ского курсора: self.setCursor(QtGui.QCursor(QtGui.QPixmap("cursor.png"), 0, 0))
Класс
QCursor также поддерживает два статических метода:
pos()
— возвращает экземпляр класса
QPoint с координатами указателя мыши относи- тельно экрана: p = QtGui.QCursor.pos() print(p.x(), p.y())
setPos()
— позволяет задать позицию указателя мыши. Метод имеет два формата: setPos(
и setPos(
19.10. Технология drag & drop
Технология drag & drop позволяет обмениваться данными различных типов между компо- нентами как одного приложения, так и разных приложений, путем перетаскивания и сбра- сывания объектов с помощью мыши. Типичным примером использования технологии слу- жит перемещение файлов в Проводнике Windows. Чтобы переместить файл в другой ката- лог, достаточно нажать левую кнопку мыши над значком файла и, не отпуская кнопки, перетащить файл на значок каталога, а затем отпустить кнопку мыши. Если необходимо скопировать файл, следует дополнительно удерживать нажатой клавишу
19.10.1. Запуск перетаскивания
Операция перетаскивания состоит из двух частей: первая часть запускает процесс, а вторая обрабатывает момент сброса объекта. Обе части могут обрабатываться как одним, так и двумя разными приложениями. Запуск перетаскивания осуществляется следующим об- разом:
1.
Внутри метода mousePressEvent()
запоминаются координаты указателя мыши в момент щелчка ее левой кнопкой.
426
Часть II. Библиотека PyQt 5 2.
Внутри метода mouseMoveEvent()
вычисляется пройденное расстояние или измеряется время операции. Это необходимо для того, чтобы предотвратить случайное перетаски- вание. Управлять задержкой позволяют следующие статические методы класса
QApplication
:
• setStartDragDistance(<Дистанция>)
— задает минимальное расстояние, после про- хождения которого будет запущена операция перетаскивания;
• startDragDistance()
— возвращает это расстояние;
• setStartDragTime(<Время>)
— задает время задержки в миллисекундах перед запус- ком операции перетаскивания;
• startDragTime()
— возвращает это время.
3.
Если пройдено минимальное расстояние или истек минимальный промежуток времени, создается экземпляр класса
QDrag
, и у него вызывается метод exec()
, который после за- вершения операции возвращает действие, выполненное с данными (например, их копи- рование или перемещение).
Создать экземпляр класса
QDrag можно так:
<Объект> = QtGui.QDrag(<Ссылка на компонент>)
Класс
QDrag поддерживает следующие методы:
exec()
— запускает процесс перетаскивания и возвращает действие, которое было вы- полнено по завершении операции. Метод имеет два формата: exec([<Действия>=MoveAction]) exec(<Действия>, <Действие по умолчанию>)
В параметре
<Действия>
указывается комбинация допустимых действий, а в параметре
<Действие по умолчанию>
— действие, которое осуществляется, если в процессе выпол- нения операции не были нажаты клавиши-модификаторы. Возможные действия могут быть заданы следующими атрибутами класса
QtCore.Qt
:
CopyAction
(
1
, копирование),
MoveAction
(
2
, перемещение),
LinkAction
(
4
, создание ссылки),
IgnoreAction
(
0
, действие игнорировано),
TargetMoveAction
(
32770
): act = drag.exec(QtCore.Qt.MoveAction | QtCore.Qt.CopyAction,
QtCore.Qt.MoveAction)
Вместо метода exec()
можно использовать аналогичный метод exec_()
, сохраненный в PyQt 5 для совместимости с кодом, написанным под библиотеку PyQt 4;
setMimeData(
— позволяет задать перемещаемые данные. В качестве значе- ния указывается экземпляр класса
QMimeData
. Вот пример передачи текста: data = QtCore.QMimeData() data.setText("Перетаскиваемый текст") drag = QtGui.QDrag(self) drag.setMimeData(data)
mimeData()
— возвращает экземпляр класса
QMimeData с перемещаемыми данными;
setPixmap(
— задает изображение, которое будет перемещаться вместе с ука- зателем мыши. В качестве параметра указывается экземпляр класса
QPixmap
: drag.setPixmap(QtGui.QPixmap("dd_representer.png"))
pixmap()
— возвращает экземпляр класса
QPixmap с изображением, которое перемещает- ся вместе с указателем;
Глава 19. Обработка сигналов и событий
427
setHotSpot(
— задает координаты «горячей» точки на перемещаемом изобра- жении. В качестве параметра указывается экземпляр класса
QPoint
: drag.setHotSpot(QtCore.QPoint(20, 20))
hotSpot()
— возвращает экземпляр класса
QPoint с координатами «горячей» точки на перемещаемом изображении;
setDragCursor(
— позволяет изменить внешний вид указателя мыши для действия, указанного во втором параметре. Первым параметром передается экземпляр класса
QPixmap
, который, собственно, станет указателем мыши. Если в первом параметре указан пустой объект класса
QPixmap
, ранее установленный указатель для дей- ствия будет отменен. Вот пример изменения указателя для перемещения: drag.setDragCursor(QtGui.QPixmap("move_cursor.png"),
QtCore.Qt.MoveAction)
dragCursor(<Действие>)
— возвращает экземпляр класса
QPixmap
, представляющий ука- затель мыши для заданного действия;
source()
— возвращает ссылку на компонент-источник;
target()
— возвращает ссылку на компонент-приемник или значение
None
, если компо- нент находится в другом приложении;
supportedActions()
— возвращает значение, представляющее комбинацию допустимых в текущей операции действий. Возможные действия обозначаются упомянутыми ранее атрибутами класса
QtCore.Qt
;
defaultAction()
— возвращает действие по умолчанию в виде одного из перечисленных ранее атрибутов класса
QtCore.Qt
Класс
QDrag поддерживает два сигнала:
actionChanged(<Действие>)
— генерируется при изменении действия. Новое действие представляется одним из упомянутых ранее атрибутов класса
QtCore.Qt
;
targetChanged(<Компонент>)
— генерируется при изменении принимающего компонен- та, который представляется экземпляром соответствующего класса.
Вот пример назначения обработчиков сигналов: drag.actionChanged.connect(self.on_action_changed) drag.targetChanged.connect(self.on_target_changed)
19.10.2. Класс QMimeData
Перемещаемые данные и сведения о MIME-типе должны быть представлены экземпляром класса
QMimeData
. Его следует передать в метод setMimeData()
класса
QDrag
. Выражение, создающее экземпляр класса
QMimeData
, выглядит так: data = QtCore.QMimeData()
Класс
QMimeData поддерживает следующие полезные для нас методы (полный их список приведен на странице https://doc.qt.io/qt-5/qmimedata.html):
setText(<Текст>)
— устанавливает текстовые данные (MIME-тип text/plain
): data.setText("Перетаскиваемый текст")
text()
— возвращает текстовые данные;
428
Часть II. Библиотека PyQt 5
hasText()
— возвращает значение
True
, если объект содержит текстовые данные, и
False
— в противном случае;
setHtml(
1 ... 35 36 37 38 39 40 41 42 ... 83