Модель обработки событий в AWT представляет собой, по существу, модель обратных вызовов (callback). При создании GUI-элемента ему сообщается, какой метод или методыон должен вызывать при возникновении нем определенного события (нажатия кнопки, мыши и т.п.). Эту модель очень легко использовать в C++, поскольку этот язык позволяет оперировать указателями на методы (чтобы определить обратный вызов, необходимо всего лишь передать указатель на функцию). Однако в Java это недопустимо (методы не являются объектами). Поэтому для реализации модели необходимо определить класс, реализующий некоторый специальный интерфейс. Затем можно передать экземпляр такого класса GUI-элементу, обеспечивая таким образом обратный вызов. Когда наступит ожидаемое событие, GUI-элемент вызовет соответствующий метод объекта, определенного ранее.
Модель обработки событий Java используется как в пакете AWT, так и в JavaBeans API. В этой модели разным типам событий соответствуют различные классы Java. Каждое событие является подклассом класса java.util.EventObject. События пакета AWT, которые и рассматриваются в данной главе, являются подклассом java.awt.AWTEvent. Для удобства события различных типов пакета AWT (например, MouseEvent или АсtionEvent) помещены новый пакет java.awt.event. Для каждого события существует порождающий его объект, который можно получить с помощью метода getSource(), и каждому событию пакета AWT соответствует определенный идентификатор, который позволяет получить метод getID(). Это значение используется для того, чтобы отличать события различных типов, которые могут описываться одним и тем же классом событий. Например, для класса FocusEvent возможны два типа событий:
FocusEvent.FOCUS_GAINED и FocusEvent.FOCUS_LOST. Подклассы событий содержат информацию, связанную с данным типом события. Например, в классе MouseEvent существуют методы getX(), getY() и getClickCount (). Этот класс наследует, в числе прочих, и методы getModifiers() и getWhen().
Модель обработки событий Java базируется на концепции слушателя событий. Слушателем события является объект, заинтересованный в получении данного события. В объекте, который порождает событие (в источнике событий), содержится список слушателей, заинтересованных в получении уведомления о том, что данное событие произошло, а также методы, которые позволяют слушателям добавлять или удалять себя из этого списка. Когда источник порождает событие (или когда объект источника зарегистрирует событие, связанное с вводом информации пользователем), он оповещает все объекты слушателей событий о том, что данное событие произошло.
Источник события оповещает объект слушателя путем вызова специального метода и передачи ему объекта события (экземпляра подкласса EventObject). Для того чтобы источник мог вызвать данный метод, он должен быть реализован для каждого слушателя. Это объясняется тем, что все слушатели событий определенного типа должны реализовывать соответствующий интерфейс. Например, объекты слушателей событий ActionEvent должны реализовывать интерфейс ActionListener. В пакете java.awt.event содержатся интерфейсы слушателей для каждого из определенных в нем типов событий (например, для событий MouseEvent здесь определено два интерфейса слушателей: MouseListener и MouseMotionListener). Все интерфейсы слушателей событий являются расширениями интерфейса java.util.EventListener. В этом интерфейсе не определяется ни один из методов, но он играет роль интерфейса-метки, в котором однозначно определены все слушатели событий как таковые.
В интерфейсе слушателя событий может определяться несколько методов. Например, класс событий, подобный MouseEvent, описывает несколько событий, связанных с мышью, таких как события нажатия и отпускания кнопки мыши. Эти события вызывают различные методы соответствующего слушателя. По установленному соглашению, методам слушателей событий может быть передан один единственный аргумент, являющийся объектом того события, которое соответствует данному слушателю. В этом объекте должна содержаться вся информация, необходимая программе для формирования реакции на данное событие.В таблице 1 приведены определенные в пакете java.awt.event типы событий, соответствующие им слушатели, а также методы, определенные в каждом интерфейсе слушателя [1].
Для каждого интерфейса слушателей событий, содержащего несколько методов, в пакете java.awt.event определен простой класс-адаптер, который обеспечивает пустое тело для каждого из методов соответствующего интерфейса. Когда нужен только один или два таких метода, иногда проще получить подкласс класса-адаптера, чем реализовать интерфейс самостоятельно. При получении подкласса адаптера требуется лишь переопределить те методы, которые нужны, а при прямой реализации интерфейса необходимо определить все методы, в том числе и ненужные в данной программе. Заранее определенные классы-адаптеры называются так же, как и интерфейсы, которые они реализуют, но в этих названиях Listener заменяется на Adapter: MouseAdapter, WindowAdapter и т.д. Как только реализован интерфейс слушателя или получены подклассы класса-адаптера, необходимо создать экземпляр нового класса, чтобы определить конкретный объект слушателя событий. Затем этот слушатель должен быть зарегистрирован соответствующим источником событий. В программах пакета AWT источником событий всегда является какой-нибудь элемент пакета. В методах регистрации слушателей событий используются стандартные соглашения об именах: если источник событий порождает события типа X, в нем существует метод addXListener () для добавления слушателя и метод removeXListener() для его удаления. Одной из приятных особенностей модели обработки событий Java является возможность легко определять типы событий, которые могут порождаться данным элементом. Для этого следует просто просмотреть, какие методы зарегистрированы для его слушателя событий. Например, из описания API для объекта класса Button следует, что он порождает события ActionEvent. В таблице 2 приведен список элементов пакета AWT и событий, которые они порождают [1].
Примеры
Рассмотрим код программы, в котором приведена обработка события, возникающего в экземплярах класса JButton. Для обработки события применяется внутренний класс ButtonListener, который объявлен и описан внутри класса апплета Button2. При нажатии на кнопку, с нее считывается надпись и помещается в компонент JTextField.
import javax.swing.*;
import java.awt.event.*;
import java.awt.*;
public class Button2 extends JApplet {
private JButton
b1 = new JButton("Button 1"),
b2 = new JButton("Button 2");
private JTextField txt = new JTextField(10);
//класс обработчик событий, реализующий интерфейс ActionListener class ButtonListener implements ActionListener {
public void actionPerformed(ActionEvent e) { String name = ((JButton)e.getSource()).getText(); txt.setText(name);
}}
//создание экземпляра класса-обработчика private ButtonListener bl = new ButtonListener();
public void init() {
b1.addActionListener(bl); //регистрация слушателя события
b2.addActionListener(bl); // экземпляра bl класса ButtonListener
setLayout(new FlowLayout());
add(b1);
add(b2);
add(txt);
}}
Данный апплет работает следующим образом. (Button_Label)
Еще один более сложный пример обработки событий, возникающих в кнопках Button, приведен здесь (Button_Color/ButtonApplet.java).
Следующий пример – рисование каракулей на апплете с помощью мыши. Это классический апплет, в котором используется модель обработки событий Java. В этом примере реализованы интерфейсы MouseListener и MouseMotionListener, регистрирующие себя с помощью своих же методов addMouseListener() и addMouseMotionListener().
Данную программу можно реализовать различными способами.
Рассмотрим сначала способ [1], в котором в качестве слушателя событий выступает сам апплет Scribble2, а не некоторый внутренний класс.
import java.applet.*;
import java.awt.*;
import java.awt.event.*;
//сам апплет реализует интерфейсы,т.е. является слушателем public class Scribble2 extends Applet implements MouseListener, MouseMotionListener { private int last_x, last_y;
public void init() {
//Сообщает данному апплету о том, какие объекты
//классов MouseListener и MouseMotionListener он
//должен оповещать о событиях, связанных с мышью
//и ее перемещением.
//Поскольку интерфейс реализуется в самом апплете,
//при этом будут вызываться методы апплета. this.addMouseListener(this); this.addMouseMotionListener(this);
}
//Метод интерфейса MouseListener. Вызывается при нажатии
//пользователем кнопки мыши.
public void mousePressed(MouseEvent e) {
last_x = e.getX();
last_y = e.getY();
}
//Метод интерфейса MouseMotionListener. Вызывается при //перемещении мыши с нажатой кнопкой.
public void mouseDragged(MouseEvent e) { Graphics g = this.getGraphics(); int x = e.getX(), y = e.getY();
g.drawLine(last_x, last_y, x, y);
last_x = x; last_y = y;
}
//Другие, не используемые методы интерфейса MouseListener. public void mouseReleased(MouseEvent e) {;}
public void mouseClicked(MouseEvent e) {;} public void mouseEntered(MouseEvent e) {;} public void mouseExited(MouseEvent e) {;}
//Другой метод интерфейса MouseMotionListener.
public void mouseMoved(MouseEvent e) {;} }
Рассмотрим второй вариант [1] реализации данного примера с
привлечением встроенных классов. Модель обработки событий Java разработана с учетом того, чтобы хорошо сочетаться с другой особенностью Java: встроенными классами. В следующем примере показано, как изменится данный апплет, если слушатели событий будут реализованы в виде анонимных встроенных классов. Обратите внимание на компактность данного варианта программы. Новая особенность, добавленная в апплет - кнопка Clear. Для этой кнопки зарегистрирован объект ActionListener, а сама она выполняет очистку экрана при наступлении соответствующего события.
import java.applet.*;
import java.awt.*;
import java.awt.event.*;
public class Scribble3 extends Applet {
int last_x, last_y;
public void init() {
//Определяет, создает и регистрирует объект MouseListener. this.addMouseListener(new MouseAdapter() {
public void mousePressed(MouseEvent e) { last_x = e.getX(); last_y = e.getY();}
});
//Определяет, создает и регистрирует объект MouseMotionListener. this.addMouseMotionListener(new MouseMotionAdapter() {
public void mouseDragged(MouseEvent e) { Graphics g = getGraphics();
int x = e.getX(), y= e.getY(); g.setColor(Color.black); g.drawLine(last_x, last_y, x, y); last_x = x; last_y = y;
}
});
//Создает кнопку Clear.
//Определяет, создает и регистрирует объект слушателя
//для обработки события, связанного с нажатием кнопки. b.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
//стирание каракулей
Graphics g = getGraphics();
g.setColor(getBackground());
g.fillRect(0, 0, getSize().width, getSize().height);
}
});
//Добавляет кнопку в апплет. this.add(b);
}}
Стоит заметить, что в аппретах с рисованием каракулей не выполняется никаких действий по различению левой и правой кнопок мыши, т.е. рисовать можно ими обеими. Java предоставляет такие возможности с помощью констант Button1_Mask. В программном коде ниже приведен пример, отличающий левую и правую кнопки в программе:
public void mousePressed(MouseEvent e) {
if((e.getModifiers()&MouseEvent.BUTTON1_MASK)!=0){ //левая кнопка
//действия
}
if((e.getModifiers()&MouseEvent.BUTTON1_MASK)==0){
// правая кнопка
//действия
}