Принципы обработки событий

Содержание

1 Введение
2 Классы Adapter
3 Интерфейс KeyListener и абстрактный класс KeyAdapter
5 Ссылки

Введение

Почему в некоторых случаях многократное нажатие на кнопку не приводит ни к какому результату? Проектируя программы с графическим интерфейсом при помощи набора компонентов Swing, необходимо разбираться в модели обработки событий. Для создания интерфейса, взаимодействующего с пользователем, недостаточно просто разместить компоненты на экране.

Модель обработки событий из набора компонентов Swing ассоциирует каждый компонент с его наблюдателем таким образом, чтобы при возникновении события об этом уведомлялись все его наблюдатели. Наблюдатель можно назвать объектом, «заинтересованным» в возникновении события. Узнать о наблюдателях, применяемых для некоторых событий компонента, мы сможем на примере Swing.

При использовании компонентов Swing наблюдателей называют слушателями событий. Они должны реализовывать пустой интерфейс java.util.EventListener, а также (почти во всех случаях) интерфейс слушателя подкласса, который должен иметь хотя бы один метод. События остаются событиями, однако они должны создавать подкласс класса java.util.EventObject. Чрезвычайно важно знать, с какими событиями компонента ассоциированы определенные слушатели событий.

Прежде чем начать изучение принципа поиска событий, ассоциированных с компонентами, рассмотрите частный случай – выбор кнопки JButton. За данное действие отвечает событие ActionEvent, которое генерируется при выборе пользователем кнопки JButton. Если какой-либо объект заинтересован в происхождении данного события, его необходимо зарегистрировать для кнопки JButton.

Для события ActionEvent регистрация проводится по форме ActionListener. Имена в ней связаны соответствующим образом, а для каждого события формы ABCEvent ассоциированным слушателем является ABCListener, где вместо ABC указывается конкретный тип события.

Регистрация происходит при вызове метода addActionListener. Каждый зарегистрированный реализатор уведомляется путем передачи (при генерации события ActionEvent) реализации интерфейса слушателя методу addActionListener. У одного события может быть несколько наблюдателей.

Весь процесс представлен следующими пунктами (хотя порядок их следования может меняться).

  • Определить для заданного события класс, реализующий связанный с ним интерфейс. В существующее определение класса можно добавить строку "implements ABCListener" или создать новый класс, реализующий данный интерфейс.

  • Добавления в определение класса строки "implements ABCListener" недостаточно. Кроме этого необходимо реализовать каждый метод данного интерфейса. У одних слушателей (например, ActionListener) есть только один метод. У других (например, WindowListener, используемого при перемещении или закрытии окна) – несколько. Код можно компилировать тогда, когда определены все методы слушателей. Однако и это еще не все. Для того чтобы программа должным образом реагировала на события необходимо выполнить последнее действие.

  • После определения реализации интерфейса необходимо создать экземпляр реализации и ассоциировать его с компонентом. Только после этого наблюдатель (слушатель) сможет получать уведомления о возникновении соответствующих событий.

В данной программе продемонстрирован случай обработки события «выбор кнопки»:

import javax.swing.*;
import java.awt.*;
import java.awt.event.*;

class MyActionListener implements ActionListener {
 
public void actionPerformed(ActionEvent e) {
   
System.out.println("Thank you.");
 
}
}

public class SelectMe extends JFrame {

 
public SelectMe() {
   
super("Hello");
    setDefaultCloseOperation
(EXIT_ON_CLOSE);
    JButton button =
new JButton("Pick Me");
    ActionListener listener =
new MyActionListener();
    button.addActionListener
(listener);
    getContentPane
().add(button, BorderLayout.CENTER);
    setSize
(200, 200);
 
}

 
public static void main(String args[]) {
   
JFrame frame = new SelectMe();
    frame.show
();
 
}
}

Как узнать, какие события инициируются каждым из компонентов? Ответ на этот вопрос можно найти в документации по API. Взгляните еще раз на пару методов add/removeABCListener, в которых вместо ABC указывается тип события. Для каждой пары методов add/remove будет сгенерировано отдельное событие. Для того чтобы отключить отправку уведомления о происхождении события используйте метод removeABCListener.

Рассмотрим класс JButton с целью определения, какие еще события можно «услышать». В документации API по классу JButton сказано, что кнопка может реагировать и на отсутствие события. В данном классе нет методов add/remove. Это один из случаев, когда используются принципы объектно-ориентированного программирования. Необходимо взглянуть в суперкласс AbstractButton. Множество его поведений используется совместно несколькими выбираемыми Swing компонентами, таким образом, базис кода обработки событий переносится на уровень выше.

На уровне AbstractButton слушателями, ассоциируемыми с классом Jbutton, кроме ActionListener могут быть ChangeListener и ItemListener.

Для успешной работы с компонентами и ассоциированными с ними наблюдателями событий вам потребуется выяснить, для чего предназначен каждый слушатель. В то время как ActionListener используется для указания на то, что компонент выбран, ChangeListener указывает на изменение каких-либо свойств кнопки.

Свойства компонента описывают его состояние. Свойствами класса JButton являются текстовая метка, фоновый и основной цвета, а также шрифт. При изменении одного из свойств уведомляются все зарегистрированные слушатели ChangeListener, причем, информация о названии измененного свойства в событии не передается. Слушатель PropertyChangeListener выполняет те же функции и, кроме этого, указывает на измененное свойство.

В следующей программе к предыдущему примеру ActionListener добавлен слушатель ChangeListener. При запуске программы можно увидеть, что слушатель ChangeListener неоднократно получает уведомления. Каждое изменение состояния инициирует отправку уведомления. В нашем примере во время настройки фонового цвета и рамки компонента его состояние изменяется множество раз.

import javax.swing.*;
import javax.swing.event.*;
import java.awt.*;
import java.awt.event.*;

class MyActionListener implements ActionListener {
 
public void actionPerformed(ActionEvent e) {
   
System.out.println("Thank you.");
 
}
}

class MyChangeListener implements ChangeListener {
 
public void stateChanged(ChangeEvent e) {
   
System.out.println("You're welcome.");
 
}
}

public class SelectMe extends JFrame {

 
public SelectMe() {
   
super("Hello");
    setDefaultCloseOperation
(EXIT_ON_CLOSE);
    JButton button =
new JButton("Pick Me");
    ActionListener aListener =
new MyActionListener();
    button.addActionListener
(aListener);
    ChangeListener cListener =
new MyChangeListener();
    button.addChangeListener
(cListener);
    getContentPane
().add(button, BorderLayout.CENTER);
    setSize
(200, 200);
 
}

 
public static void main(String args[]) {
   
JFrame frame = new SelectMe();
    frame.show
();
 
}
}

Как уже упоминалось ранее, с классом JButton можно ассоциировать также слушатель ItemListener. Но на этом обработка событий кнопки не заканчивается. Двигаясь дальше, мы столкнемся с суперклассом JComponent для класса AbstractButton.

Для класса JComponent родительским будет класс Container, чьим родительским классом будет, соответственно, класс Component. На каждом из данных уровней иерархии имеется свой набор слушателей, которые можно ассоциировать с кнопкой. У класса java.lang.Object таких слушателей нет.

Далее перечислены все возможные события, которые инициирует класс JButton и которые можно «услышать».

  • java.awt.Component: ComponentListener, FocusListener, HierarchyBoundsListener, HierarchyListener, InputMethodListener, KeyListener, MouseListener, MouseMotionListener, MouseWheelListener, PropertyChangeListener

  • java.awt.Container: ContainerListener, PropertyChangeListener javax.swing.JComponent AncestorListener, PropertyChangeListener, VetoableChangeListener

  • javax.swing.AbstractButton: ActionListener, ChangeListener, ItemListener

Список мог показаться вам чрезмерно большим, однако, не торопитесь с выводами. В большинстве случаев слушатель ActionListener просто ассоциируется с кнопкой, что позволяет ему отслеживать событие.

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

Теперь вы можете проверить полученные знания относительно обработки событий с помощью нашего он-лайн теста.

Классы Adapter

API AWT и Project Swing определяют интерфейсы, называемые слушателями. У каждого типа события есть интерфейс слушателя, а у каждого слушателя есть методы для каждого возможного события.

Интерфейс слушателя требует от вас замены всех его абстрактных методов на методы, определенные в вашем классе. Даже если вам не нужны все эти методы, вы должны будете реализовать их, не устанавливая в теле кода какого-либо содержимого. При таком подходе код получается запутанным и на его написание затрачивается больше времени (в некоторых интерфейсах присутствует порядка семи абстрактных методов).

Например, интерфейс MouseListener сконструирован для того, чтобы получать для компонента события мыши (press, release, click, enter, exit). Для использования интерфейса 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)

Если для вашего приложения требуется переопределить только один из этих методов, то необходимо будет также реализовать и остальные. Избежать реализации методов пустого интерфейса можно путем использования классов adapter.

Классы adapter обеспечивают пустую реализацию всех методов интерфейса слушателя событий. Новый класс, действующий как слушатель событий, определяется путем расширения одного из классов adapter, что позволяет вам переопределять только интересующие вас методы.

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

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;

public class MouseAdapterExample extends JFrame {
 
JButton button1, button2;
  JTextField tf;

 
public MouseAdapterExample() {
   
// открыть конструктор

    // создать кнопки и добавить слушателей
   
button1 = new JButton("Button 1");
    button1.setName
("Button 1");
    button1.addMouseListener
(new MouseHandler());
    button2 =
new JButton("Button 2");
    button2.setName
("Button 2");
    button2.addMouseListener
(new MouseHandler());

   
// Создать текстовое поле, в котором
    // пользователь не сможет вводить текст
   
tf = new JTextField(25);
    tf.setEditable
(false);

   
// создать панели, добавить кнопки и текстовое поле
   
JPanel p1 = new JPanel();
    p1.setBackground
(Color.white);
    p1.add
(button1);
    p1.add
(button2);

    JPanel p2 =
new JPanel();
    p2.setBackground
(Color.white);
    p2.add
(tf);

   
// получить ContentPane и добавить панели
   
getContentPane().setLayout(new BorderLayout());
    getContentPane
().add(p1, BorderLayout.NORTH);
    getContentPane
().add(p2, BorderLayout.SOUTH);

    addWindowListener
(new WinClosing());
    setBounds
(100, 100, 300, 100);
    setVisible
(true);

 
}

 
// закрыть конструктор

  // MouseHandler обрабатывает события мыши и
  // расширяет класс adapter, обеспечивающий пустые
  // методы. Данный класс (MouseHandler) переопределяет
  // методы mouseEntered и mouseExit.
 
class MouseHandler extends MouseAdapter {
   
public void mouseEntered(MouseEvent me)
           {
            
// При перемещении мыши над кнопкой
             // имя кнопки фиксируется и отображается в текстовом поле.
            
tf.setText("Mouse is over
                 "
+me.getComponent().getName());
           
}

   
public void mouseExited(MouseEvent me) {
     
// Когда мышь покидает область кнопки
      // значение в текстовом поле сбрасывается.

     
tf.setText("");
   
}
  }

 
// закрыть MouseHandler

 
public static void main(String args[]) {
   
MouseAdapterExample mae = new MouseAdapterExample();
 
}
}

// Данный класс расширяет класс adapter и
// переопределяет только один метод: windowClosing. Вместо
// того, чтобы использовать данный класс для закрытия окна,
// вы можете добавить в код еще одну строку:
// setDefaultCloseOperation(EXIT_ON_CLOSE);
// как показано ранее в примере SelectMe.
class WinClosing extends WindowAdapter {
 
public void windowClosing(WindowEvent we) {
   
System.exit(0);
 
}
}

Интерфейс KeyListener и абстрактный класс KeyAdapter

События клавиатуры также можно отслеживать. Для получения такого рода событий используется интерфейс KeyListener. Объекты-слушатели регистрируются для таких компонентов, как окно или панель, при помощи метода addKeyListener компонента.

Класс, заинтересованный в обработке событий клавиатуры, или реализует данный интерфейс вместе со всеми его методами, или же расширяет абстрактный класс KeyAdapter. В последнем случае вы можете переопределить только те методы, которые имеют значение для приложения.

В качестве примера в следующем приложении создается объект, который устанавливает, вводятся ли с клавиатуры какие-либо символы. Введенный текст отображается в метке внизу экрана:

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;

public class KeyAdapterExample extends JFrame {
 
// Метка, отображающая введенный текст.
 
JLabel label;
 
// Окно, в котором перемещается мышь и вводится текст
 
JPanel p;

 
public KeyAdapterExample() {
   
label = new JLabel("You typed: ");

    p =
new JPanel();
    p.setBackground
(Color.white);
   
// Зарегистрировать введенный символ
   
p.addKeyListener(new KeyHandler());
   
// Зарегистрировать перемещение мыши в области глухой панели
   
p.addMouseListener(new MyMouseAdapter(p));

   
// Получить ContentPane и отформатировать
   
getContentPane().setLayout(new BorderLayout());
    getContentPane
().add(p, BorderLayout.CENTER);
    getContentPane
().add(label, BorderLayout.SOUTH);
   
// зарегистрировать выход из программы
   
addWindowListener(new WinClosing());
    setBounds
(100, 100, 200, 200);
    setVisible
(true);
 
}

 
// закрыть конструктор

  // Класс, обрабатывающий набор символов
 
class KeyHandler extends KeyAdapter {
   
// Метод, извлекающий набранные символы и
    // устанавливающий их в качестве значений метки
   
public void keyTyped(KeyEvent ke) {
     
label.setText("You typed: " + ke.getKeyChar());
      label.invalidate
();
      invalidate
();
      validate
();
   
}
  }

 
// Класс, обрабатывающий перемещение мыши
 
class MyMouseAdapter extends MouseAdapter {
   
MyMouseAdapter(Component c) {
     
this.c = c;
   
}

   
public void mousePressed(MouseEvent e) {
     
c.requestFocus();
   
}

   
private Component c;
 
}

 
public static void main(String args[]) {
   
KeyAdapterExample kae = new KeyAdapterExample();
 
}
}

class WinClosing extends WindowAdapter

{
 
public void windowClosing(WindowEvent we) {
   
System.exit(0);
 
}
}

Ссылки

Event Handling (обработка событий)

Lesson: Writing Event Listeners (урок: создание слушателей событий)

Lesson: Swing Features and Concepts (урок: возможности и принципы Swing)

How to Write a Mouse Listener (создание слушателя событий мыши)

Building an Application Introduction, Part 3 (введение в создание приложений, часть 3)

Теги: events JFrame