60 |
ГЛАВА 3 |
вцепочку обработки событий еще на уровне метода dispatchEvent()4 и, прежде чем продолжить обработку события обычным образом, «ворошит» все содержащиеся
внем легковесные компоненты, проверяя, не принадлежит ли пришедшее событие им. Если это так, то обработка события тяжеловесным контейнером прекращается, а событие передается в метод dispatchEvent() соответствующего легковесного компонента. Эти действия встроены в базовый класс контейнеров java.awt.Container,
и любой унаследованный от него контейнер может быть уверен, что содержащиеся в нем легковесные компоненты получат необходимую поддержку. Для программис- тов-клиентов Swing все эти хитрости остаются незаметными, и цепочка обработки событий кажется одинаковой как для тяжеловесного, так и для легковесного компонента.
Мы упомянули здесь методы processXXXEvent() не для «красного словца», они могут быть полезными и в обычных программах. Чаще всего эти методы используют там, где необходимо выполнить некоторое действие перед поступлением события к слушателю или вообще запретить распространение события. Для этого необходимо переопределить метод processXXXEvent(), соответствующий нужному нам событию. Рассмотрим простой пример, в котором мы попытаемся вмешаться в цепочку обработки событий от мыши:
//PreProcessMouse.java
//Перехват событий от мыши до их поступления к слушателям import javax.swing.*;
import java.awt.event.*;
public class PreProcessMouse extends JFrame { PreProcessMouse() {
super("PreProcessMouse");
//при закрытии окна - выход setDefaultCloseOperation(EXIT_ON_CLOSE);
//добавим слушателя событий от мыши addMouseListener(new MouseL());
//выводим окно на экран
setSize(200, 200); setVisible(true);
}
// перехват событий от мыши
public void processMouseEvent(MouseEvent e) { if ( e.getClickCount() == 1 ) {
// один щелчок не пропускаем к слушателям return;
}
4 Несмотря на то, что метод dispatchEvent() является неизменным (final), вмешаться в его работу контейнер все-таки может, потому что метод dispatchEvent() просто вызывает метод dispatchEventImpl(), в котором и выполняется вся работа. Метод dispatchEventImpl() обладает видимостью в пределах пакета, так что все классы из пакета java.awt могут привнести в него что-то новое.
За кулисами системы обработки событий |
61 |
// иначе вызываем метод базового класса else super.processMouseEvent(e);
}
// в этом слушателе будем следить за щелчками мыши class MouseL extends MouseAdapter {
@Override
public void mouseClicked(MouseEvent e) { System.out.println(
"ClickCount: " + e.getClickCount());
}
}
public static void main(String[] args) { SwingUtilities.invokeLater(
new Runnable() {
public void run() { new PreProcessMouse(); } });
}
}
В примере мы создаем небольшое окно JFrame, при его закрытии приложение будет заканчивать свою работу. К этому окну (которое является обыкновенным компонентом, унаследованным от базового класса Component), мы присоединяем слушателя событий от мыши MouseL, который унаследован от адаптера MouseAdapter и следит за щелчками мыши в окне (о них сообщается в метод слушателя mouseClicked()). Однако слушатель этот — не единственное заинтересованное в щелчках мыши «лицо»: мы переопределили метод processMouseEvent() нашего окна JFrame. Как нам теперь известно, именно в этом методе происходит рассылка событий от мыши слушателям, в нем мы также отслеживаем щелчки мыши еще до поступления события к слушателям. Посмотрите, что происходит: в методе processMouseEvent() мы выясняем, сколько раз пользователь щелкнул мышью (не важно, правая эта кнопка или левая), и если количество щелчков равно единице, заканчиваем работу метода, что равносильно игнорированию всех присоединенных слушателей (рассылка событий происходит в методе базового класса, и если мы его не вызываем, то слушатели ничего не узнают). В противном случае, если количество щелчков больше единицы, мы вызываем метод базового класса, в обязанности которого входит рассылка события слушателям.
Запустив программу с примером, вы (глядя на консольный вывод) сможете увидеть, какие щелчки мыши добираются до слушателя событий MouseL, а какие щелчки, несмотря на то, что вы их старательно производите в окне, так и не появляются в этом слушателе. В методы processXXXEvent() попадают всевозможные события одного типа, для мыши это щелчки, перетаскивание, нажатия, вход в область компонента и выход из нее,
имногое другое. Определяя, какие из этих событий вам нужны, вы можете встроить в свои графические интерфейсы поддержку самой тщательной фильтрации событий
итонко настроить их поведение. Слушатели находятся на самом верху пирамиды рассылки событий и не знают, что событие до них может не добраться: они просто терпеливо ожидают, когда оно произойдет. Вы, со своей стороны, всегда можете опуститься ниже по цепочке обработки событий, и чем ниже вы будете опускаться, тем более тонкие возможности будут оказываться в ваших руках.
Тем не менее, как бы заманчиво не выглядела перспектива фильтрации событий до их поступления к слушателям, возможности этого подхода весьма ограничены. Вспомните еще раз, как происходит рассылка событий: все основные действия происходят
62 |
ГЛАВА 3 |
в недоступных для нас механизмах класса Component и Container, и в итоге событие попадает в наши руки уже после того, как система определила, какому компоненту оно принадлежит. То есть фактически фильтровать события мы можем только для того компонента, которому они принадлежат, а смысла в этом немного: чаще всего фильтрация событий имеет смысл для контейнеров, содержащих другие компоненты. Фильтруя или особым образом обрабатывая события для контейнера, к примеру, отключая все щелчки правой кнопки мыши, мы, как правило, хотим, чтобы подобная фильтрация работала и для всех содержащихся в контейнере компонентов. Но реализовать такое поведение, переопределяя методы processXXXEvent(), нельзя: события обрабатываются для каждого компонента отдельно. Создатели Swing учли ситуацию и добавили в контейнеры высшего уровня особый компонент, прозрачную панель; она позволяет фильтровать и предварительно обрабатывать события сразу для всего пользовательского интерфейса программы. Прозрачную панель мы рассмотрим в главе 6, там будет упомянут и похожий по смыслу компонент JXLayer.
Напоследок стоит упомянуть о том, что события от клавиатуры (KeyEvent) заслуживают особого внимания. По многим причинам они не вписываются в общие схемы: среди основных причин — необходимость поддержки механизмов передачи фокуса ввода и клавиатурных сокращений. Фокус ввода определяет, какие компоненты получают события от клавиатуры первыми, а также то, в какой последовательности происходит обработка этих событий. На самом деле, операционная система не видит легковесных компонентов, так что с ее точки зрения все события от клавиатуры происходят только в контейнере высшего уровне, в котором однако может находиться множество легковесных компонентов. Система передачи фокуса ввода должна четко определять, какой компонент получит событие от клавиатуры. Эта система встроена в работу метода dispatchEvent() базового класса Component и благодаря этому всегда имеет шанс перехватить любое необходимое для передачи фокуса ввода событие от клавиатуры. Клавиатурные сокращения — один из основополагающих инструментов Swing, поддерживаются благодаря особой реализации метода processKeyEvent() базового класса библиотеки JComponent. Поэтому переопределение метода processKeyEvent() для специальной обработки или фильтрации событий от клавиатуры нужно производить с осторожностью
ивсегда вызывать метод базового класса super.processKeyEvent(), а также осознавать, что некоторые нажатия клавиш до этого метода могут так и не добраться. Мы будем подробно обсуждать систему передачи фокуса ввода и клавиатурные сокращения в главе 5,
итам же узнаем, какие дополнительные способы специальной обработки событий от клавиатуры у нас есть.
Маскирование и поглощение событий
При окончательной «шлифовке» системы событий ее создатели заметили, что методы компонентов processEvent() и processXXXEvent(), служащие для рассылки событий, могут становится «узким местом» графических программ: в них вполне способна «затеряться» некоторая часть процессорного времени. Исследование показывает, что в компоненты приходят уведомления абсолютно обо всех происходящих с ними низкоуровневых событиях, будь то события от мыши, клавиатуры, изменения в размерах контейнера или что-либо еще, причем приходят такие уведомления всегда, независимо от того, заинтересован компонент в этих событиях или нет, и в легковесные, и в тяжеловесные компоненты. В итоге получалось, что и для сложных компонентов, таких как таблицы, по-настоящему нуждающихся в информации о большей части событий, и для простых вспомогательных компонентов, подобных панелям, которые по большому счету нуждаются только в своевременной перерисовке, система присылала полную информацию обо всех происходящих событиях. И это не приводило к блестящей производительности.
За кулисами системы обработки событий |
63 |
Решением стало маскирование событий (event masking). С каждым компонентом графической системы, способным получать уведомления о событиях, теперь ассоциирована специальная маска в виде длинного (long) целого, которая и определяет, какие события компонент получает. По умолчанию отключено получение всех событий, кроме уведомлений о необходимости перерисовки. Но, как только вы (или другой компонент) присоединяете к компоненту слушателя события определенного типа, например, слушателя событий от мыши MouseListener, компонент тут же включает в свою маску признак обработки данного события (от мыши), так что система обработки событий начинает присылать ему все связанные с этой маской события. Принцип прост: нет слушателей — нет и событий, как только слушатели появляются — автоматически появляются события.
Маскирование скорее относится к внутренним механизмам системы обработки событий, и чаще всего вам достаточно того, что при присоединении слушателей маска компонента автоматически обновляется. Однако вы можете и вручную управлять маскированием, быстро включая (или отключая) доставку интересующих вас событий. Это менее гибкий способ управления событиями, чем, к примеру, методы processXXXEvent(), но иногда он позволяет добиться желаемого гораздо быстрее. Одним из самых распространенных вариантов «ручного» маскирования можно назвать как раз переопределение методов processXXXEvent(): если вы не присоедините к компоненту слушателей, а попытаетесь сразу же получить информацию о событиях из методов processXXXEvent(), у вас ничего не выйдет. Вспомните, по умолчанию события в маску не включаются. Это распространенная ошибка, а решение ее просто: надо просто включить в маску нужное вам событие, после чего метод processXXXEvent() заработает «как по маслу».
Добавить событие в маску компонента или удалить его оттуда позволяет пара методов enableEvents() и disableEvents(). Правда, вызвать их напрямую не так то просто: методы эти описаны как защищенные (protected), то есть они доступны только подклассам компонента. В качестве параметров им требуется передать набор объединенных логическим «ИЛИ» констант из класса AWTEvent, которые и определят, какие низкоуровневые события вы включаете или отключаете. Рассмотрим небольшой пример:
//MaskingEvents.java
//Маскирование событий import java.awt.*; import javax.swing.*;
public class MaskingEvents extends JFrame { public MaskingEvents() {
super("MaskingEvents");
//при закрытии окна - выход setDefaultCloseOperation(EXIT_ON_CLOSE);
//отключим события от окна disableEvents(AWTEvent.WINDOW_EVENT_MASK);
//добавим особую кнопку
JPanel contents = new JPanel(); contents.add(new CustomButton("Привет!")); setContentPane(contents);
// выведем окно на экран setSize(400, 300); setVisible(true);
64 |
ГЛАВА 3 |
|
} |
|
// особая кнопка |
|
class CustomButton extends JButton { |
|
public CustomButton(String label) { |
|
super(label); |
|
// отключаем события с клавиатуры и от мыши |
|
disableEvents(AWTEvent.KEY_EVENT_MASK); |
|
disableEvents(AWTEvent.MOUSE_EVENT_MASK); |
|
} |
|
} |
|
public static void main(String[] args) { |
SwingUtilities.invokeLater( new Runnable() {
public void run() { new MaskingEvents(); } });
}
}
Здесь мы создаем небольшое окно с рамкой JFrame и сразу же указываем ему, что при закрытии окна нужно будет завершить работу приложения. В нашем примере все меняется: мы пытаемся властно приказать окну (методом disableEvents(), он нам доступен, потому что класс в примере унаследован от окна JFrame, а оно, в свою очередь, от базового класса Component) исключить из маски события от окна. Однако после запуска примера вы убедитесь, что окно не обратило внимания на нашу маску и закрывается так же, как и обычное окно. В первом издании книги пример работал, и окно не закрывалось, однако это было сочтено нарушением безопасности пользователей, особенно при работе из апплетов, и маскирование оконных событий теперь отключено. Это первая особенность системы маскирования событий.
Пример также демонстрирует, как управлять маскированием событий от клавиатуры; мы отключаем их для особой кнопки, унаследованной от обычной кнопки JButton библиотеки Swing. Запустив программу с примером, вы увидите, что созданную кнопку легко нажать мышью, но невозможно активизировать с клавиатуры (с помощью клавиши Enter или пробела в зависимости от используемых внешнего вида и поведения). Все работает как обычно, слушатели и UI-представители готовы к обработке событий, но они просто не доходят до них: маскирование выполняется на самых нижних уровнях системы обработки событий, и если событие не включено в маску компонента, обработать его оказывается невозможно.
Однако тут же маскирование демонстрирует еще одну особенность, постойте-ка, что мы только что сказали? «Кнопку легко нажать мышью» - но глянув на код примера, совершенно очевидно, что мы отключили события от мыши. Это вторая особенность маскирования событий — событие маскируется только в том случае, если оно не включено в маску событий и если для событий данного типа нет слушателей. Однако наша кнопка самаприсоединила слушателя, потому что желает знать, когда на нее нажмут, и придуманная нами маска снова оказывается не у дел.
ВНИМАНИЕ
Маскирование событий не так уж и очевидно, как кажется, — оно не работает, если событие ожидает хотя бы один слушатель, неважно, кем и когда он добавлен, и не работает для оконных событий.