Система событий
В Qt события — это объекты, производные от абстрактного класса QEvent, представляющие собой произошедшие события как внутри приложения, так и в результате внешней активности, о которой приложение должно знать. События могут быть получены и обработаны любой экземпляром подкласса QObject, но они особенно важны для виджетов. В этом документе описано, как события доставляются и обрабатываются в типичном приложении.
Доставка событий
Когда происходит событие, Qt создаёт объект события, представляющий его, создавая экземпляр соответствующего подкласса QEvent, и доставляет его конкретному экземпляру QObject (или одного из его подклассов), вызвав его функцию event().
Эта функция не обрабатывает само событие; в зависимости от типа доставленного события она вызывает обработчик событий для этого конкретного типа события и отправляет ответ, основанный на том, было ли событие принято или проигнорировано.
Некоторые события, такие как QMouseEvent и QKeyEvent, поступают из системы окон; некоторые, такие как QTimerEvent, — из других источников; некоторые — из самого приложения.
Типы событий
У большинства типов событий есть специализированные классы, в частности QResizeEvent, QPaintEvent, QMouseEvent, QKeyEvent и QCloseEvent. Каждый класс является подклассом QEvent и добавляет функции, специфичные для события. Например, QResizeEvent добавляет size() и oldSize(), чтобы виджеты могли определить, как изменились их размеры.
Некоторые классы поддерживают более одного фактического типа событий. QMouseEvent поддерживает нажатия кнопок мыши, двойные щелчки, перемещения и другие связанные операции.
Каждое событие имеет связанный тип, определенный в QEvent::Type, и это может быть использовано в качестве удобного источника информации о типе во время выполнения, чтобы быстро определить, из какого подкласса был создан данный объект события.
Поскольку программы должны реагировать различными и сложными способами, механизмы доставки событий Qt гибкие. Документация по QCoreApplication::notify() лаконично рассказывает всю историю; статья Qt Quarterly Another Look at Events пересказывает её менее лаконично. Здесь мы объясним достаточно для 95% приложений.
Обработчики событий
Обычный способ доставки события — вызов виртуальной функции. Например, QPaintEvent доставляется путём вызова QWidget::paintEvent(). Эта виртуальная функция отвечает за соответствующую реакцию, обычно перерисовкой виджета. Если вы не выполнили всю необходимую работу в своём варианте виртуальной функции, вам может потребоваться вызвать реализацию базового класса.
Например, следующий код обрабатывает нажатия левой кнопки мыши на пользовательском виджете флажка, передавая все другие нажатия кнопок базовому классу QCheckBox:
void MyCheckBox::mousePressEvent(QMouseEvent *event)
{
if (event->button() == Qt::LeftButton) {
// handle left mouse button here
} else {
// pass on other buttons to base class
QCheckBox::mousePressEvent(event);
}
} Если вы хотите заменить функцию базового класса, вы должны реализовать всё самостоятельно. Однако, если вы хотите лишь расширить функциональность базового класса, вы реализуете то, что хотите, и вызываете базовый класс, чтобы получить стандартное поведение для любых случаев, которые вы не хотите обрабатывать.
Иногда нет такой функции, специфичной для события, или функции, специфичной для события, недостаточно. Наиболее распространённый пример связан с нажатиями клавиши Tab. Обычно QWidget перехватывает их, чтобы переместить фокус клавиатуры, но некоторые виджеты нуждаются в клавише Tab для себя.
Эти объекты могут переопределить QObject::event(), общий обработчик событий, и либо обработать событие до или после обычной обработки, либо полностью заменить функцию. Очень необычный виджет, который и интерпретирует Tab, и имеет прикладное пользовательское событие, может содержать следующую функцию event():
bool MyWidget::event(QEvent *event)
{
if (event->type() == QEvent::KeyPress) {
QKeyEvent *ke = static_cast<QKeyEvent *>(event);
if (ke->key() == Qt::Key_Tab) {
// special tab handling here
return true;
}
} else if (event->type() == MyCustomEventType) {
MyCustomEvent *myEvent = static_cast<MyCustomEvent *>(event);
// custom event handling here
return true;
}
return QWidget::event(event);
} Обратите внимание, что QWidget::event() всё ещё вызывается для всех необработанных случаев, и что возвращаемое значение указывает, было ли событие обработано; значение true предотвращает отправку события другим объектам.
Фильтры событий
Иногда объекту необходимо посмотреть и, возможно, перехватить события, которые доставляются другому объекту. Например, диалоговые окна обычно хотят фильтровать нажатия клавиш для некоторых виджетов; например, чтобы изменить обработку клавиши Return.
Функция QObject::installEventFilter() позволяет это сделать, настроив фильтр событий, вызывая назначенный объект-фильтр для получения событий для целевого объекта в его функции QObject::eventFilter(). Фильтр событий получает возможность обработать события до целевого объекта, позволяя ему проверять и отбрасывать события по мере необходимости. Существующий фильтр событий можно удалить с помощью функции QObject::removeEventFilter().
Когда вызывается реализация функции eventFilter() объекта-фильтра, он может принять или отклонить событие и разрешить или запретить дальнейшую обработку события. Если все фильтры событий разрешают дальнейшую обработку события (возвращая false), событие отправляется целевому объекту. Если один из них останавливает обработку (возвращая true), целевой объект и любые последующие фильтры событий вообще не видят события.
bool FilterObject::eventFilter(QObject *object, QEvent *event)
{
if (object == target && event->type() == QEvent::KeyPress) {
QKeyEvent *keyEvent = static_cast<QKeyEvent *>(event);
if (keyEvent->key() == Qt::Key_Tab) {
// Special tab handling
return true;
} else
return false;
}
return false;
} Приведенный выше код демонстрирует другой способ перехвата событий нажатия клавиши Tab, отправленных определённому целевому виджету. В этом случае фильтр обрабатывает соответствующие события и возвращает true для прекращения их дальнейшей обработки. Все остальные события игнорируются, и фильтр возвращает false для разрешения их отправки целевому виджету через любые другие фильтры событий, установленные на нём.
Также возможно фильтровать все события для всего приложения, установив фильтр событий на объект QApplication или QCoreApplication. Такие глобальные фильтры событий вызываются до объектно-специфичных фильтров. Это очень мощно, но это также замедляет доставку событий каждого события во всём приложении; другие обсуждаемые техники, как правило, должны использоваться вместо этого.
Отправка событий
Многие приложения хотят создавать и отправлять свои собственные события. Вы можете отправлять события точно так же, как и собственный цикл событий Qt, создавая подходящие объекты событий и отправляя их с помощью QCoreApplication::sendEvent() и QCoreApplication::postEvent().
sendEvent() обрабатывает событие немедленно. Когда он возвращается, фильтры событий и/или сам объект уже обработали событие. Для многих классов событий есть функция isAccepted(), которая показывает, было ли событие принято или отклонено последним обработчиком, который был вызван.
postEvent() помещает событие в очередь для последующей отправки. В следующий раз, когда выполняется основной цикл событий Qt, он отправляет все размещённые события с некоторыми оптимизациями. Например, если есть несколько событий изменения размера, они объединяются в одно. То же самое относится к событиям рисования: QWidget::update() вызывает postEvent(), что устраняет мерцание и увеличивает скорость, избегая нескольких перерисовок.
postEvent() также используется во время инициализации объекта, поскольку размещённое событие, как правило, отправляется очень скоро после завершения инициализации объекта. При реализации виджета важно понимать, что события могут быть доставлены очень рано в его жизни, поэтому в его конструкторе позаботьтесь об инициализации членов рано, прежде чем есть шанс, что он может получить событие.
Для создания событий пользовательского типа вам нужно определить номер события, который должен быть больше QEvent::User, и вам может потребоваться создать подкласс QEvent, чтобы передать определённую информацию о вашем пользовательском событии. Дополнительные сведения см. в документации по QEvent.
© The Qt Company Ltd
Licensed under the GNU Free Documentation License, Version 1.3.
https://doc.qt.io/qt-5.14/eventsandfilters.html