В предыдущих главах мы написали много программ, создающих компоненты интерфейса пользователя, но, собственно говоря, интерфейса, т. е. взаимодействия с пользователем, эти программы не обеспечивают. Можно щелкать по кнопке на экране, она будет "вдавливаться" в плоскость экрана, но больше ничего не будет происходить. Можно ввести текст в поле ввода, но он не станет восприниматься и обрабатываться программой. Все это происходит из-за того, что мы не задали обработку действий пользователя, т. е. обработку событий.
Событие (event) в библиотеке AWT возникает при воздействии на компонент какими-нибудь манипуляциями мышью, при вводе с клавиатуры, при перемещении окна, изменении его размеров.
Объект, в котором произошло событие, называется источником (source) события.
Р’СЃРµ события РІ AWT классифицированы. РџСЂРё возникновении события исполняющая система Java автоматически создает объект соответствующего событию класса. Ртот объект РЅРµ РїСЂРѕРёР·РІРѕРґРёС‚ никаких действий, РѕРЅ только хранит РІСЃРµ сведения Рѕ событии.
Во главе иерархии классов-событий стоит класс Eventobject из пакета java.utii — непосредственное расширение класса object. Его расширяет абстрактный класс AWTEvent из пакета java.awt — глава классов, описывающих события библиотеки AWT. Дальнейшая иерархия классов-событий AWT показана на рис. 15.1. Все классы, отображенные на рисунке, кроме класса AWTEvent, собраны в пакет java.awt. event.
□ События типа ComponentEvent, FocusEvent, KeyEvent, MouseEvent возникают во всех компонентах.
□ События типа ContainerEvent- в контейнерах класса Container, а значит, и во всех
компонентах графической библиотеки Swing.
□ События типа WindowEvent возникают в окнах класса Window и в его наследниках.
□ События типа TextEvent генерируются только в компонентах TextComponent, TextArea, TextField.
□ События типа ActionEvent проявляются в компонентах Button, List, TextField, JComboBox, JTextField, кнопках класса AbstractButton и его наследниках.
□ События типа ItemEvent возникают в компонентах Checkbox, JCheckbox, Choice, JComboBox, List и в кнопках класса AbstractButton и его наследниках.
□ Наконец, события типа AdjustmentEvent возникают только в полосах прокрутки
Scrollbar Рё JScrollBar.
Узнать, РІ каком объекте произошло событие, можно методом getSource() класса EventObj ect. Ртот метод возвращает ссылку РЅР° объект типа Obj ect.
Р’ каждом РёР· этих классов-событий определен метод paramString(), возвращающий содержимое объекта данного класса РІ РІРёРґРµ строки String. РљСЂРѕРјРµ того, РІ каждом классе есть СЃРІРѕРё методы, предоставляющие те или иные сведения Рѕ событии. Р’ частности, метод getID() возвращает идентификатор (identifier) события — целое число, обозначающее тип события. Рдентификаторы события определены РІ каждом классе-событии как константы.
Графическая библиотека Swing добавляет еще несколько классов-событий, собранных в пакет javax.swing.event. Большинство этих классов наследуют напрямую от класса
EventObj ect.
Событие нельзя обработать произвольно написанным методом. РЈ каждого события есть СЃРІРѕРё методы, Рє которым обращается исполняемая система Java РїСЂРё его возникновении. РћРЅРё описаны РІ интерфейсах-слушателях (listener). Для каждого показанного РЅР° СЂРёСЃ. 15.1 типа событий, РєСЂРѕРјРµ InputEvent (РѕРЅРѕ редко используется самостоятельно), есть СЃРІРѕР№ интерфейс. Рмена интерфейсов составляются РёР· имени события Рё слова "Listener", например: ActionListener, MouseListener. Методы интерфейса "слушают", что РїСЂРѕРёСЃС…РѕРґРёС‚ РІ потенциальном источнике события. РџСЂРё возникновении события эти методы автоматически выполняются, получая РІ качестве аргумента объект-событие Рё используя РїСЂРё обработке сведения Рѕ событии, содержащиеся РІ этом объекте.
AWTEvent
—ActionEvent —AdjustmentEvent
- ContainerEvent
- FocusEvent
- InputEvent-
- PaintEvent — WindowEvent
KeyEvent
MouseEvent
— ComponentEvent -
— ItemEvent —TextEvent
Р РёСЃ. 15.1. Рерархия классов, описывающих события AWT
Чтобы задать обработку события определенного типа, надо реализовать соответствующий интерфейс. Классы, реализующие такой интерфейс, классы-обработчики (handlers) события называются слушателями (listeners): они "слушают", что происходит в объекте, чтобы отследить возникновение события и обработать его.
Чтобы связаться с обработчиком события, классы-источники события должны получить ссылку на экземпляр eventHandler класса-обработчика события одним из методов
addXxxListener(XxxEvent eventHandler), где Xxx — имя события.
Такой СЃРїРѕСЃРѕР± регистрации, РїСЂРё котором слушатель оставляет "визитную карточку" источнику для своего вызова РїСЂРё наступлении события, называется обратным вызовом (callback). РРј часто пользуются студенты, которые, Р·РІРѕРЅСЏ родителям Рё РЅРµ желая платить Р·Р° телефонный разговор, РіРѕРІРѕСЂСЏС‚: "Перезвони РјРЅРµ РїРѕ такому-то номеру".
Обратное действие — отказ от обработчика, прекращение прослушивания — выполняется методом removeXxxListener ().
Таким образом, компонент-источник, в котором произошло событие, не занимается его обработкой. Он обращается к экземпляру класса-слушателя, умеющего обрабатывать события, делегирует (delegate) ему полномочия по обработке.
Такая схема получила название схемы делегирования (delegation). Она удобна тем, что мы можем легко сменить класс-обработчик и обработать событие по-другому или назначить несколько обработчиков одного и того же события. С другой стороны, мы можем один обработчик назначить на прослушивание нескольких объектов-источников событий.
Рта схема кажется слишком сложной, РЅРѕ РјС‹ ею часто пользуемся РІ жизни. Допустим, РјС‹ решили оборудовать квартиру. РњС‹ помещаем РІ нее, как РІ контейнер, разные компоненты: мебель, сантехнику, электронику, антиквариат. РњС‹ предполагаем, что может произойти неприятное событие — квартиру посетят РІРѕСЂС‹, — Рё хотим его обработать. РњС‹ знаем, что существуют классы-обработчики этого события — охранные агентства, — Рё обращаемся Рє некоторому экземпляру такого класса. Компоненты-источники события, С‚. Рµ. те, которые РјРѕРіСѓС‚ быть украдены, присоединяют Рє себе датчики методом РІРёРґР° addXxxListener (). Затем экземпляр-обработчик "слушает", что РїСЂРѕРёСЃС…РѕРґРёС‚ РІ объектах, Рє которым РѕРЅ подключен. РћРЅ реагирует РЅР° наступление только РѕРґРЅРѕРіРѕ события — похищения прослушиваемого объекта, — прочие события, например короткое замыкание или прорыв РІРѕРґРѕРїСЂРѕРІРѕРґРЅРѕР№ трубы, его РЅРµ интересуют. РџСЂРё наступлении "своего" события РѕРЅ действует РїРѕ контракту, записанному РІ методе обработки события.
Приведем пример. Пусть в контейнер типа JFrame помещено поле ввода tf типа JTextField, нередактируемая область ввода ta типа JTextArea и кнопка b типа JButton. В поле tf вводится строка, после нажатия клавиши
Здесь и при нажатии клавиши
Ртак, пишем:
class TextMove implements ActionListener{ private JTextField tf; private JTextArea ta;
TextMove(JTextField tf, JTextArea ta){ this.tf = tf; this.ta = ta;
}
public void actionPerformed(ActionEvent ae){ ta.append(tf.getText()+"\n");
}
}
Обработчик событий готов. При наступлении события типа ActionEvent будет создан экземпляр класса-обработчика TextMove, конструктор получит ссылки на конкретные поля объекта-источника, метод actionPerformed (), автоматически включившись в работу, перенесет текст из одного поля в другое.
Теперь напишем класс-контейнер, в котором находятся источники tf и b события ActionEvent, и подключим к ним слушателя этого события TextMove, передав им ссылки на него методом addActionListener ( ), как показано в листинге 15.1.
Листинг 15.1. Обработка события ActionEvent
import java.awt.*; import java.awt.event.*; import javax.swing.*;
class MyNotebook extends JFrame{
MyNotebook(String title){ super(title);
JTextField tf = new JTextField("Вводите текст", 50); add(tf, BorderLayout.NORTH);
JTextArea ta = new JTextArea(); ta.setEditable(false); add(ta);
JPanel p = new JPanel(); add(p, BorderLayout.SOUTH);
JButton b = new JButton("Перенести"); p.add(b);
tf.addActionListener(new TextMove(tf, ta)); b.addActionListener(new TextMove(tf, ta));
setSize(300, 200); setVisible(true);
}
public static void main(String[] args){
JFrame f = new MyNotebook(" Обработка ActionEvent"); f.setDefaultCloseOperation(EXIT ON CLOSE);
}
}
// Текст класса TextMove // ...
На рис. 15.2 показан результат работы с этой программой.
В листинге 15.1 в методах addActionListener() создаются два экземпляра класса TextMove — для прослушивания поля tf и для прослушивания кнопки b. Можно создать один экземпляр класса TextMove, он будет прослушивать оба компонента:
TextMove tml = new TextMove(tf, ta); tf.addActionListener(tml); b.addActionListener(tml);
Но в первом случае экземпляры создаются после наступления события в соответствующем компоненте, а во втором — независимо от того, наступило событие или нет, что приводит к расходу памяти, даже если событие не произошло. Решайте сами, что лучше.
Рис. 15.2. Обработка события ActionEvent |
Самообработка событий
Класс, содержащий источники события, может сам обрабатывать его. Возвращаясь к примеру с установкой охранной сигнализации, можно сказать, что вы способны самостоятельно прослушивать компоненты в своей квартире, установив пульт сигнализации у кровати. Для этого достаточно реализовать соответствующий интерфейс прямо в классе-контейнере, как показано в листинге 15.2.
Листинг 15.2. Самообработка события ActionEvent
import java.awt.*; import java.awt.event.*; import javax.swing.*;
class MyNotebook extends JFrame implements ActionListener{ private JTextField tf; private JTextArea ta;
MyNotebook(String title){ super(title);
tf = new JTextField("Вводите текст", 50); add(tf, BorderLayout.NORTH);
ta = new JTextArea(); ta.setEditable(false); add(ta);
JPanel p = new JPanel(); add(p, BorderLayout.SOUTH);
JButton b = new JButton("Перенести"); p.add(b);
tf.addActionListener(this); b.addActionListener(this);
setSize(300, 200); setVisible(true);
}
public void actionPerformed(ActionEvent ae){ ta.append(tf.getText()+"\n");
}
public static void main(String[] args){
JFrame f = new MyNotebook(" Обработка ActionEvent"); f.setDefaultCloseOperation(EXIT ON CLOSE);
}
}
Здесь поля tf Рё ta уже РЅРµ локальные переменные, Р° переменные экземпляра, поскольку РѕРЅРё используются Рё РІ конструкторе, Рё РІ методе actionPerformed (). Ртот метод теперь — РѕРґРёРЅ РёР· методов класса MyNotebook. Класс MyNotebook стал классом-обработчиком события ActionEvent — РѕРЅ реализует интерфейс ActionListener. Р’ методе addActionListener() указывается аргумент this — класс сам слушает СЃРІРѕРё компоненты.
Рассмотренная схема, кажется, проще и удобнее, но она предоставляет меньше возможностей. Если вы захотите изменить обработку, например заносить записи в поле ta по алфавиту или по времени выполнения заданий, то придется переписать и перекомпилировать класс MyNotebook.
Обработка вложенным классом
Еще РѕРґРёРЅ вариант — сделать обработчик вложенным классом. Рто позволяет обойтись без переменных экземпляра Рё конструктора РІ классе-обработчике TextMove, как показано РІ листинге 15.3.
Листинг 15.3. Обработка вложенным классом
import java.awt.*; import java.awt.event.*; import javax.swing.*;
class MyNotebook extends JFrame{ private JTextField tf; private JTextArea ta;
MyNotebook(String title){ super(title);
tf = new JTextField("Вводите текст", 50);
add(tf, BorderLayout.NORTH); ta = new JTextArea(); ta.setEditable(false); add(ta);
JPanel p = new JPanel(); add(p, BorderLayout.SOUTH);
JButton b = new JButton("Перенести"); p.add(b);
tf.addActionListener(new TextMove()); b.addActionListener(new TextMove());
setSize(300, 200); setVisible(true);
}
public static void main(String[] args){
JFrame f = new MyNotebook(" Обработка ActionEvent"); f.setDefaultCloseOperation(EXIT ON CLOSE);
}
// Вложенный класс
class TextMove implements ActionListener{
public void actionPerformed(ActionEvent ae){ ta.append(tf.getText()+"\n");
}
}
}
Наконец, можно создать безымянный вложенный класс, что мы и делали в этой и предыдущих главах, обрабатывая нажатие комбинации клавиш
Перейдем к детальному рассмотрению разных типов событий.
Упражнение
1. Реализуйте обработку события безымянным вложенным классом.
Событие ActionEvent
Рто простое событие означает, что надо выполнить какое-то действие. РџСЂРё этом неважно, что вызвало событие: щелчок мыши, нажатие клавиши или что-то РґСЂСѓРіРѕРµ.
В классе ActionEvent есть два полезных метода:
□ getActionCommand ( ) возвращает в виде строки String надпись на кнопке JButton,
точнее, то, что установлено методом setActionCommand(String s) класса JButton, выбранный пункт меню или списка JList, или что-то другое, зависящее от компонента;
□ getModifiers () — возвращает код клавиш
Примечание
Клавиши на PC-клавиатуре нет, ее действие часто назначается на клавишу
Вот как, например, можно отследить нажатие клавиши
public void actionPerformed(ActionEvent ae){ if (ae.getActionCommand() == "Open" &&
(ae.getModifiers() | ActionEvent.ALT MASK) != 0){
// Какие-то действия
}
}
Обработка действий мыши
Событие MouseEvent возникает в компоненте по любой из семи причин:
□ нажатие кнопки мыши — идентификатор mouse_pressed;
□ отпускание кнопки мыши — идентификатор MOUSE_RELEASED;
□ щелчок кнопкой мыши — идентификатор mouse_clicked (нажатие и отпускание не различаются);
□ перемещение мыши — идентификатор mouse_moved;
□ перемещение мыши с нажатой кнопкой — идентификатор mouse_dragged;
□ появление курсора мыши в компоненте — идентификатор mouse_entered;
□ выход курсора мыши из компонента — идентификатор mouse_exited.
Для их обработки есть семь методов в двух интерфейсах:
public interface MouseListener extends EventListener{ public void mouseClicked(MouseEvent e); public void mousePressed(MouseEvent e); public void mouseReleased(MouseEvent e); public void mouseEntered(MouseEvent e); public void mouseExited(MouseEvent e);
}
public interface MouseMotionListener extends EventListener{ public void mouseDragged(MouseEvent e); public void mouseMoved(MouseEvent e);
}
Рти методы РјРѕРіСѓС‚ получить РѕС‚ параметра e координаты РєСѓСЂСЃРѕСЂР° мыши РІ системе координат компонента методами e. getX (), e. getY ( ) или РѕРґРЅРёРј методом e . getPoint ( ), возвращающим экземпляр класса Point. Координаты РєСѓСЂСЃРѕСЂР° мыши относительно всего экрана, определяемые объектом класса GraphicsConfiguration, можно получить методами
e.getXOnScreen(), e.getYOnScreen() или e.getLocationOnScreen().
Двойной щелчок кнопкой мыши можно отследить методом e.getClickCount(), возвращающим количество щелчков. При перемещении мыши возвращается 0.
Узнать, какая кнопка была нажата, можно с помощью метода e.getModifiers() класса InputEvent сравнением со следующими статическими константами класса InputEvent:
□ button1_mask — нажата первая кнопка, обычно левая;
□ button2_mask — нажата вторая кнопка, обычно средняя, или одновременно нажаты обе кнопки на двухкнопочной мыши;
□ button3_mask — нажата третья кнопка, обычно правая.
Приведем пример, уже ставший классическим. В листинге 15.4 представлен простейший вариант "рисовалки" — класс Scribble. При нажатии первой кнопки мыши методом mousePressed () запоминаются координаты курсора мыши. При протаскивании мыши вычерчиваются отрезки прямых между текущим и предыдущим положением курсора мыши методом mouseDragged(). На рис. 15.3 показан пример работы с этой программой.
Листинг 15.4. Простейшая программа рисования
import java.awt.*; import java.awt.event.*; import javax.swing.*;
public class ScribbleTest extends JFrame{
public ScribbleTest(String s){ super(s);
Scribble scr = new Scribble(this, 500, 500); JScrollPane pane = new JScrollPane(scr); pane.setSize(300, 300); add(pane, BorderLayout.CENTER);
JPanel p = new JPanel(); add(p, BorderLayout.SOUTH);
p.add(b1); p.add(b2); p.add(b3); p.add(b4); p.add(b5);
JButton b1 = new JButton(,,Красный,,); b1.addActionListener(scr);
JButton b2 = new JButton(,,Зеленый,,); b2.addActionListener(scr);
JButton b3 = new JButtonC'^в„ў^'); b3.addActionListener(scr);
JButton b4 = new JButton("Черный"); b4.addActionListener(scr);
JButton b5 = new JButton("Очистить") b5.addActionListener(scr);
setDefaultCloseOperation(EXIT ON CLOSE); pack();
setVisible(true);
}
public static void main(String[] args){ new ScribbleTest(" \"Рисовалка\"");
}}
class Scribble extends JPanel implements
ActionListener, MouseListener, MouseMotionListener{ protected int lastX, lastY, w, h; protected Color currColor = Color.black; protected JFrame f;
public Scribble(JFrame frame, int width, int height){ f = frame; w = width; h = height; enableEvents (AWTEvent.MOUSE_EVENT_MASK |
AWTEvent.MOUSE_MOTION_EVENT_MASK) ; addMouseListener(this); addMouseMotionListener(this);
}
public Dimension getPreferredSize(){ return new Dimension(w, h);
}
public void actionPerformed(ActionEvent event){
String s = event.getActionCommand();
if (s.equals(,,Очистить,,)) repaint();
else if (s.equals("Красный")) currColor = Color.red;
else if (s.equals("Зеленый")) currColor = Color.green;
else if (s.equals("РЎРёРЅРёР№")) currColor = Color.blue;
else if (s.equals("Черный")) currColor = Color.black;
}
public void mousePressed(MouseEvent e){
if ((e.getModifiers() & MouseEvent.BUTTON1 MASK) == 0) return; lastX = e.getX(); lastY = e.getY();
}
public void mouseDragged(MouseEvent e){
if ((e.getModifiers() & MouseEvent.BUTTON1 MASK) == 0) return;
Graphics g = getGraphics(); g.setColor(currColor);
g.drawLine(lastX, lastY, e.getX(), e.getY());
lastX = e.getX(); lastY = e.getY();
}
public void mouseReleased(MouseEvent e){} public void mouseClicked(MouseEvent e){} public void mouseEntered(MouseEvent e){} public void mouseExited(MouseEvent e){} public void mouseMoved(MouseEvent e){}
}
При создании класса-с^шателя Scribble и реализации интерфейсов MouseListener и MouseMotionListener пришлось реализовать все их семь методов, хотя мы отслежива-
ли только нажатие кнопки и перемещение мыши, и нам нужны были лишь методы mousePressed ( ) и mouseDragged( ). Для остальных методов мы задали пустые реализации.
Чтобы облегчить задачу реализации интерфейсов, имеющих более одного метода, созданы классы-адаптеры.
Рис. 15.3. Пример работы с программой рисования |
Упражнение
2. Реализуйте оставшиеся методы интерфейсов-слушателей событий мыши.
Классы-адаптеры
Классы-адаптеры представляют собой пустую реализацию интерфейсов-слушателей,
имеющих более РѕРґРЅРѕРіРѕ метода. РС… имена составляются РёР· имени события Рё слова
"Adapter". Например, для действий с мышью есть два класса-адаптера. Выглядят они
очень просто:
public abstract class MouseAdapter implements MouseListener{ public void mouseClicked(MouseEvent e){} public void mousePressed(MouseEvent e){} public void mouseReleased(MouseEvent e){} public void mouseEntered(MouseEvent e){} public void mouseExited(MouseEvent e){} public void mouseMoved(MouseEvent e){} public void mouseDragged(MouseEvent e){} public void mouseWheelMoved(MouseEvent e){}
}
public abstract class MouseMotionAdapter implements MouseMotionListener{ public void mouseDragged(MouseEvent e){} public void mouseMoved(MouseEvent e){}
}
Вместо того чтобы реализовать интерфейс, можно расширять эти классы. Не бог весть что, но полезно для создания безымянного вложенного класса, как у нас и делалось для закрытия окна. Там мы использовали класс-адаптер WindowAdapter.
Классов-адаптеров всего семь. Кроме уже упомянутых трех классов, это классы
ComponentAdapter, ContainerAdapter, FocusAdapter Рё KeyAdapter.
Управление колесиком мыши
Колесико, дополняющее среднюю кнопку мыши, очень удобно и популярно. Его использование встроено в Java SE, начиная с версии JDK 1.4, и применяется, например, в контейнере JScrollPane. Если же разработчику нужно создать свой компонент, в котором прокрутка осуществляется колесиком мыши, то он может задать обработку события MouseWheelEvent.
Класс MouseWheelEvent расширяет класс MouseEvent, следовательно, содержит все его поля и методы. Дополнительно он различает два типа прокрутки: прокрутку блока, отмечаемую статическим полем wheel_block_scroll, и прокрутку одной единицы — статическое поле wheel_unit_scroll. Тип прокрутки, а также смысл понятий "блок" и "единица" определяется графической оболочкой операционной системы и возвращается методом getScrollType (). Например, блок — это страница текста, а единица — одна строка.
Если тип прокрутки WHEEL_UNIT_SCROLL, то метод getUnitsToScroll() возвращает количество прокрученных единиц с учетом направления вращения колесика: на себя — положительное число, от себя — отрицательное. Метод getScrollAmount () возвращает то же число без знака. Конкретное значение зависит от платформы.
Если же разработчик не хочет использовать понятие "единица", то он может методом getWheelRotation () отследить количество "щелчков", сделанных колесиком во время прокрутки. При этом положительное число возвращается при повороте колесика на себя и означает прокрутку вниз, а отрицательное — при повороте колесика от себя, прокрутка вверх.
Рнтерфейс MouseWheelListener описывает РѕРґРёРЅ метод
public void mouseWheelMoved(MouseWheelEvent e);
которому передается объект-событие, происшедшее в результате вращения колесика мыши. В листинге 15.5 приведен тренировочный пример работы с колесиком мыши.
Листинг 15.5. Событие колесика мыши
import java.awt.*; import java.awt.event.*; import javax.swing.*;
public class WheelEv extends JFrame
implements MouseWheelListener{
JTextArea ta = new JTextArea(5, 30);
WheelEv(){
super(" Колесико мыши"); getContentPane().add(ta);
ta.addMouseWheelListener(this); setSize(400, 400);
setDefaultCloseOperation(EXIT ON CLOSE); setVisible(true);
}
public void mouseWheelMoved(MouseWheelEvent e){
if (e.getScrollType() == MouseWheelEvent.WHEEL UNIT SCROLL) ta.append(" Единицы = " + e.getUnitsToScroll() +
" Количество = "+ e.getScrollAmount() +
" Вращение = " + e.getWheelRotation() + "\n");
}
public static void main(String[] args){ new WheelEv();
}
}
Рисунок 15.4 показывает конкретные значения, полученные этой программой в операционной системе Windows XP.
Рис. 15.4. Значения, полученные от колесика мыши |
Обработка действий клавиатуры
Событие KeyEvent происходит в компоненте по любой из трех причин:
□ нажата клавиша — идентификатор key_pressed;
□ отпущена клавиша — идентификатор key_released;
□ введен символ — идентификатор key_typed.
Последнее событие возникает РёР·-Р·Р° того, что некоторые символы вводятся нажатием нескольких клавиш, например заглавные Р±СѓРєРІС‹ вводятся комбинацией клавиш ^Ый>+<Р±СѓРєРІР°>. Вспомните еще <РђР>-РІРІРѕРґ РІ MS Windows. Нажатие функциональных клавиш, например
Обрабатываются эти события тремя методами, описанными в интерфейсе:
public interface KeyListener extends EventListener{ public void keyTyped(KeyEvent e);
public void keyPressed(KeyEvent e); public void keyReleased(KeyEvent e);
}
Аргумент e этих методов может дать следующие сведения.
Метод e.getKeyChar() возвращает символ Unicode типа char, связанный с клавишей. Если с клавишей не связан никакой символ, то возвращается константа char_undefined.
Метод e.getKeyCode () возвращает код клавиши в виде целого числа типа int. В классе KeyEvent определены коды всех клавиш в виде констант, называемых виртуальными кодами клавиш (virtual key codes), например: vk_f1, vk_shift, vk_a, vk_b, vk_plus. Они перечислены в документации к классу KeyEvent. Фактическое значение виртуального кода зависит от языка и раскладки клавиатуры. Чтобы узнать, какая клавиша была нажата, надо сравнить результат выполнения метода getKeyCode () с этими константами. Если кода клавиши нет, как происходит при наступлении события key_typed, то возвращается значение vk_undefined.
Чтобы узнать, не нажата ли одна или несколько клавиш-модификаторов
SHIFT_MASK. Другой способ применить логические методы isAltDown(), isControlDown ( ),
isMetaDown(), isShiftDown().
Добавим в листинг 15.3 возможность очистки поля ввода tf после нажатия клавиши
class TextMove implements ActionListener, KeyListener{ public void actionPerformed(ActionEvent ae){ ta.append(tf.getText()+"\n");
}
public void keyPressed(KeyEvent ke){
if (ke.getKeyCode() == KeyEvent.VK ESCAPE) tf.setText("");
}
public void keyReleased(KeyEvent ke){} public void keyTyped(KeyEvent ke){}
}
Упражнение
3. Реализуйте обработку нажатия различных клавиш.
Событие TextEvent
Событие TextEvent происходит только по одной причине — изменению текста — и отмечается идентификатором text_value_changed.
Соответствующий интерфейс имеет только один метод:
public interface TextListener extends EventListener{ public void textValueChanged(TextEvent e);
От аргумента e этого метода можно получить ссылку на объект-источник события методом getSource (), унаследованным от класса EventObj ect, например, так:
JTextComponent tc = (JTextComponent)e.getSource();
String s = tc.getText();
// Дальнейшая обработка
Событие изменения ChangeEvent
Еще одно событие общего характера ChangeEvent происходит при изменении состояния компонента Swing: щелчке кнопкой мыши на кнопке Swing любого типа, выборе пункта меню, движении ползунка и "градусника", смене страниц на панели с вкладками. Большинство этих компонентов реагируют на собственные события, более удобные для обработки, но для окна выбора цвета JColorChooser и ползунка JSlider это основное событие.
Поскольку событие класса aangeEvent носит такой общий характер, в этом классе нет собственных методов, только унаследованный метод getSource ( ).
Слушатель этого события changeListener обладает всего одним методом:
public void stateChanged(ChangeEvent);
При обработке события следует получить ссылку на источник и извлечь из него всю необходимую информацию, например:
import javax.swing.event.*;
class SliderHandler implements ChangeListener{
private int threshold; private JLabel l;
public SliderHandler(int threshold, JLabel l){ this.threshold = threshold; this.l = l;
}
public void stateChanged(ChangeEvent e){
JSlider sl = (JSlider)e.getSource(); if (!sl.getValueIsAdjusting())
if (threshold <= (int)sl.getValue()) l.setText("Порог достигнут");
}
}
Обработка действий с окном
Событие WindowEvent может произойти по двенадцати причинам:
□ окно открылось — идентификатор window_opened;
□ окно закрылось — идентификатор window_closed;
□ попытка закрытия окна — идентификатор window_closing;
□ окно получило фокус — идентификатор window_activated;
□ процесс получения фокуса — идентификатор window_gained_focus;
□ окно потеряло фокус — идентификатор window_deactivated;
□ процесс потери фокуса — идентификатор window_lost_focus;
□ окно свернулось в ярлык — идентификатор window_iconified;
□ окно развернулось — идентификатор window_deiconified;
□ всякое изменение состояния окна — window_state_changed;
□ окно стало первым — window_first;
□ окно стало последним — window_last.
Соответствующий интерфейс содержит семь методов:
public interface WindowListener extends EventListener { public void windowOpened(WindowEvent e); public void windowClosing(WindowEvent e); public void windowClosed(WindowEvent e); public void windowIconified(WindowEvent e); public void windowDeiconified(WindowEvent e); public void windowActivated(WindowEvent e); public void windowDeactivated(WindowEvent e);
}
Аргумент e этих методов дает ссылку типа Window на окно-источник методом
e.getWindow().
Чаще всего эти события используются для перерисовки окна методом repaint () при изменении его размеров и для остановки приложения при закрытии окна.
Событие ComponentEvent
Данное событие происходит в компоненте по четырем причинам:
□ компонент перемещается — идентификатор component_moved;
□ компонент меняет размер — идентификатор component_resized;
□ компонент убран с экрана — идентификатор component_hidden;
□ компонент появился на экране — идентификатор ccmponent_shcwn.
Соответствующий интерфейс содержит описания четырех методов:
public interface ComponentListener extends EventListener{ public void componentMoved(ComponentEvent e); public void componentResized(ComponentEvent e); public void componentHidden(ComponentEvent e); public void componentShown(ComponentEvent e);
}
Аргумент e методов этого интерфейса предоставляет ссылку на компонент-источник
события методом e.getComponent ().
Событие ContainerEvent
Данное событие происходит по двум причинам:
□ в контейнер добавлен компонент — идентификатор component_added;
□ из контейнера удален компонент — идентификатор component_removed.
Ртим причинам соответствуют методы интерфейса:
public interface ContainerListener extends EventListener{ public void componentAdded(ContainerEvent e); public void componentRemoved(ContainerEvent e);
}
Аргумент e предоставляет ссылку на компонент, чье добавление или удаление из контейнера вызвало событие, методом e.getChild( ) и ссылку на контейнер — источник события методом e. getContainer (). Обычно при наступлении данного события контейнер перемещает свои компоненты.
Ообытие FocusEvent
Событие возникает в компоненте, когда он получает фокус ввода — идентификатор focus_gained — или теряет фокус — идентификатор FOCUS_LOST.
Соответствующий интерфейс:
public interface FocusListener extends EventListener{ public void focusGained(FocusEvent e); public void focusLost(FocusEvent e);
}
Обычно РїСЂРё потере фокуса компонент перечерчивается бледным цветом, для этого применяется метод brighter( ) класса Color, РїСЂРё получении фокуса становится ярче, что достигается применением метода darker (). Рто приходится делать самостоятельно РїСЂРё создании своего компонента.
Событие ItemEvent
Данное событие возникает при выборе или отказе от выбора элемента в списке List, Choice, JComboBox, или кнопки выбора Checkbox, а также в кнопках Swing, наследующих от класса AbstractButton, и отмечается идентификатором ITEM_STATE_CHANGED.
Соответствующий интерфейс очень прост:
public interface ItemListener extends EventListener{ void itemStateChanged(ItemEvent e);
}
Аргумент e предоставляет ссылку на источник методом e.getItemSelectable(), ссылку на выбранный пункт методом e.getItem( ) в виде Object.
Метод e.getStateChange () позволяет уточнить, что произошло: значение selected указывает на то, что элемент был выбран, значение deselected — произошел отказ от выбора.
В главе 10 мы уже видели примеры использования этого события.
Событие AdjustmentEvent
Рто событие возникает для полосы прокрутки Scrollbar или JScrollBar РїСЂРё РІСЃСЏРєРѕРј изменении ее бегунка Рё отмечается идентификатором adjustment_value_changed.
Соответствующий интерфейс описывает один метод:
public interface AdjustmentListener extends EventListener{ public void adjustmentValueChanged(AdjustmentEvent e);
}
Аргумент e этого метода предоставляет ссылку на источник события методом e. getAdj ustable (), текущее значение положения движка полосы прокрутки методом e.getValue ( ) и способ изменения его значения методом e.getAdjustmentType(), возвращающим следующие значения:
□ unit_increment — увеличение на одну единицу;
□ unit_decrement — уменьшение на одну единицу;
□ block_increment — увеличение на один блок;
□ block_decrement — уменьшение на один блок;
□ track — процесс передвижения бегунка полосы прокрутки.
"Оживим" программу создания цвета, приведенную в листинге 10.4, добавив необходимые действия. Результат этого представлен в листинге 15.6.
Листинг 15.6. Программа создания цвета
import java.awt.*; import java.awt.event.*;
class ScrollTest1 extends Frame{ private Scrollbar
sbRed = new Scrollbar(Scrollbar.VERTICAL, 127, 16, 0, 271), sbGreen = new Scrollbar(Scrollbar.VERTICAL, 127, 16, 0, 271), sbBlue = new Scrollbar(Scrollbar.VERTICAL, 127, 16, 0, 271);
private Color c = new Color(127, 127, 127); private Label lm = new Label();
private Button
b1 = new Button("Применить"), b2 = new Button("Отменить");
ScrollTest1(String s){ super(s);
setLayout(null);
setFont(new Font("Serif", Font.BOLD, 15));
Panel p = new Panel(); p.setLayout(null);
p.setBounds(10,50, 150, 260); add(p);
Label lc = new Label("Подберите цвет"); lc.setBounds(20, 0, 120, 30); p.add(lc);
Label lmin = new Label("0", Label.RIGHT); lmin.setBounds(0, 30, 30, 30); p.add(lmin);
Label lmiddle = new Label("127", Label.RIGHT); lmiddle.setBounds(0, 120, 30, 30); p.add(lmiddle); Label lmax = new Label("255", Label.RIGHT); lmax.setBounds(0, 200, 30, 30); p.add(lmax);
sbRed.setBackground(Color.red); sbRed.setBounds(40, 30, 20, 200); p.add(sbRed); sbRed.addAdjustmentListener(new ChColor());
sbGreen.setBackground(Color.green); sbGreen.setBounds(70, 30, 20, 200); p.add(sbGreen) sbGreen.addAdjustmentListener(new ChColor());
sbBlue.setBackground(Color.blue); sbBlue.setBounds(100, 30, 20, 200); p.add(sbBlue); sbBlue.addAdjustmentListener(new ChColor());
Label lp = new Label("Образец:"); lp.setBounds(250, 50, 120, 30); add(lp);
lm.setBackground(new Color(127, 127, 127)); lm.setBounds(220, 80, 120, 80); add(lm);
b1.setBounds(240, 200, 100, 30); add(b1); b1.addActionListener(new ApplyColor());
b2.setBounds(240, 240, 100, 30); add(b2); b2.addActionListener(new CancelColor());
setSize(400, 300); setVisible(true);
}
class ChColor implements AdjustmentListener{
e) {
= c.getBlue(); getValue(); getValue(); getValue();
public void adjustmentValueChanged(AdjustmentEvent int red = c.getRed(), green = c.getGreen(), blue if (e.getAdjustable() == sbRed) red = e
else if (e.getAdjustable() == sbGreen) green = e else if (e.getAdjustable() == sbBlue) blue = e c = new Color(red, green, blue); lm.setBackground(c);
}
class ApplyColor implements ActionListener{
public void actionPerformed(ActionEvent ae){ setBackground(c);
}
}
class CancelColor implements ActionListener{ public void actionPerformed(ActionEvent ae){ c = new Color(127, 127, 127); sbRed.setValue(127); sbGreen.setValue(127) ; sbBlue.setValue(127); lm.setBackground(c); setBackground(Color.white);
}
}
public static void main(String[] args){
Frame f = new ScrollTest1(" Выбор цвета"); f.addWindowListener(new WindowAdapter(){
public void windowClosing(WindowEvent ev){ System.exit(0);
}
});
}
}
Несколько слушателей одного источника
В начале этой главы, в листингах 15.1—15.3, мы привели пример класса TextMove, слушающего сразу два компонента: поле ввода tf типа JTextField и кнопку b типа JButton.
Чаще встречается обратная ситуация — несколько слушателей следят за одним компонентом. В том же примере кнопка b в ответ на щелчок по ней кнопки мыши совершала еще и собственные действия — она "вдавливалась", а при отпускании кнопки мыши становилась "выпуклой". В классе Button эти действия осуществляет peer-объект.
В классе FlowerButton листинга 10.7 такие же действия выполняет метод paint ( ) этого класса.
В данной модели реализован design pattern под названием Observer.
К каждому компоненту можно присоединить сколько угодно слушателей одного и того же события или разных типов событий. Однако при этом не гарантируется какой-либо определенный порядок их вызова, хотя чаще всего слушатели вызываются в порядке написания методов addXxxListener ().
Если нужно задать определенный порядок вызовов слушателей для обработки события, то придется обращаться к ним друг из друга или создавать объект, вызывающий слушателей в нужном порядке.
Ссылки на присоединенные методами addXxxListener( ) слушатели можно было бы хранить в любом классе-коллекции, например Vector, но в пакет java.awt специально для этого введен класс AWTEventMulticaster. Он реализует все одиннадцать интерфейсов XxxListener, значит, сам является слушателем любого события. Основу класса составляют своеобразные статические методы add (), написанные для каждого типа событий, например:
add(ActionListener a, ActionListener b);
Своеобразие этих методов РґРІРѕСЏРєРѕРµ: РѕРЅРё возвращают ссылку РЅР° тот же интерфейс, РІ данном случае ActionListener, Рё присоединяют объект a Рє объекту b, создавая совокупность слушателей РѕРґРЅРѕРіРѕ Рё того же типа. Рто позволяет использовать РёС… наподобие операций a += b.
Заглянув в исходный текст класса Button, вы увидите, что метод addActionListener() очень прост:
public synchronized void addActionListener(ActionListener l){ if (l == null){ return; }
actionListener = AWTEventMulticaster.add(actionListener, l); newEventsOnly = true;
}
Он добавляет к совокупности слушателей actionListener нового слушателя l.
Для событий типа InputEvent, а именно KeyEvent и MouseEvent, есть возможность прекратить дальнейшую обработку события методом consume (). Если записать вызов этого метода в класс-слушатель, то ни peer-объекты, ни следующие слушатели не будут обрабатывать событие. Таким способом обычно пользуются, чтобы отменить стандартные действия компонента, например "вдавливание" кнопки.
Диспетчеризация событий
Если вам понадобится обработать просто действие мыши, не важно, нажатие это, перемещение или еще что-нибудь, то придется включать эту обработку во все семь методов двух классов-слушателей событий мыши.
Задачу можно облегчить, выполнив обработку не в слушателе, а на более ранней стадии. Дело в том, что прежде чем событие дойдет до слушателя, оно обрабатывается несколькими методами.
Чтобы в компоненте произошло событие AWT, должно быть выполнено хотя бы одно из двух условий: к компоненту присоединен слушатель или в конструкторе компонента определена возможность появления события методом enableEvents (). В аргументе этого метода через операцию побитового сложения перечисляются константы класса AWTEvent, задающие события, которые могут произойти в компоненте, например:
enableEvents (AWTEvent.MOUSE_MOTION_EVENT_MASK |
AWTEvent.MOUSE_EVENT_MASK | AWTEvent. KEY_EVENT_MASK)
При появлении события создается объект соответствующего класса XxxEvent. Метод dispatchEvent () определяет, где появилось событие — в компоненте или одном из его
подкомпонентов,- и передает объект-событие методу processEvent () компонента-
источника.
Метод processEvent () определяет тип события и передает его специализированному методу processXxxEvent (). Вот начало этого метода:
protected void processEvent(AWTEvent e){ if (e instanceof FocusEvent){
processFocusEvent((FocusEvent)e);
}else if (e instanceof MouseEvent){ switch(e.getID()){
case MouseEvent.MOUSE_PRESSED: case MouseEvent.MOUSE RELEASED: case MouseEvent.MOUSE_CLICKED: case MouseEvent.MOUSE ENTERED: case MouseEvent.MOUSE EXITED:
processMouseEvent((MouseEvent)e); break;
case MouseEvent.MOUSE MOVED: case MouseEvent.MOUSE_DRAGGED:
processMouseMotionEvent((MouseEvent)e); break;
}
}else if (e instanceof KeyEvent){ processKeyEvent((KeyEvent)e);
}
// ...
Затем в дело вступает специализированный метод, например processKeyEvent(). Он-то и передает объект-событие слушателю. Вот исходный текст этого метода:
protected void processKeyEvent(KeyEvent e){
KeyListener listener = keyListener; if (listener != null){ int id = e.getID(); switch(id){
case KeyEvent.KEY TYPED: listener.keyTyped(e); break;
case KeyEvent.KEY PRESSED: listener.keyPressed(e); break;
case KeyEvent.KEY RELEASED: listener.keyReleased(e); break;
}
}
}
РР· этого описания РІРёРґРЅРѕ, что если РІС‹ хотите обработать любое событие типа AWTEvent, то вам надо переопределить метод processEvent(), Р° если более конкретное событие, например событие клавиатуры, — переопределить более конкретный метод processKeyEvent (). Если РІС‹ РЅРµ переопределяете весь метод целиком, то РЅРµ забудьте РІ конце обратиться Рє методу суперкласса, например:
super.processKeyEvent(e);
Замечание
Не забывайте обращаться к методу processXxxEvent () суперкласса.
В главе 10 мы уже применили такое переопределение в листинге 10.10 для вызова всплывающего меню.
Создание собственного события
Вы можете создать собственное событие и определить источник и условия его возникновения.
В листинге 15.7 приведен пример создания события MyEvent, любезно предоставленный Вячеславом Педаком.
Событие MyEvent говорит о начале работы программы (start) и окончании ее работы
(stop).
Листинг 15.7. Создание собственного события
// 1. Создаем свой класс события:
public class MyEvent extends java.util.EventObject{ protected int id;
public static final int START = 0, STOP = 1; public MyEvent(Object source, int id){ super(source); this.id = id;
}
public int getID(){ return id; }
}
// 2. Описываем Listener:
public interface MyListener extends java.util.EventListener{ public void start(MyEvent e); public void stop(MyEvent e);
}
// 3. В теле нужного класса создаем метод fireEvent(): protected Vector listeners = new Vector(); public void fireEvent( MyEvent e){
Vector list = (Vector) listeners.clone(); for(int i = 0; i < list.size(); i++){
MyListener listener = (MyListener)list.elementAt(i); switch(e.getID()){
case MyEvent.START: listener.start(e); break; case MyEvent.STOP: listener.stop(e); break;
}
}
}
Все, теперь при запуске программы делаем
fireEvent(this, MyEvent.START);
а при окончании
fireEvent(this, MyEvent.STOP);
При этом все зарегистрированные слушатели получат экземпляры событий.
Вопросы для самопроверки
1. Какая модель обработки событий выбрана в AWT?
2. Что нового добавила в обработку событий библиотека Swing?
3. Какие компоненты отслеживают события мыши?
4. Какие компоненты отслеживают события клавиатуры?
5. Может ли одно и то же событие возникнуть сразу в нескольких компонентах?
6. Может ли одно действие вызвать сразу несколько событий?
7. Можно ли сделать обработку нескольких событий одним методом?
8. Можно ли обработать одно событие сразу несколькими методами?
9. Как в AWT осуществляется диспетчеризация событий?
ГЛАВА 16