Сталкивались ли вы с ситуацией, когда UI «живёт своей жизнью»: кнопка нажалась, но координаты клика потерялись; окно мигает при ресайзе; перетаскивание ведёт себя непредсказуемо — и непонятно, где именно «ломается» логика?
Эта глава раскроет, что на самом деле происходит внутри Qt, когда «что-то происходит». Вы обнаружите, почему сигналы и слоты не заменяют события, и узнаете секрет, как профессиональные разработчики получают точный контроль над вводом (мышь/клавиатура/тач) без хаоса в коде и без потери производительности.
Будут разобраны accept()/ignore() и цепочка передачи вверх по иерархии, QEvent::type() и центральный event(), а также практические кейсы с QMouseEvent, QKeyEvent и QPaintEvent — включая «упаковку» событий перерисовки, которая может сэкономить вам лишние вызовы paintEvent() и сделать интерфейс ощутимо отзывчивее.
Не откладывайте: если вы пишете собственные виджеты или хотите перестать «угадывать» поведение UI, эту главу пропускать опасно.
И чтобы сразу перейти от теории к практике: в конце доступна ссылка на архив со всеми исходниками примеров (готовы к компиляции) и 16 бесплатными главами.
Самопроверка по главе
Почему события считаются механизмом более низкого уровня по сравнению с сигналами и слотами?Ответ
Правильный ответ: События предоставляют детальную информацию о действиях пользователя (координаты мыши, коды клавиш), тогда как сигналы — это высокоуровневые уведомления без деталей. События обрабатываются одним методом, а сигналы могут быть связаны с множеством слотов.
Какие последствия вызовет метод ignore() в обработчике события и когда это может быть полезно?Ответ
Правильный ответ: Метод ignore() передаёт событие объекту-предку для дальнейшей обработки. Это полезно, когда виджет не может полностью обработать событие и хочет делегировать его родителю.
Почему по умолчанию mouseMoveEvent() вызывается только при нажатой кнопке мыши?Ответ
Правильный ответ: Это оптимизация производительности — предотвращается создание лишних событий при простом перемещении указателя. Для отслеживания всех перемещений нужно вызвать setMouseTracking(true).
Что произойдёт, если отключить двойную буферизацию вызовом setAttribute(Qt::WA_PaintOnScreen)?Ответ
Правильный ответ: Графическая информация будет исчезать при изменении размеров окна или его перекрытии другими окнами, так как без буферизации необходимо постоянно перерисовывать содержимое в paintEvent().
Зачем Qt «упаковывает» серию событий QPaintEvent в одно событие?Ответ
Правильный ответ: Для оптимизации производительности — множество событий перерисовки объединяются в одно с общим регионом, что приводит к однократному вызову paintEvent() вместо множественных.
Почему методы обработки событий определены как virtual protected в Qt?Ответ
Правильный ответ: Virtual позволяет переопределять методы в унаследованных классах, а protected ограничивает их вызов только внутри класса и потомков, что соответствует архитектуре событийной системы Qt.
В каких случаях следует переопределять метод event() вместо специализированных обработчиков?Ответ
Правильный ответ: Только для обработки типов событий, для которых не существует специализированных методов, или когда необходимо перехватить событие до его распределения. В остальных случаях лучше использовать специализированные методы.
Почему клавиша Tab не генерирует события keyPressEvent() по умолчанию?Ответ
Правильный ответ: Клавиша Tab обрабатывается методом event() для управления фокусом — передачи фокуса следующему виджету, что является системным поведением интерфейса.
Как правильно обработать прокрутку колеса мыши для поддержки разных устройств (мышь и тачпад)?Ответ
Правильный ответ: Необходимо проверять оба метода: angleDelta() для мыши с колесиком и pixelDelta() для тачпада, используя тот, который вернул непустое значение.
Что произойдёт, если в интенсивном цикле не вызывать processEvents()?Ответ
Правильный ответ: Графический интерфейс «замрёт» — перестанет реагировать на действия пользователя и не будет перерисовываться, так как очередь событий не обрабатывается во время выполнения цикла.
Почему при двойном щелчке мыши метод mousePressEvent() вызывается дважды?Ответ
Правильный ответ: Двойной щелчок обрабатывается как два отдельных нажатия плюс событие mouseDoubleClickEvent(), чтобы приложение могло реагировать как на одиночные, так и на двойные щелчки.
Зачем нужно устанавливать атрибут Qt::WA_AcceptsTouchEvents для обработки мультитач?Ответ
Правильный ответ: По умолчанию виджеты игнорируют мультитач-события и преобразуют их в события мыши. Атрибут активирует получение настоящих мультитач-событий QTouchEvent.
Как определить, какая из 10 точек касания принадлежит конкретному пальцу в мультитач-интерфейсе?Ответ
Правильный ответ: Используя метод TouchPoint::id(), который возвращает уникальный идентификатор точки касания, сохраняющийся на протяжении всего жеста от нажатия до отпускания.
Почему методы modifiersInfo() и buttonsInfo() используют побитовое И (&) для проверки статуса?Ответ
Правильный ответ: Состояние кнопок и модификаторов хранится в виде битовой маски, где каждый бит представляет отдельную кнопку. Побитовое И проверяет, установлен ли конкретный бит в комбинации.
Какое значение должен вернуть метод event(), чтобы предотвратить дальнейшую передачу события предкам?Ответ
Правильный ответ: Метод должен вернуть true, что означает полную обработку события. Возврат false передаст событие виджету-предку для дальнейшей обработки.
Практические задания
Простой уровень
Виджет информации о клавишах
Создайте виджет, который отображает информацию о нажатой клавише: её код, Unicode-символ (если есть) и статус клавиш-модификаторов (Shift, Ctrl, Alt). При нажатии клавиши информация должна обновляться в QLabel. Реализуйте обработку как нажатия (keyPressEvent), так и отпускания клавиши (keyReleaseEvent) с разными цветами фона.
Подсказки: Унаследуйтесь от QLabel. Используйте методы key(), text() и modifiers() объекта QKeyEvent. Для изменения цвета фона примените setStyleSheet(). Сравнивайте значение key() с константами Qt::Key_*.
Средний уровень
Рисование траектории мыши
Разработайте виджет для рисования, который запоминает траекторию движения мыши при нажатой левой кнопке и отображает её линией. При нажатии правой кнопки цвет линии должен меняться. Реализуйте отслеживание перемещений мыши даже без нажатых кнопок, показывая текущую позицию курсора полупрозрачным кругом. Добавьте возможность очистки холста клавишей Escape.
Подсказки: Используйте QVector<QPoint> для хранения точек траектории. Вызовите setMouseTracking(true) для отслеживания всех перемещений. В paintEvent() рисуйте линии через сохранённые точки. Обрабатывайте события mousePressEvent, mouseMoveEvent и keyPressEvent.
Сложный уровень
Мультитач-холст с жестами
Создайте продвинутый холст для рисования с поддержкой мультитач-жестов: рисование несколькими пальцами одновременно (каждый палец рисует линию своего цвета), жест масштабирования двумя пальцами (pinch-to-zoom) для изменения толщины кисти, жест поворота двумя пальцами для вращения холста. Реализуйте индикацию активных точек касания и отображение текущей толщины кисти. Добавьте кнопку сброса масштаба и поворота.
Подсказки: Активируйте setAttribute(Qt::WA_AcceptsTouchEvents). В event() обрабатывайте QEvent::TouchBegin/Update/End. Используйте QMap<int, QVector<QPointF>> для хранения траекторий каждого пальца по id(). Для масштабирования вычисляйте расстояние между двумя точками. Для поворота используйте QLineF::angleTo(). Примените QTransform для трансформаций при рисовании.
💬 Присоединяйтесь к обсуждению!
Разобрались с событиями клавиатуры и мыши? Возникли вопросы о разнице между событиями и сигналами?
Столкнулись с проблемами при обработке мультитач-жестов или не понимаете, когда использовать event(), а когда специализированные обработчики?
Поделитесь своим опытом реализации собственных событий, задайте вопросы о производительности или помогите другим читателям освоить событийную модель Qt!