Обработка событий

We use cookies. Read the Privacy and Cookie Policy

В предыдущих главах мы написали много программ, создающих компоненты интерфейса пользователя, но, собственно говоря, интерфейса, т. е. взаимодействия с пользователем, эти программы не обеспечивают. Можно щелкать по кнопке на экране, она будет "вдавливаться" в плоскость экрана, но больше ничего не будет происходить. Можно ввести текст в поле ввода, но он не станет восприниматься и обрабатываться программой. Все это происходит из-за того, что мы не задали обработку действий пользователя, т. е. обработку событий.

Событие (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 вводится строка, после нажатия клавиши <Enter> или щелчка кнопкой мыши по кнопке b строка переносится в область ta. После этого можно снова вводить строку в поле tf и т. д.

Здесь и при нажатии клавиши <Enter>, и при щелчке кнопкой мыши возникает событие класса ActionEvent, причем оно может произойти в двух компонентах-источниках: поле tf или кнопке b. Обработка события в обоих случаях заключается в получении строки текста из поля tf (например, методом tf.getText ()) и помещения ее в область ta (скажем, методом ta.append()). Значит, можно написать один обработчик события ActionEvent, реализовав соответствующий интерфейс, который называется ActionListener. В этом интерфейсе есть всего один метод actionPerformed (), который надо определить.

Итак, пишем:

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()+" ");

}

}

Обработчик событий готов. При наступлении события типа 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()+" ");

}

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()+" ");

}

}

}

Наконец, можно создать безымянный вложенный класс, что мы и делали в этой и предыдущих главах, обрабатывая нажатие комбинации клавиш <Alt>+<F4> или щелчок кнопкой мыши по кнопке закрытия окна AWT. При этом возникает событие типа WindowEvent, для его обработки мы обращались к методу windowClosing ( ), реализуя его обращением к методу завершения приложения System.exit(0). Но для этого нужно иметь суперкласс определяемого безымянного класса, такой как WindowAdapter. Такими суперклассами могут быть классы-адаптеры, о них речь пойдет чуть позднее.

Перейдем к детальному рассмотрению разных типов событий.

Упражнение

1. Реализуйте обработку события безымянным вложенным классом.