Система событий
В 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 Ещё раз о событиях пересказывает её менее лаконично. Здесь мы объясним достаточно для 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 предотвращает отправку события другим объектам.
Фильтры событий
Иногда объекту нужно посмотреть на события, доставляемые другому объекту, и, возможно, перехватить их. Например, диалоги обычно хотят отфильтровать нажатия клавиш для некоторых виджетов, например, для изменения обработки клавиши Возврат.
Функция 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.9/eventsandfilters.html