ээдд. Прохоренок_Н_А__Дронов_В_А_Python_3_и_PyQt_5_Разработка_приложен. Николай Прохоренок Владимир Дронов
Скачать 7.92 Mb.
|
Параметр <Интервал> задает промежуток времени в миллисекундах, по истечении которого выполняется метод 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("Нажата клавиша |