7.2. Установка фильтров событий.

Одна из замечательных особенностей модели обработки событий в Qt -- возможность одного экземпляра QObject отслеживать события, предназначенные для другого экземпляра QObject до того, как последний получит их.

Предположим, что у нас имеется виджет CustomerInfoDialog, собранный из нескольких QLineEdit, и нам необходимо передавать фокус ввода, от одного к другому, нажатием на клавишу "пробел". Решение "в лоб" -- создать дочерний класс от QLineEdit и перекрыть обработчик события keyPressEvent(), в котором вызывать focusNextPrevChild(), примерно так:

void MyLineEdit::keyPressEvent(QKeyEvent *event) { if (event->key() == Key_Space) focusNextPrevChild(true); else QLineEdit::keyPressEvent(event); } Однако это решение имеет массу недостатков. Поскольку MyLineEdit -- это нестандартный виджет, то нам придется приложить некоторые усилия, чтобы интегрировать его с Qt Designer, если захотим создавать формы с помощью визуального построителя. Кроме того, если потребуется, чтобы другие типы виджетов (такие как QComboBoxe и QSpinBox) так же поддерживали эту особенность, то мы будем вынуждены создать дочерние классы и для этих виджетов.

Более гибкое решение -- позволить CustomerInfoDialog отслеживать события, отправляемые подчиненным виджетам и реализовать необходимую функциональность. Делается это с помощью фильтров событий. Установка фильтра событий производится в два этапа:

  1. Регистрация фильтра событий, вызовом функции installEventFilter() того объекта, которому предназначены события.

  2. Создание обработчика перехваченных событий eventFilter().

Регистрацию фильтра событий мы поместим в конструктор класса CustomerInfoDialog: CustomerInfoDialog::CustomerInfoDialog(QWidget *parent, const char *name) : QDialog(parent, name) { ... firstNameEdit->installEventFilter(this); lastNameEdit->installEventFilter(this); cityEdit->installEventFilter(this); phoneNumberEdit->installEventFilter(this); } После регистрации фильтра, все события, которые предназначены объектам firstNameEdit, lastNameEdit, cityEdit и phoneNumberEdit, сначала попадут в обработчик CustomerInfoDialog::eventFilter().

Ниже приводится исходный код функции eventFilter():

bool CustomerInfoDialog::eventFilter(QObject *target, QEvent *event) { if (target == firstNameEdit || target == lastNameEdit || target == cityEdit || target == phoneNumberEdit) { if (event->type() == QEvent::KeyPress) { QKeyEvent *keyEvent = (QKeyEvent *)event; if (keyEvent->key() == Key_Space) { focusNextPrevChild(true); return true; } } } return QDialog::eventFilter(target, event); } Прежде всего мы убеждаемся, что событие отправлено одному из QLineEdit. Не забывайте, что базовый класс QDialog может контролировать и другие виджеты. (В Qt 3.2 это не относится к QDialog. Однако, другие классы, такие как QMainWindow, отслеживают события некоторых из подчиненных виджетов по различным причинам.)

Если событие пришло от клавиатуры, то выполняется приведение к типу QKeyEvent и проверяется -- какая клавиша нажата. Если нажата клавиша "пробел", то вызывается функция focusNextPrevChild(), которая передает фокус вводв следующему виджету и возвращается результат true, сообщая Qt о том, что событие обработано. Если вернуть false, то Qt передаст событие объекту назначения.

Если событие порождено не клавишей "пробел", то управление передается функции eventFilter() базового класса.

В Qt предусмотрены пять уровней, на которых событие может быть перехвачено и обработано:

  1. Обработка событий в функциях-обработчиках

    Перекрытие обработчиков событий, таких как: mousePressEvent(), keyPressEvent() и paintEvent(), безусловно самый распространенный способ. Мы уже видели множество примеров тому.

  2. Перекрытие метода QObject::event().

    Внутри этого обработчика мы можем перехватывать события до того, как они попадут в специализированные функции-обработчики. Этот подход чаще всего используется для того, чтобы изменить реакцию виджета на клавишу табуляции, как это было показано ранее. Он так же используется для обработки событий, которые встречаются не так часто, например: LayoutDirectionChange. Если мы перекрываем функцию event(), то необходимо предусмотреть вызов обработчика event() базового класса, чтобы обработать события, которые нас не интересуют.

  3. Установка фильтра событий для QObject.

    После того, как фильтр будет зарегистрирован функцией installEventFilter(), все события, предназначающиеся указанному объекту, сначала будут попадать в обработчик eventFilter(). Такой способ мы использовали для перехвата событий от клавиши "пробел" в примере выше.

  4. Установка фильтра событий объекта QApplication.

    После регистрации фильтра, любое событие, предназначенное для любого объекта в приложении, будет сначала попадать в обработчик eventFilter(). Такой подход чаще всего используется в целях отладки и реализации в приложении скрытых сюрпризов (так называемых "пасхальных яиц").

  5. Создание дочернего класса от QApplication и перекрытие метода notify().

    Qt вызывает QApplication::notify(), чтобы передать событие приложению. Таким способом можно перехватить любое событие до того, как оно попадет в фильтр событий. Вообще фильтры событий более удобны, поскольку допускается одновременное существование любого количества фильтров, а функция notify() может быть только одна.



Большинство типов событий, включая события от мыши и клавиатуры, могут передаваться дальше. Если событие не было обработано по пути к объекту наначения, или самим объектом, то процесс обработки события повторяется, но на этот раз объектом назначения становится виджет-владелец. Так продолжается до тех пор, пока событие не будет обработано, либо пока событие не достигнет виджет самого верхнего уровня.

Рисунок 7.2. Обработка событий в окне диалога.


На рисунке 7.2 показан порядок передачи события от подчиненного виджета к владельцу. Когда пользователь нажимает какую либо клавишу, событие сначала передается виджету, который владеет фокусом ввода, в данном случае это QCheckBox в правом нижнем углу. Если виджет не обрабатывает событие, то оно передается виджету QGroupBox и затем QDialog.