{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# События\n", "\n", "\n", "\n", "\n", "## Обработка событий\n", "\n", "Любое взаимодействие пользователя с приложением `Qt`, будь то клики мышкой или нажатия клавиш клавиатуры, осуществляется в форме событий (`events`). Кроме пользователя события могут быть порождены операционной системой, самим приложением или другими событиями. Чтобы как-то реагировать на эти события, приложение должно следить за их возникновениями и как-то их обрабатывать (`event handling`). В частности, метод `exec` приложения запускает событийный цикл (`event loop`), которая и организовывает всё взаимодействие с графическим интерфейсом.\n", "\n", "```{figure} ../_static/lecture_specific/qt/event-loop.png\n", "Картинка позаимствована [отсюда](https://www.pythonguis.com/tutorials/creating-your-first-pyqt-window/).\n", "```\n", "\n", "Каждое взаимодействие с приложением порождает объект типа [QtCore.QEvent](https://doc.qt.io/qtforpython/PySide6/QtCore/QEvent.html?highlight=qevent) или производного от него типа. Затем событие помещается в очередь событий (`event queue`). Событийный цикл на каждой итерации проверяет эту очередь и, если находится ожидающее событие, передаёт управление соответствующему обработчику (`event handler`). \n", "\n", "Например, щелчок мышью на виджет состоит из двух событий: зажатие кнопки мыши `QEvent.MouseButtonPress` и отпуск кнопки мыши `QEvent.MouseButtonRelease`. Оба из них генерируют объект события типа [QMouseEvent](https://doc.qt.io/qtforpython/PySide6/QtGui/QMouseEvent.html#qmouseevent). Чтобы их обработать особым образом, необходимо переопределить у виджета соответствующие обработчики.\n", "\n", "В качестве примера реализуем обработчик события нажатия клавишей мыши на главное окно. Для этого необходимо перегрузить метод [mousePressEvent](https://doc.qt.io/qtforpython/PySide6/QtWidgets/QWidget.html#PySide6.QtWidgets.PySide6.QtWidgets.QWidget.mousePressEvent) класса `QMainWindow`. Перегрузим его так, чтобы он печатал в консоль информацию о том, где и какая конкретно кнопка была нажата. Информацию об этом можно получить из объекта [QMouseEvent](https://doc.qt.io/qtforpython/PySide6/QtGui/QMouseEvent.html#qmouseevent), который этот метод получает в качестве параметра.\n", "\n", "```python\n", "import sys\n", "\n", "from PySide6.QtWidgets import QApplication, QMainWindow\n", "\n", "\n", "class MainWindow(QMainWindow):\n", " def mousePressEvent(self, event):\n", " button_name = event.button().name.decode()\n", " pos = event.position() \n", " x, y = pos.x(), pos.y()\n", " print(f\"{button_name:12} mouse button was pressed at x={int(x):3d} and y={int(y):3d}\")\n", "\n", "\n", "app = QApplication(sys.argv)\n", "main_window = MainWindow()\n", "main_window.show()\n", "app.exec()\n", "```\n", "\n", "При нажатии мышкой в любом месте внутри окна в консоли должно печататься сообщение.\n", "\n", "## Распространение события на родительский виджет\n", "\n", "У `QEvent` есть флаг `accept`. Обработчик события может выставить значение этого флага в `False` методом [QEvent.ignore](https://doc.qt.io/qtforpython/PySide6/QtCore/QEvent.html?highlight=qevent#PySide6.QtCore.PySide6.QtCore.QEvent.ignore), чтобы сигнализировать, что этот виджет не хочет обрабатывать это событие. В таком случае обработка такого события передаётся родительскому (`parent`) виджету.\n", "\n", "Для демонстрации этого определим кнопку, которая будет игнорировать нажатие клавиши мышки. Для этого расширим класс [QPushButton](https://doc.qt.io/qtforpython/PySide6/QtWidgets/QPushButton.html) и перегрузим у него обработчик события `mousePressEvent` вызовом метода `ignore` у объекта события. \n", "```python\n", "class IgnoringButton(QPushButton):\n", " def mousePressEvent(self, event):\n", " event.ignore()\n", "```\n", "Также определим кнопку, которая при нажатии мышкой на ней будет печатать в консоли соответствующее сообщение.\n", "```python\n", "class AcceptingButton(QPushButton):\n", " def mousePressEvent(self, event):\n", " event.accept()\n", " print(\"A mouse button was pressed at the accepting button\")\n", "```\n", "\n", "Добавим эти кнопки на окно из предыдущего примера и получим нечто следующее.\n", "```python\n", "import sys\n", "\n", "from PySide6.QtWidgets import QApplication, QMainWindow, QHBoxLayout, QWidget, QPushButton\n", "\n", "\n", "class IgnoringButton(QPushButton):\n", " def mousePressEvent(self, event):\n", " event.ignore()\n", "\n", "\n", "class AcceptingButton(QPushButton):\n", " def mousePressEvent(self, event):\n", " event.accept()\n", " print(\"A mouse button was pressed at the accepting button\")\n", "\n", "\n", "class MainWindow(QMainWindow):\n", " def __init__(self):\n", " super().__init__()\n", "\n", " widget = QWidget(parent=self)\n", " self.setCentralWidget(widget)\n", "\n", " layout = QHBoxLayout()\n", " layout.addWidget(AcceptingButton(\"Accept\"))\n", " layout.addWidget(IgnoringButton(\"Ignore\"))\n", " widget.setLayout(layout)\n", "\n", " def mousePressEvent(self, event):\n", " print(\"A mouse button was pressed at the main window\")\n", "\n", "\n", "app = QApplication(sys.argv)\n", "main_window = MainWindow()\n", "main_window.show()\n", "app.exec()\n", "```\n", "\n", "Запустив это код вы увидите следующее окно.\n", "```{figure} ../_static/lecture_specific/qt/ignore_event.png\n", "```\n", "Нажав на кнопку `Accept` вы увидите в консоли \"A mouse button was pressed at the accepting button\", а нажав в любом другом месте, в том числе и на кнопку `Ignore`, вы увидите другое сообщение \"A mouse button was pressed at the main window\".\n", "\n", "## Виды событий\n", "\n", "### Мышь\n", "Пока обсуждались только события мыши, каждый из которых создаёт `QMouseEvent`:\n", "- `QEvent.MouseButtonDblClick` --- двойной щелчок клавишей мыши;\n", "- `QEvent.MouseButtonPress` --- нажатие клавиши мыши;\n", "- `QEvent.MouseButtonRelease` --- отпуск клавиши мыши;\n", "- `QEvent.MouseMove` --- перемещение указателя мыши;\n", "\n", "### Клавиатура\n", "Другая пара событий связанна с нажатием и отпуском клавиш клавиатуры, которым соответствует [QKeyEvent](https://doc.qt.io/qtforpython/PySide6/QtGui/QKeyEvent.html#qkeyevent):\n", "- `QEvent.KeyPressEvent` --- нажатие клавиши клавиатуры;\n", "- `QEvent.KeyReleaseEvent` --- отпуск клавиши клавиатуры;\n", "\n", "В качестве примера рассмотрим окно, которое закрывается при нажатии на клавишу `Esc`.\n", "```python\n", "import sys\n", "\n", "from PySide6.QtCore import Qt\n", "from PySide6.QtWidgets import QApplication, QMainWindow\n", "\n", "\n", "class MainWindow(QMainWindow):\n", " def keyPressEvent(self, event):\n", " if event.key() == Qt.Key_Escape:\n", " self.close()\n", "\n", "\n", "app = QApplication(sys.argv)\n", "main_window = MainWindow()\n", "main_window.show()\n", "app.exec()\n", "```\n", "### Ещё\n", "\n", "Существует ещё огромное количество событий операционной системы, самого `Qt` и другие.\n", "\n" ] } ], "metadata": { "language_info": { "name": "python" }, "orig_nbformat": 4 }, "nbformat": 4, "nbformat_minor": 2 }