ГЛАВА 12
Обработка событий
В двух предыдущих главах мы написали
много программ, создающих интерфейсы, но, собственно, интерфейса, т. е. взаимодействия
с пользователем, эти программы не обеспечивают. Можно щелкать по кнопке на экране,
она будет "вдавливаться" в плоскость экрана, но больше ничего не будет
происходить. Можно ввести текст в поле ввода, но он не станет восприниматься
и обрабатываться программой. Все это происходит из-за того, что мы не задали
обработку действий пользователя, обработку событий.
Событие
(event) в библиотеке
AWT возникает при воздействии на компонент какими-нибудь манипуляциями мышью,
при вводе с клавиатуры, при перемещении окна, изменении его размеров.
Объект, в котором произошло событие,
называется
источником
(source) события.
Все события в AWT классифицированы.
При возникновении события исполняющая система Java автоматически создает объект
соответствующего событию класса. Этот объект не производит никаких действий,
он только хранит все сведения о событии.
Во главе иерархии классов-событий
стоит класс Eventobject из пакета java.utii — непосредственное расширение класса
object. Его расширяет абстрактный класс AWTEvent из пакета java.awt — глава
классов, описывающих события библиотеки AWT. Дальнейшая иерархия классов-событий
показана на рис. 12.1. Все классы, отображенные на рисунке, кроме класса AWTEvent,
собраны в пакет java.awt.event.
События типа ComponentEvent, FbeusEvent,
KeyEvent, MouseEvent возникают во всех компонентах.
А события типа ContainerEvent — только
в контейнерах: Container, Dialog, FileDialog, Frame, Panel, ScrollPane, Window.
Рис. 12.1
.
Иерархия классов, описывающих события AWT
События типа WindowEvent возникают
ТОЛЬКО В окнах: Frame, Dialog, FileDialog, Window.
События типа TextEvent генерируются
только в контейнерах Textcomponent, TextArea, TextField.
События типа ActionEvent проявляются
только в контейнерах Button, List, TextField.
События типа ItemEvent возникают
только в контейнерах Checkbox, Choice, List.
Наконец, события типа AdjustmentEvent
возникают только в контейнере Scrollbar.
Узнать, в каком объекте произошло
событие, можно методом getsourceo класса Eventobject. Этот метод возвращает
тип object.
В каждом из этих классов-событий
определен метод paramstring (), возвращающий содержимое объекта данного класса
в виде строки string. Кроме того, в каждом классе есть свои методы, предоставляющие
те или иные сведения о событии. В частности, метод getioo возвращает
идентификатор
(identifier) события — целое число, обозначающее тип события. Идентификаторы
события определены в каждом классе-событии как константы.
Методы обработки событий описаны
в интерфейсах-
слушателях
(listener). Для каждого показанного на рис.
12.1 типа событий, кроме inputEvent (это событие редко используется самостоятельно),
есть свой интерфейс. Имена интерфейсов составляются из имени события и слова
Listener, например, ActionListener, MouseListener. Методы интерфейса "слушают",
что происходит в потенциальном источнике события. При возникновении события
эти методы автоматически выполняются, получая в качестве аргумента объект-событие
и используя при обработке сведения о событии, содержащиеся в этом объекте.
Чтобы задать обработку события определенного
типа, надо реализовать соответствующий интерфейс. Классы, реализующие такой
интерфейс, классы-обработчики (handlers) события,, называются
слушателями
(listeners): они "слушают", что происходит в объекте, чтобы отследить
возникновение события и обработать его.
Чтобы связаться с обработчиком события,
классы-источники события должны получить ссылку на экземпляр eventHandier класса-обработчика
события одним из методов addXxxListener(XxxEvent eventHandier), где Ххх — имя
события.
Такой способ регистрации, при котором
слушатель оставляет "визитную карточку" источнику для своего вызова
при наступлении события, называется
обратный вызов
(callback). Им часто
пользуются студенты, которые, звоня родителям и не желая платить за телефонный
разговор, говорят: "Перезвони мне по такому-то номеру". Обратное действие
— отказ от обработчика, прекращение прослушивания — выполняется методом removeXxxListener
().
Таким образом, компонент-источник,
в котором произошло событие, не занимается его обработкой. Он обращается к экземпляру
класса-слушателя, умеющего обрабатывать события,
делегирует
(delegate)
ему полномочия по обработке.
Такая схема получила название схемы
делегирования
(delegation). Она удобна тем, что мы можем легко сменить
класс-обработчик и обработать событие по-другому или назначить несколько обработчиков
одного и того же события. С другой стороны, мы можем один обработчик назначить
на прослушивание нескольких объектов-источников событий.
Эта схема кажется слишком сложной,
но мы ей часто пользуемся в жизни. Допустим, мы решили оборудовать квартиру.
Мы помещаем в нее, как в контейнер, разные компоненты: мебель, сантехнику, электронику,
антиквариат. Мы предполагаем, что может произойти неприятное событие — квартиру
посетят воры, — и хотим его обработать. Мы знаем, что классы-обработчики этого
события — охранные агентства, — и обращаемся к некоторому экземпляру такого
класса. Компоненты-источники события, т. е. те, которые могут быть украдены,
присоединяют к себе датчики методом addXxxListener(). Затем экземпляр-обработчик
"слушает", что происходит в объектах, к которым он подключен. Он реагирует
на наступление только одного события — похищения прослушиваемого объекта, —
прочие события, например, короткое замыкание или обрыв водопроводной трубы,
его не интересуют. При наступлении "своего" события он действует по
контракту, записанному в методе обработки.
Замечание
В JDK 1.0 была принята другая
модель обработки событий. Не удивляйтесь, читая старые книги и просматривая
исходные тексты старых программ, но и не пользуйтесь старой моделью.
Приведем пример. Пусть в контейнер
типа Frame помещено поле ввода tf типа TextField, не редактируемая область ввода
ta типа TextArea и кнопка ь типа Button. В поле tf вводится строка, после нажатия
клавиши <Enter> или щелчка кнопкой мыши по кнопке ь строка переносится
в область ta. После этого можно снова вводить строку в поле tf и т. д.
Здесь и при нажатии клавиши <Enter>
и при щелчке кнопкой мыши возникает событие класса ActionEvent, причем оно может
произойти в двух компонентах-источниках: поле tf или кнопке ь. Обработка события
в обоих случаях заключается в получении строки текста из поля tf (например,
методом tf .getTexto) и помещений ее в область ta (скажем, методом ta. append
()). Значит, можно написать один обработчик события ActionEvent, реализовав
соответствующий интерфейс, который называется ActionListener. В этом Интерфейсе
всего один метод actionPerformed().
Итак, пишем:
class TextMove
implements ActionListener{
private TextField
tf;
private TextArea
ta;
TextMove(TextField
tf, TextArea ta){
this.tf = tf;
this.ta = ta;
}
public void actionPerformed(ActionEvent
ae){
ta.append(tf.getText()+"\n");
}
}
Обработчик событий готов. При наступлении
события типа ActionEvent будет создан экземпляр класса-обработчика TextMove,
конструктор получит ссылки на конкретные поля объекта-источника, метод actionPerformed
(), автоматически включившись в работу, перенесет текст из одного поля в другое.
Теперь напишем класс-контейнер,
в котором находятся источники tf и ь события ActionEvent, и подключим к ним
слушателя этого события TextMove, передав им ссылки на него методом addActionListenerO,
как показано в листинге 12.1.
Листинг 12.1.
Обработка события ActionEvent
import j ava.awt.*;
impo rt j ava.awt.event.*;
class MyNotebook
extends Frame{
MyNotebook(String
title) {
super(title);
TextField tf =
new TextField("Вводите текст", 50);
add(tf, BorderLayout.NORTH);
TextArea ta =
new TextArea();
ta.setEditable(false);
add(ta);
Panel p = new
Panel();
add(p, BorderLayout.SOUTH);
Button b = new
Button("Перенести");
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){
Frame f = new
MyNotebook(" Обработка ActionEvent");
f.addWindowListener(new
WindowAdapter(){
public void windowClosing(WindowEvent
ev){
System.exit(0);
}
});
}
}
// Текст класса
TextMove
// ...
На рис. 12.2 показан результат работы
с этой программой.
В листинге 12.1 в методах addActionListener()
создаются два экземпляра класса TextMove — для прослушивания поля tf и для прослушивания
кнопки ь. Можно создать один экземпляр класса TextMove, он будет прослушивать
оба компонента:
TextMove tml =
new TextMove(tf, ta);
tf.addActionListener(tml);
b.addActionListener(tml);
Но в первом случае экземпляры создаются
после наступления события в соответствующем компоненте, а во втором — независимо
от того, наступило событие или нет, что приводит к расходу памяти, даже если
событие не произошло. Решайте сами, что лучше.
Рис. 12.2.
Обработка события
ActionEvent
Класс, содержащий источники события,
может сам обрабатывать его. Вы можете самостоятельно прослушивать компоненты
в своей квартире, установив пульт сигнализации у кровати.
Для этого достаточно реализовать
соответствующий интерфейс прямо в классе-контейнере, как показано в листинге
12.2.
Листинг 12.2.
Самообработка события ActionEvent
import j ava.awt.*;
import java.awt.event.*;
class MyNotebook
extends Frame implements ActionListener{
private TextField
tf;
private TextArea
ta;
MyNotebook(String
title){
super(title) ;
tf = new TextField
("Вводите текст**", 50) ;
add(tf, BorderLayout.NORTH);
ta = new TextArea();
ta.setEditable(false);
add(ta);
Panel p = new
Panel();
add(p, BorderLayout.SOUTH);
Button b = new
Button("Перенести");
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){
Frame f = new
MyNotebook(" Обработка ActionEvent");
f.addWindowListener(new
WindowAdapter(){
public void windowClosing(WindowEvent
ev){
System.exit(0);
}
});
}
}
Здесь tf и ta уже не локальные переменные,
а переменные экземпляра, поскольку они используются и в конструкторе, и в методе
actionPerformed о. Этот метод теперь — один из методов класса MyNotebook. Класс
MyNotebook стал классом-обработчиком события ActionEvent — он реализует интерфейс
ActionListener. В МвТОДе addActionListener ()
указывается
аргумент
this —
класс сам слушает свои компоненты.
Рассмотренная схема, кажется, проще
и удобнее, но она предоставляет меньше возможностей. Если вы захотите изменить
обработку, например заносить записи в поле ta по алфавиту или по времени выполнения
заданий, то придется переписать и перекомпилировать класс MyNotebook.
Еще один вариант — сделать обработчик
вложенным классом. Это позволяет обойтись без переменных экземпляра и конструктора
в классе-обработчике TextMove, как показано в листинге 12.3.
Листинг 12.3.
Обработка вложенным классом
import Java.awt.*;
import j ava.awt.event.*;
class MyNotebook
extends Frame{ private TextField tf;
private TextArea
ta;
MyNotebook(String
title){
super(title);
tf = new TextField("Вводите
текст", 50);
add(tf, BorderLayout.NORTH);
ta = new TextArea();
ta.setEditable(false);
add (tab-Panel
p = new Panel();
add(p, BorderLayout.SOUTH);
Button b = new
Button("Перенести");
p.add(b);
tf.addActionListener(new
TextMove());
b.addActionListener(new
TextMove());
setSizepOO, 200);
setVisible(true);
}
public static
void main(String[] args){
Frame f = new
MyNotebook(" Обработка ActionEvent");
f.addWindowListener(new
WindowAdapter(){
public void windowClosing(WindowEvent
ev){
System.exit (0);
}
});
}
// Вложенный класс
class TextMove
implements ActionListener{
public void actionPerformed(ActionEvent
ae){
ta.appendftf.getText()+"\n");
}
}
}
Наконец, можно создать безымянный
вложенный класс, что мы и делали в этой и предыдущих главах, обрабатывая нажатие
комбинации клавиш <Alt>+<F4> или щелчок кнопкой мыши по кнопке закрытия
окна. При этом возникает событие типа windowEvent, для его обработки мы обращались
к методу windowciosingo, реализуя его обращением к методу завершения приложения'System.exit
(0). Но для этого нужно иметь суперкласс определяемого безымянного класса, такой
как windowAdapter. Такими суперклассами могут быть классы-адаптеры, о них речь
пойдет чуть ниже.
Перейдем к детальному рассмотрению
разных типов событий.
Событие
ActionEvent
Это простое событие означает, что
надо выполнить какое-то действие. При этом неважно, что вызвало событие: щелчок
мыши, нажатие клавиши или что-то другое.
В классе ActionEvent есть два полезных
метода:
-
метод
getActionCommand
()
возвращает в виде строки
string
надпись на
кнопке
Button
, точнее, то, что установлено методом
setActionCoramand
(String s)
класса
Button
,
выбранный пункт списка
List
, или что-то другое, зависящее
от компонента;
-
метод
getModifiers()
возвращает код клавиш
<Alt>, <Ctrl>, <Meta>
или
<Shift>
, если какая-нибудь одна или несколько
из них были нажаты, в виде числа типа
int
; узнать, какие
именно клавиши были нажаты, можно сравнением со статическими константами этого
класса
ALT_MASK
,
CTRL_MASK, META_MASK,
SHIFT_MASK.
Примечание
Клавиши <Meta> на PC-клавиатуре
нет, ее действие часто назначается на клавишу <Esc> или левую клавишу
<Alt>.
Например:
public void actionPerformed(ActionEvent
ae){
if (ae.getActionCommand()
== "Open" &&
(ae.getModifiers()
| ActionEvent.ALT_MASK) != 0){
// Какие-то действия
}
}
|