8354 Глава 8. Обработка событий Например, если...

60
••••• 8 ОБРАБОТКА СОБЫТИЙ В этой главе... d Общие сведения об обработке событий d Иерархия событий библиотеки AWT d Семантические и низкоуровневые события в библиотеке AWT d Типы низкоуровневых событий d Действия d Многоадресная передача событий d Реализация источников событий бработка событий имеет огромное значение для создания программ с графи ческим пользовательским интерфейсом. Чтобы профессионально реализовать ин терфейс программы, необходимо очень хорошо разбираться в этом механизме. В этой главе описана модель событий Java AWT. Вы узнаете, как перехватывать собы тия мыши и клавиатуры, а также использовать простейшие элементы графического пользовательского интерфейса, например кнопки. В частности, здесь обсуждается обработка событий, генерируемых этими компонентами. В следующей главе будет по казано, как объединить в одно целое компоненты библиотеки Swing, учитывая весь спектр событий, которые ими генерируются. В Java 1.1 была предложена новая модель обработки событий, позволяющая созда вать эффективно работающие пользовательские интерфейсы. Устаревшая модель события, использованная в Java 1.0, рассматриваться здесь не будет. Общие сведения об обработке событий Каждая операционная система, поддерживающая графический пользовательский интерфейс, непрерывно отслеживает такие события, как нажатие клавиш или щелчок мыши, а затем сообщает о них выполняемой программе. Программа решает, как реа гировать на эти события (если это предусмотрено ее кодом). В языках, подобных языку Visual Basic, соответствие между событиями и кодом очевидно: среда програм мирования создает код, реагирующий на определенное событие, и помещает его в так называемую процедуру обработки события (event procedure). Например, после щелчка О

Upload: others

Post on 12-Jun-2020

9 views

Category:

Documents


0 download

TRANSCRIPT

Page 1: 8354 Глава 8. Обработка событий Например, если пользователь щелкает на кнопке с надписью Yellow, вызывается

••••• 8

ОБРАБОТКА СОБЫТИЙ

В этой главе...

Общие сведения об обработке событий Иерархия событий библиотеки AWT Семантические и низкоуровневые события

в библиотеке AWT Типы низкоуровневых событий Действия Многоадресная передача событий Реализация источников событий

бработка событий имеет огромное значение для создания программ с графи�ческим пользовательским интерфейсом. Чтобы профессионально реализовать ин�терфейс программы, необходимо очень хорошо разбираться в этом механизме.В этой главе описана модель событий Java AWT. Вы узнаете, как перехватывать собы�тия мыши и клавиатуры, а также использовать простейшие элементы графическогопользовательского интерфейса, например кнопки. В частности, здесь обсуждаетсяобработка событий, генерируемых этими компонентами. В следующей главе будет по�казано, как объединить в одно целое компоненты библиотеки Swing, учитывая весьспектр событий, которые ими генерируются.

В Java 1.1 была предложена новая модель обработки событий, позволяющая созда+вать эффективно работающие пользовательские интерфейсы. Устаревшая модельсобытия, использованная в Java 1.0, рассматриваться здесь не будет.

Общие сведения об обработке событийКаждая операционная система, поддерживающая графический пользовательский

интерфейс, непрерывно отслеживает такие события, как нажатие клавиш или щелчокмыши, а затем сообщает о них выполняемой программе. Программа решает, как реа�гировать на эти события (если это предусмотрено ее кодом). В языках, подобныхязыку Visual Basic, соответствие между событиями и кодом очевидно: среда програм�мирования создает код, реагирующий на определенное событие, и помещает его в такназываемую процедуру обработки события (event procedure). Например, после щелчка

О

Page 2: 8354 Глава 8. Обработка событий Например, если пользователь щелкает на кнопке с надписью Yellow, вызывается

350 Глава 8. Обработка событий

на кнопке HelpButton будет вызвана процедура HelpButton_Click. Каждому ком�поненту пользовательского интерфейса в языке Visual Basic соответствует фиксиро�ванное множество событий, состав которого изменить невозможно.

Если для создания программы, реагирующей на события, используется язык, по�добный языку С, программист должен создать код, непрерывно проверяющий оче�редь событий, которая передается ему операционной системой. (Обычно для этогокод обработки событий помещается в гигантский цикл с большим количеством опе�раторов switch.) Создавать такие программы чрезвычайно трудно. Однако подоб�ный способ имеет преимущество, заключающееся в том, что множество событий ни�чем не ограничено, в отличие от языка Visual Basic, в котором очередь событий скры�та от программиста.

Подход, принятый в языке Java, представляет собой нечто среднее между подходами,применяемыми в языках Visual Basic и С. Он довольно сложен. При обработке событий,предусмотренных в библиотеке AWT, программист полностью контролирует передачу со�бытий от источников (event sources) (например, кнопок или полос прокруток) к обработчику(event listener). Любой объект можно определить как обработчик некоего события —на практике все объекты так или иначе реагируют на события. Эта модель делегированиясобытий (event delegation model) позволяет достичь большей гибкости по сравнениюс языком Visual Basic, в котором обработчики предопределены. Однако в процессе созда�ния программы нужно написать намного больше кода, разобраться в котором намногосложнее (по крайней мере до тех пор, пока вы не привыкнете к этой технологии).

Источники событий содержат методы, позволяющие связывать их с обработчика�ми. Когда происходит соответствующее событие, источник извещает всех зарегист�рированных обработчиков.

Поскольку язык Java является объектно�ориентированным, информация о событииинкапсулируется в объекте события (event object). В языке Java все события описываютсяподклассами java.util.EventObject. В качестве примеров можно привестиActionEvent и WindowEvent.

Различные источники могут порождать разные виды событий. Например, кнопкаможет создавать объекты класса ActionEvent, а окно — объекты класса WindowEvent.

Кратко механизм обработки событий AWT можно описать следующим образом.

• Обработчик представляет собой экземпляр класса, реализующего специальныйинтерфейс.

• Источник события — это объект, который может регистрировать обработчикии посылать им объекты событий.

• При наступлении события источник посылает объекты события всем зарегист�рированным обработчикам.

• Обработчики используют информацию, инкапсулированную в объекте собы�тия, для того, чтобы решить, как реагировать на это событие.

Для регистрации обработчика применяется выражение в следующем формате:

объект_источника_события.add_событие_Listener(обработчик_события)

Рассмотрим следующий фрагмент программы:ActionListener listener = ...;Jbutton button = new JButton("OK");button.addActionListener(listener);

Page 3: 8354 Глава 8. Обработка событий Например, если пользователь щелкает на кнопке с надписью Yellow, вызывается

Общие сведения об обработке событий 351

Теперь обработчик оповещается о событии, связанном с кнопкой. Это можетбыть, например, простой щелчок.

Код, подобный приведенному выше, требует, чтобы класс обработчика реализовывалсоответствующий интерфейс (в данном случае — ActionListener). Как и для остальныхинтерфейсов, реализация предполагает наличие методов, имеющих соответствующуюсигнатуру. Для того чтобы реализовать интерфейс ActionListener, класс обработчикадолжен иметь метод с именем actionPerformed(), получающий в качестве параметраобъект класса ActionEvent.

class MyListener implements ActionListener{ ... public void actionPerformed(ActionEvent event) { // Реакция на активизацию кнопки. ... }}

Когда пользователь щелкает на кнопке, объект класса JButton создает объект классаActionEvent и вызывает метод listener.actionPerformed(event), передавая емуобъект события. Обработчиками события (например, щелчка мышью на кнопке) можноназначать и несколько объектов одновременно. В этом случае после щелчка на кнопкеобъект класса JButton вызовет методы actionPerformed() всех зарегистрирован�ных обработчиков.

На рис. 8.1 показано взаимодействие источника, обработчика и объекта события.

MyListener

JButton

MyPanel

new

addActionListener

new

actionPerformed

Рис. 8.1. Регистрация и обработка события

Page 4: 8354 Глава 8. Обработка событий Например, если пользователь щелкает на кнопке с надписью Yellow, вызывается

352 Глава 8. Обработка событий

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

Пример: обработка щелчка на кнопкеДля того чтобы лучше освоить модель делегирования событий, рассмотрим под�

робно, как обрабатывается щелчок на кнопке. Для этого нам понадобятся три кнопки,расположенные на панели, и три объекта�обработчика, обеспечивающие реакцию насобытия, связанные с кнопками.

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

Прежде чем перейти к рассмотрению программы, реагирующей на щелчок кнопки,нужно объяснить, как создаются кнопки и как их размещают на панели. (Более подроб�но элементы графического пользовательского интерфейса рассматриваются в главе 9.)

Для того чтобы создать кнопку, в ее конструкторе нужно задать метки, пиктограм�му или оба параметра одновременно. Рассмотрим две строки кода.

JButton yellowButton = new JButton("Yellow");JButton blueButton = new JButton(new ImageIcon("blue-ball.gif"));

Кнопки помещаются на панель с помощью вызова метода с достаточно вырази�тельным именем add(). Этот метод получает в качестве параметра указанный компо�нент и добавляет его в контейнер. Ниже приведен пример, демонстрирующий опи�санные действия.

class ButtonPanel extends JPanel{ public ButtonPanel() { JButton yellowButton = new JButton("Yellow"); JButton blueButton = new JButton("Blue"); JButton redButton = new JButton("Red");

add(yellowButon); add(blueButton); add(redButton); }}

Результат работы этого фрагмента показан на рис. 8.2.Теперь, поместив кнопки на панель, надо добавить код, позволяющий отслежи�

вать события, происходящие с этими кнопками. Для этого нужен класс, реализующийинтерфейс ActionListener, в котором, как мы уже говорили, объявлен одинметод — actionPerformed(). Сигнатура этого метода выглядит следующим образом:

public void actionListener(ActionEvent event)

Page 5: 8354 Глава 8. Обработка событий Например, если пользователь щелкает на кнопке с надписью Yellow, вызывается

Общие сведения об обработке событий 353

Рис. 8.2. Панель с кнопками

Интерфейс ActionListener, использованный в нашем примере, не ограничиваетсяотслеживанием щелчков на кнопках. Он применяется во многих ситуациях.

• После двойного щелчка на пункте списка.

• После выбора пункта меню.

• После нажатия клавиши <Enter>, когда курсор находится в поле редактирования.

• По истечении заданного отрезка времени, отслеживаемого компонентом Timer.

Во всех случаях интерфейс ActionListener используется совершенно одинаково: ме+тод actionPerformed() — единственный метод в интерфейсе ActionListener —получает в качестве параметра объект типа ActionEvent. Этот объект несет в себе ин+формацию о событии.

Предположим, что мы хотим после щелчка на кнопке изменить цвет панели.Новый цвет нужно указать в классе обработчика.

class ColorAction implementsListener{ public ColorAction(Color c) { backgroundColor = c; } public void actionPerformed(ActionEvent event) { // Устанавливаем цвет панели. ... }

private Color backgroundColor;}

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

ColorAction yellowAction = new ColorACtion(Color.YELLOW);ColorAction blueAction = new ColorACtion(Color.BLUE);ColorAction redAction = new ColorAction(Color.RED);

yellowButton.addActionListener(yellowAction);blueButton.addActionListener(blueAction);redButton.addActionListener(redAction);

Page 6: 8354 Глава 8. Обработка событий Например, если пользователь щелкает на кнопке с надписью Yellow, вызывается

354 Глава 8. Обработка событий

Например, если пользователь щелкает на кнопке с надписью Yellow, вызывается ме�тод actionPerformed() объекта yellowAction. Поле экземпляра backgroundColorхранит значение Color.YELLOW, поэтому выполняющийся метод может установитьтребуемый цвет панели.

Остался один небольшой вопрос. Объект ColorAction не имеет доступа к перемен�ной panel. Эту проблему можно решить разными способами. Переменную panel можноуказать в конструкторе класса. Но удобнее сделать класс ColorAction внутренним по от�ношению к классу ButtonPanel. В этом случае его методы получат доступ к внешнимпеременным автоматически. (Более подробно о внутренних классах см. в главе 6.)

Мы будем применять второй способ. Ниже показан пример включения классаColorAction в класс ButtonPanel.

class ButtonPanel extends JPanel{ ... private class ColorAction implements ActionListener { ... public void actionPerformed(ActionEvent event) { setBackground(backgroundColor); // Т.е. outer.setBackground(...). } private Color backgroundColor; }}

Рассмотрим метод actionPerformed() более подробно. Класс ColorActionне имеет методов setBackground() и repaint(). Однако во внешнем классеButtonPanel эти методы есть. Они вызываются в объекте ButtonPanel, создающемобъекты внутреннего класса. (Напомним, что слово outer не является ключевымсловом языка Java. Мы применяем его в качестве интуитивно понятного символа дляобозначения ссылки на внешний класс в объекте внутреннего класса.)

Такая ситуация встречается довольно часто. Обработчики должны выполнятькакие�то действия, влияющие на другие объекты. Часто бывает очень удобным зара�нее поместить класс обработчика в состав другого класса, который данный обработ�чик должен модифицировать.

Листинг 8.1 содержит полный код программы, в которой реализуется обработкасобытий. Как только пользователь щелкает на какой�нибудь кнопке, соответствующийобработчик изменяет цвет панели.

Листинг 8.1. Содержимое файла ButtonTest.java

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

public class ButtonTest{ public static void main(String[] args) { ButtonFrame frame = new ButtonFrame();

Page 7: 8354 Глава 8. Обработка событий Например, если пользователь щелкает на кнопке с надписью Yellow, вызывается

Общие сведения об обработке событий 355

frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.setVisible(true); }}

/** Фрейм, содержащий панель с кнопками.*/class ButtonFrame extends JFrame{ public ButtonFrame() { setTitle("ButtonTest"); setSize(DEFAULT_WIDTH, DEFAULT_HEIGHT);

// Добавление панели к фрейму.

ButtonPanel panel = new ButtonPanel(); add(panel); }

public static final int DEFAULT_WIDTH = 300; public static final int DEFAULT_HEIGHT = 200;}

/** Панель с тремя кнопками.*/class ButtonPanel extends JPanel{ public ButtonPanel() { // Создание кнопок.

JButton yellowButton = new JButton("Yellow"); JButton blueButton = new JButton("Blue"); JButton redButton = new JButton("Red");

// Включение кнопок в состав панели.

add(yellowButton); add(blueButton); add(redButton);

// Создание обработчиков.

ColorAction yellowAction = new ColorAction(Color.YELLOW); ColorAction blueAction = new ColorAction(Color.BLUE); ColorAction redAction = new ColorAction(Color.RED);

// Связывание обработчиков с кнопками.

yellowButton.addActionListener(yellowAction); blueButton.addActionListener(blueAction); redButton.addActionListener(redAction); }

Page 8: 8354 Глава 8. Обработка событий Например, если пользователь щелкает на кнопке с надписью Yellow, вызывается

356 Глава 8. Обработка событий

/** Обработчик, изменяющий цвет панели. */ private class ColorAction implements ActionListener { public ColorAction(Color c) { backgroundColor = c; }

public void actionPerformed(ActionEvent event) { setBackground(backgroundColor); }

private Color backgroundColor; }}

javax.swing.JButton 1.2

• JButton(String label)

Создает кнопку. Строка, передаваемая в качестве параметра, может представ�лять собой текст или, начиная с версии JDK 1.3, HTML-выражение, например<HTML><B>OK</B></HTML>

Параметр label Текст на кнопке

• JButton(Icon icon)

Создает кнопку.

Параметр icon Пиктограмма на кнопке

• JButton(String label, Icon icon)

Создает кнопку.

Параметры label Текст на кнопкеicon Пиктограмма на кнопке

java.awt.Container 1.0

• Component add(Component c)

Добавляет компонент c в контейнер.

javax.swing.ImageIcon 1.2

• ImageIcon(String filename)

Создает пиктограмму, изображение которой хранится в файле. Это изображе�ние автоматически загружается в объект класса MediaTracker (см. главу 7).

Page 9: 8354 Глава 8. Обработка событий Например, если пользователь щелкает на кнопке с надписью Yellow, вызывается

Общие сведения об обработке событий 357

Использование внутренних классов в качестве обработчиков событийОтдельные программисты считают, будто внутренние классы, применяемые в каче�

стве обработчиков, замедляют выполнение программы. Попробуем разобраться в этом.Для каждого компонента пользовательского интерфейса совсем не нужен новый класс.В нашем примере все три кнопки делят между собой один класс�обработчик. Разумеется,каждой из них соответствует отдельный объект этого класса. Однако эти объекты неве�лики. Такой объет содержит обозначение цвета и ссылку на панель.

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

Приведем яркий пример, показывающий, как безымянные внутренние классыпозволяют упростить исходный код программы. Вернемся к листингу 8.1. Обратитевнимание на то, что обработка событий для всех кнопок совершенно одинакова и со�стоит из следующих операций.

1. Создание кнопки с соответствующей меткой.

2. Размещение кнопки на панели.

3. Создание обработчика, изменяющего цвет панели.

4. Связывание обработчика с объектом панели.

Чтобы упростить эту задачу, создадим вспомогательный метод.

void makeButton(String name, Color backgroundColor){ JButton button = new JButton(name); add(button); ColorAction action = new ColorAction(backgroundColor); button.addActionListener(action);}

Тогда конструктор класса ButtonPanel упрощается.

public ButtonPanel(){ makeButton("yellow", Color.YELLOW); makeButton("blue", Color.BLUE); makeButton("red", Color.RED);}

Однако возможности упрощения программы этим не исчерпываются. Отметим,что класс ColorAction нужен лишь один раз: в методе makeButton(). Следователь�но, его можно преобразовать в безымянный внутренний класс.

void makeButton(String name, final Color backgroundColor){ JButton button = new JButton(name); add(button); button.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent event) {

Page 10: 8354 Глава 8. Обработка событий Например, если пользователь щелкает на кнопке с надписью Yellow, вызывается

358 Глава 8. Обработка событий

setBackgroiund(backgroundColor); } });}

Код обработчика упростился. Метод actionPerformed() теперь просто ссылает�ся на параметр backgroundColor. (Как и все локальные переменные, доступные вовнутреннем классе, этот параметр должен быть терминальным.)

Явный конструктор теперь не нужен. Как мы уже видели в главе 6, механизм внут�реннего класса автоматически генерирует конструктор, размещающий в памяти все ло�кальные терминальные переменные, используемые в одном из методов этого класса.

На первый взгляд может показаться, что внутренние классы существенно усложняюткод программы. Однако если вы научитесь выделять главное, абстрагируясь на времяот остального кода, то легко освоите синтаксис внутренних классов. Рассмотримпример.

button.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent event)

{

setBackground(backgroundColor); } });

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

В пакете JDK 1.4 реализован механизм, позволяющий создавать обработчики событийбез программирования внутренних классов. Допустим, например, что приложение име+ет кнопку Load, обработчик событий которой содержит всего один вызов метода:

frame.loadData();

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

loadButton.addActionListener(new ActionListener()

{ public void actionPerformed(ActionEvent event)

{

frame.loadData();

}

};

Однако класс EventHandler может создать такой обработчик автоматически, вызвавметод create().

EventHandler.create(ActionListener.class, frame, "loadData")

Page 11: 8354 Глава 8. Обработка событий Например, если пользователь щелкает на кнопке с надписью Yellow, вызывается

Общие сведения об обработке событий 359

Разумеется, обработчик по+прежнему необходимо связать с источником событий.

loadButton.addActionListener((ActionListener) EventHandler.create(ActionListener.class, frame, "loadData"));

Приведение типа необходимо, поскольку метод create() возвращает объект Object.Возможно, в последующих версиях JDK будут использованы универсальные типы, чтоупростит работу с данным методом.

Если обработчик событий вызывает метод с одним параметром, производным от об+работчика событий, можно воспользоваться другой формой метода create().Например, вызов

EventHandler.create(ActionListener.class, frame, "loadData", "source.text")

эквивалентен конструкции

new ActionListener()

{

public void actionPerformed(ActionEvent event)

{

frame.loadData(

((JTextField)event.getSource()).getText());

}

}

Обратите внимание на то, что обработчик событий превращает имена свойствsource и text в вызовы методов getSource() и getText(), используя соглашения,принятые для компонентов JavaBeans. (Более полная информация о свойствах ком+понентов JavaBeans содержится во втором томе.)

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

Превращение компонентов в обработчики событийПрограммист может совершенно свободно назначать обработчиком события, свя�

занного с кнопкой, любой объект класса, реализующего интерфейс ActionListener.В нашем примере используются объекты нового класса, созданного специально длявыполнения требуемых действий. Многие поступают иначе и добавляют методactionPerformed() в существующий класс. Разумеется, этот класс также долженреализовывать интерфейс ActionListener. Например, можно легко превратитьв обработчик класс ButtonPanel.

class ButtonPanel extends JPanel implements ActionListener{ ... public void actionPerformed(ActionEvents event) { // Устанавливает цвет фона.

Page 12: 8354 Глава 8. Обработка событий Например, если пользователь щелкает на кнопке с надписью Yellow, вызывается

360 Глава 8. Обработка событий

... }}

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

yellowButton.addActionListener(this);blueButton.addActionListener(this);redButton.addActionListener(this);

Отметим, что теперь три кнопки больше не имеют индивидуальных обработчиков.У них есть общий обработчик — панель, на которой они расположены. Следовательно,метод actionPerformed() должен как�то распознавать, на какой кнопке произведенщелчок.

Источник любого события распознается с помощью метода getSource() классаEventObject — суперкласса для всех событий. Источник события — это объект, гене�рирующий событие и уведомляющий об этом обработчик.

Object source = event.getSource();

После вызова данного метода actionPerformed() проверяет, какая из кнопок былаисточником события.

if (source == yellowButton) ...else if (source == blueButton) ...else if (source == redButton) ...

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

Некоторые программисты пользуются другим способом распознавания источника.В классе ActionEvent есть метод getActionCommand(), который возвращает ко�мандную строку, связанную с данным действием. Для кнопки он по умолчанию возвра+щает ее метку. Если вы предпочитаете такой способ, то метод actionPerformed()должен содержать следующий фрагмент кода (или подобный ему):

String command = event.getActionCommand();if (command.equals("Yellow");) ...;else if (command.equals("Blue")) ...;else if (command.equals("Red")) ...;

Тем не менее полагаться на метки кнопок небезопасно. Легко сделать ошибку и на+звать кнопку "Gray", а затем попытаться проверить источник события с помощьюоператора

if (command.equals("Grey")) ...

Кроме того, при интернационализации вашей программы придется для каждого отдель+ного языка изменять название кнопки. Например, в немецком языке кнопки будут носитьназвания “Gelb”, “Blau” и “Rot”. В каждом из этих случаев программист будет вынужденизменять как метку кнопки, так и параметр метода actionPerformed().

java.util.EventObject 1.1

• Object getSource() Возвращает ссылку на объект, являющийся источником события.

Page 13: 8354 Глава 8. Обработка событий Например, если пользователь щелкает на кнопке с надписью Yellow, вызывается

Общие сведения об обработке событий 361

java.awt.event.ActionEvent 1.1

• String getActionCommand()

Возвращает командную строку, связанную с событием. Если событие произош�ло с кнопкой, командной строкой является метка строки (заметьте, что онаможет быть изменена с помощью метода setActionCommand()).

java.beans.EventHandler 1.4

• static Object create(Class listenerInterface, Object target, String action)• static Object create(Class listenerInterface, Object target, String action, String eventProperty)• static Object create(Class listenerInterface, Object target, String action, String eventProperty, String listenerMethod)

Эти методы создают proxy-объект, реализующий заданный интерфейс. В ре�зультате либо указанный метод, либо все методы интерфейса выполняютзаданное действие с целевым объектом.

Параметр action может быть именем метода или свойством объекта. Если этосвойство — выполняется метод set...(). Например, если параметр actionимеет значение text, оно превращается в вызов метода setText().

Свойство события состоит из одного или нескольких имен свойств, разделенныхточками. Первое свойство задает считывание параметра метода из обработчика,второе свойство — считывание результирующего объекта и т.д. Например, свой�ство "source.text" превращается в вызовы методов getSource() и getText().

Пример: изменение стиляПо умолчанию программы, созданные с помощью пакета Swing, используют стиль

Metal. Изменить стиль можно двумя способами. В подкаталоге JDK jre/lib можно пре�дусмотреть файл swing.properties и задать в нем с помощью свойстваswing.defaultlaf имя класса, определяющего нужный стиль, например:

swing.defaultlaf=com.sun.java.swing.plaf.motif.MotifLookAndFeel

Отметим, что стиль Metal описан в пакете javax.swing. Другие стили описаныв пакете com.sun.java. Они не являются обязательным атрибутом каждой реализа�ции языка Java. В данный момент по соображениям, связанным с авторским правом,пакеты стилей для операционных систем Windows и Mac поставляются только с вер�сиями JDK, ориентированными на эти системы.

Поскольку строки, начинающиеся символом #, в файлах, описывающих свойства сти+лей, игнорируются, в файле swing.properties можно предусмотреть несколькостилей, “закомментировав” ненужные.

#swing.defaulflaf=javax.swing.plaf.metal.MetalLookAndFeelswing.defaultlaf=com.sun.java.swing.plaf.motif.MotifLookAndFeel#swing.defaultlaf=com.sun.java.swing.plaf.windows.WindowsLookAndFeel

Чтобы изменить стиль интерфейса программы, ее нужно запустить заново. Програм+ма прочитывает файл swing.properties лишь один раз, при запуске.

Page 14: 8354 Глава 8. Обработка событий Например, если пользователь щелкает на кнопке с надписью Yellow, вызывается

362 Глава 8. Обработка событий

Для того чтобы изменять стиль динамически, вызовите методUIManager.setLookAndFeel() и задайте ему имя требуемого стиля. Затем вызовитестатический метод SwingUtilities.updateComponentTreeUI() и обновите с егопомощью весь набор компонентов. Этому методу достаточно передать один компо�нент, остальные он найдет автоматически. Если нужный стиль не будет обнаруженили произойдет ошибка при его загрузке, метод UIManager.setLookAndFeel можетсгенерировать различные исключения. Как обычно, вы можете не уделять вниманияобработке исключений, поскольку эта тема будет полностью освещена в главе 11.

Рассмотрим пример, в котором задается стиль Motif графического интерфейсапрограммы.

String plaf = "com.sun.java.swing.plaf.motif.MotifLookAndFeel";try{ UIManager.setLookAndFeel(plaf); SwingUtilities.updateComponentTreeUI(panel);}catch(Exception e); { e.printStackTrace(); }

Для того чтобы пронумеровать инсталлированные реализации стилей, надоиспользовать следующее выражение:

UIManager.LookAndFeelInfo[] infos = UIManager.getInstalledLookAndFeels();

После этого вы можете с помощью приведенного ниже кода получить имя каждогостиля и реализующего его класса.

String name = infos[i].getName();String className = infos[i].getClassName();

В листинге 8.2 приведена законченная программа, демонстрирующая переключениестилей (рис. 8.3). Эта программа очень похожа на ту, которая приведена в листинге 8.1.Следуя советам, изложенным выше, для того, чтобы задать действия с кнопками, т.е. пе�реключение стилей, мы применили вспомогательный метод makeButton() и безымян�ный внутренний класс.

Рис. 8.3. Переключение стилей

Page 15: 8354 Глава 8. Обработка событий Например, если пользователь щелкает на кнопке с надписью Yellow, вызывается

Общие сведения об обработке событий 363

У этой программы есть одно преимущество. Метод actionPerformed() во внут�реннем классе�обработчике должен передать методу updateComponentTreeUI()ссылку this на объект внешнего класса PlafPanel. Напомним, что ссылка thisна объект внешнего класса должна сопровождаться префиксом — именем этого класса.

SwingUtilities.updateComponentTreeUI(PlafPanel.this)

Листинг 8.2. Содержимое файла PlafTest.java

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

public class PlafTest{ public static void main(String[] args) { PlafFrame frame = new PlafFrame(); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.setVisible(true); }}

/** Фрейм с панелью, содержащей кнопки. С помощью кнопок изменяется стиль окна.*/class PlafFrame extends JFrame{ public PlafFrame() { setTitle("PlafTest"); setSize(DEFAULT_WIDTH, DEFAULT_HEIGHT);

// Добавление панели к фрейму.

PlafPanel panel = new PlafPanel(); add(panel); }

public static final int DEFAULT_WIDTH = 300; public static final int DEFAULT_HEIGHT = 200;}

/** Панель с кнопками, предназначенными для изменения стиля*/class PlafPanel extends JPanel{ public PlafPanel() { UIManager.LookAndFeelInfo[] infos = UIManager.getInstalledLookAndFeels(); for (UIManager.LookAndFeelInfo info : infos) makeButton(info.getName(), info.getClassName()); }

Page 16: 8354 Глава 8. Обработка событий Например, если пользователь щелкает на кнопке с надписью Yellow, вызывается

364 Глава 8. Обработка событий

/** Создает кнопки, изменяющие стиль отображения. @param name Имя кнопки @param plafName Имя класса, который описывает стиль */ void makeButton(String name, final String plafName) { // Включение кнопки в состав панели.

JButton button = new JButton(name); add(button);

// Связывание обработчика событий с кнопкой.

button.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent event) { // Действие: переключение на другой стиль. try { UIManager.setLookAndFeel(plafName); SwingUtilities.updateComponentTreeUI (PlafPanel.this); } catch(Exception e) { e.printStackTrace(); } } }); }}

javax.swing.UIManager 1.2

• static UIManager.LookAndFeelInfo[] getInstalledLookAndFeels()

Получение массива объектов, описывающих реализации инсталлированныхстилей.

• static setLookAndFeel(String className)

Установка текущего стиля.

Параметр className Имя, класса, реализующего стиль

javax.swing.UIManager.LookAndFeelInfo 1.2

• String getName()

Возвращает имя стиля.

• String getClassName()

Возвращает имя класса, реализующего стиль.

Page 17: 8354 Глава 8. Обработка событий Например, если пользователь щелкает на кнопке с надписью Yellow, вызывается

Общие сведения об обработке событий 365

Пример: обработка событий, связанных с окнамиНе все события обрабатываются так же просто, как события, связанные с кнопка�

ми. Рассмотрим более сложный случай, о котором мы уже упоминали в главе 7. До по�явления EXIT_ON_CLOSE в пакете JDK 1.3 программисты должны были вручную реа�лизовывать выход из программы при закрытии главного фрейма. Если программапредставляет собой профессиональное приложение, она обязательно должна завер�шаться корректном, и пользователь должен иметь гарантию, что он не потеряет ре�зультаты выполненной работы. Например, если пользователь закрыл фрейм, можновывести на экран диалоговое окно и предупредить его о необходимости сохранитьрезультаты работы и только после его согласия завершить выполнение приложения.

Если пользователь пытается закрыть фрейм, объект JFrame становится источни�ком события WindowEvent. Для того чтобы обработать это событие, нужен соответ�ствующий объект, который следует зарегистрировать известным вам образом.

WindowListener listener = ...;frame.addWindowListener(listener);

В данном случае обработчик должен быть экземпляром класса, реализующего ин�терфейс WindowListener. В интерфейсе WindowListener есть семь методов.Фрейм вызывает их в ответ на семь разных событий, которые могут произойти с ок�ном. Их имена отражают их назначение. Исключением, может быть, является словоiconified, что в системе Windows означает “свернутое” окно. Вот как выглядит полныйинтерфейс WindowsListener.

public interface WindowListener{ void windowOpened(WindowEvent e); void windowClosing(WindowEvent e); void windowClosed(WindowEvent e); void windowIconified(WindowEvent e); void windowDeiconified(WindowEvent e); void windowActivated(WindowEvent e); void windowDeactivated(WindowEvent e);}

Если нужно проверить, было ли окно увеличено до максимальных размеров, следуетиспользовать обработчик типа WindowStateListener. Подробную информациюо нем можно найти в документации на API.

Как обычно, любой класс, реализующий какой�либо интерфейс, должен реализо�вывать все его методы. В данном случае это означает создание тела семи методов.Напомним, что нас интересует только один из них — метод windowClosing().

Разумеется, можно определить класс, реализующий интерфейс WindowListener,добавить в метод windowClosing() вызов System.exit(0) и написать пустыефункции для остальных шести методов.

class Terminator implements WindowListener{ public void windowClosing(WindowEvent e) { System.exit(0); }

Page 18: 8354 Глава 8. Обработка событий Например, если пользователь щелкает на кнопке с надписью Yellow, вызывается

366 Глава 8. Обработка событий

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) {}}

Классы�адаптерыСоздавать код для шести методов, которые ничего не делают, — неблагодарное занятие.

Чтобы упростить нашу задачу, для каждого из интерфейсов обработчиков, в котором со�держится несколько методов, создается класс�адаптер, реализующий все эти методы, при�чем тело их пусто. Например, класс WindowAdapter содержит семь методов, не выпол�няющих никаких операций. Следовательно, класс�адаптер автоматически удовлетвориттребованиям, предъявляемым к реализации соответствующего интерфейса. Программистможет расширить класс�адаптер и уточнить нужные реакции на некоторые и даже на всетипы событий, происходящих в интерфейсе. (Заметьте, что интерфейс ActionListenerсодержит только один метод, поэтому для него класс�адаптер не нужен.)

Рассмотрим применение адаптера для окна. Расширим класс WindowAdapter,унаследовав шесть пустых методов и заменив метод windowClosing().

class Terminator extends WindowAdapter{ public void windowClosing(WindowEvent e) { System.exit(0); }}

Теперь объект класса Terminator можно зарегистрировать как обработчиксобытий.

WindowListener listener = new Terminator();frame.addWindowListener(listener);

С этого момента, как только фрейм сгенерирует событие, он передаст его обра�ботчик, вызвав один из его семи методов (рис. 8.4). Шесть из этих методов ничегоне делают, а метод windowClosing() выполняет вызов System.exit(0), завершаяработу приложения.

Если, расширяя класс адаптера, вы неправильно укажете имя метода, компиляторне сообщит об ошибке. Например, если в подклассе WindowAdapter вы создадитеметод windowIsClosing(), то получите новый класс, содержащий восемь методов,причем метод windowClosing() не будет выполнять никаких действий.

Создание класса обработчика, расширяющего класс WindowAdapter, — несомнен�ный шаг вперед, но можно достичь еще большего. Объекту обработчика не обяза�тельно давать имя, достаточно написать следующее выражение:

frame.addWindowListener(new Terminator());

Page 19: 8354 Глава 8. Обработка событий Например, если пользователь щелкает на кнопке с надписью Yellow, вызывается

Общие сведения об обработке событий 367

Terminator

MyFrame

new

addActionListener

windowClosing

windowClosed

Рис. 8.4. Обработка событий, связанных с окном

Но и это еще не все! Можно использовать в качестве обработчика безымянныйвнутренний класс фрейма.

frame.addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) { System.exit(0); } } );

В этом фрагменте кода выполняются следующие действия.

• Определяется безымянный класс, расширяющий класс WindowAdapter.

• В этом безымянном классе переопределяется метод windowClosing(). Как ипрежде, данный метод отвечает за выход из программы.

• Новый класс наследует от класса WindowAdapter шесть методов, не выпол�няющих никаких действий.

• Создается объект класса. Этот объект также не имеет имени.

• Безымянный объект передается методу addWindowListener().

Page 20: 8354 Глава 8. Обработка событий Например, если пользователь щелкает на кнопке с надписью Yellow, вызывается

368 Глава 8. Обработка событий

java.awt.event.WindowListener 1.1

• void windowOpened(WindowEvent e)

Этот метод вызывается после открытия окна.

• void windowClosing(WindowEvent e)

Этот метод вызывается, когда пользователь отдает оконному диспетчерукоманду закрыть окно. Обратите внимание на то, что окно закрывается, толькоесли вызван метод hide() или dispose().

• void windowClosed(WindowEvent e)

Этот метод вызывается после закрытия окна.

• void windowIconified(WindowEvent e)

Этот метод вызывается после свертывания окна.

• void windowDeiconified(WindowEvent e)

Этот метод вызывается после развертывания окна.

• void windowActivated(WindowEvent e)

Этот метод вызывается после активизации окна. Активным может быть толькофрейм или диалоговое окно. Обычно оконный диспетчер специально отмечаетактивное окно, например подсвечивает его заголовок.

• void windowDeactivated(WindowEvent e)

Этот метод вызывается после того, как окно стало неактивным.

java.awt.event.WindowStateListener 1.4

• void windowStateChanged(WindowEvent event) 1.4

Этот метод вызывается после того, как окно было увеличено до максимальныхразмеров, свернуто или если были восстановлены его нормальные размеры.

java.awt.event.WindowEvent 1.1

• int getNewState() 1.4• int getOldState() 1.4

Эти методы возвращают новое или старое состояние окна при изменении егосостояния. Возвращаемое целое число может принимать одно из следующихзначений:

Frame.NORMAL Frame.ICONIFIED Frame.MAXIMIZED_HORIZ Frame. MAXIMIZED_VERT Frame.MAXIMIZED_BOTH

Page 21: 8354 Глава 8. Обработка событий Например, если пользователь щелкает на кнопке с надписью Yellow, вызывается

Иерархия событий библиотеки AWT 369

Иерархия событий библиотеки AWTПосле обсуждения механизма обработки событий перейдем к более общим вопро�

сам. Как уже указывалось ранее, в языке Java при обработке событий используетсяобъектный подход. Все события являются потомками класса EventObject из пакетаjava.util. (Общий суперкласс не назвали именем Event, так как это имя носилкласс событий в старой модели. Хотя старая модель в настоящее время не рекомендо�вана к использованию, ее классы все еще являются частью библиотеки Java.)

Класс EventObject имеет подкласс AWTEvent, являющийся родительским по от�ношению ко всем классам событий из пакета AWT. На рис. 8.5 показана диаграмманаследования событий из пакета AWT.

EventObject

AWT Event

ActionEvent

AdjustmentEvent

ComponentEvent

Item Event

FocusEvent

PaintEvent

WindowEvent

KeyEvent

MouseEvent

InputEvent

MouseWheelEvent

Рис. 8.5. Диаграмма наследования классов событий AWT

Page 22: 8354 Глава 8. Обработка событий Например, если пользователь щелкает на кнопке с надписью Yellow, вызывается

370 Глава 8. Обработка событий

Некоторые компоненты пакета Swing генерируют другие объекты событий и не�посредственно расширяют класс EventObject.

Объекты событий инкапсулируют информацию, которую источник события передаетобработчику. По мере необходимости объекты событий можно проанализировать с по�мощью методов getSource() и getActionSource().

Некоторые из классов событий AWT не имеют практической ценности. Например, па�кет AWT помещает объекты PaintEvent в очередь событий, однако эти объекты обра�ботчику не передаются. Программисты должны сами переопределять методpaintComponent(), чтобы управлять перерисовкой. AWT также генерирует ряд собы�тий, интересующих лишь системных программистов; они могут использоваться для под�держки иероглифических языков, автоматизации тестирования роботов и выполнениядругих самых разнообразных задач. В этой книге мы не будем обсуждать специализиро�ванные типы событий, а также обойдем вниманием события, связанные с устаревшимии не используемыми сейчас компонентами AWT.

Ниже перечислены часто применяемые события AWT.

ActionEvent KeyEventAdjustmentEvent MouseEventFocusEvent MouseWheelEventItemEvent WindowEvent

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

Пакет javax.swing.event содержит дополнительные события, характерные длякомпонентов пакета Swing. Некоторые из них будут описаны в следующей главе.

Интерфейсы, используемые для создания обработчиков событий, имеют следую�щие имена.

ActionListener MouseMotionListenerAdjustmentListener MouseWheelListenerFocusListener WindowListenerItemListener WindowFocusListenerKeyListener WindowStateListenerMouseListener

С интерфейсами ActionListener и WindowListener вы уже знакомы. Они ис�пользовались в рассмотренных выше примерах.

Несмотря на то что пакет javax.swing.event содержит очень много интерфей�сов, специфичных для компонентов из пакета Swing, в нем все еще используются ос�новные интерфейсы из библиотеки AWT. В частности, они применяются для обра�ботки событий общего характера.

Семь интерфейсов из пакета AWT, имеющих несколько методов, сопровождаютсяклассами�адаптерами, реализующими пустые методы. (Оставшиеся интерфейсы содер�жат только по одному методу, поэтому для них классы�адаптеры не нужны.) Ниже пере�числены имена классов�адаптеров.

FocusAdapter MouseMotionAdapterKeyAdapter WindowAdapterMouseAdapter

Page 23: 8354 Глава 8. Обработка событий Например, если пользователь щелкает на кнопке с надписью Yellow, вызывается

Семантические и низкоуровневые события в библиотеке AWT 371

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

Программисты, работавшие на языках С и С++, могут поинтересоваться: зачем дляобработки событий нужны все эти хитросплетения объектов, методов и интерфей+сов? Ведь для создания графического пользовательского интерфейса на языке С++можно использовать обратные вызовы с обобщенными указателями или обработчи+ками. Однако в языке Java этот механизм не работает. Модель обработки событийв языке Java строго типизирована: компилятор следит, чтобы события передавалисьтолько объектам, предназначенным для их обработки.

Семантические и низкоуровневые события в библиотеке AWTВ AWT события разделены на низкоуровневые (low�level) и семантические (semantic).

Семантические события описывают действия пользователя, например щелчок на кноп�ке; следовательно, событие ActionEvent является семантическим. Низкоуровневыесобытия обеспечивают возможность таких действий. Если пользователь щелкнулна кнопке, значит, он нажал кнопку мыши, возможно, переместил курсор по экрануи отпустил кнопку мыши (причем курсор мыши должен находиться в пределах кнопки).Семантические события могут быть также сгенерированы путем нажатия клавиш, на�пример для перемещения по кнопкам с помощью клавиши <Tab>. Аналогично, дейст�вие с полосой прокрутки относится к семантическим событиям, в то время как перетас�кивание объекта с помощью мыши относится к низкоуровневым.

Из классов пакета java.awt.event, описывающих семантические события, наи�более часто используются следующие.

• ActionEvent (щелчок на кнопке, выбор пункта меню, выбор пункта в списке,нажатие клавиши <Enter> при работе с полм редактирования).

• AdjustmentEvent (перемещение ползунка на полосе прокрутки).

• ItemEvent (выбор положения переключателя опций или пункта в списке).

Из низкоуровневых событий наиболее часто встречаются перечисленные ниже.

• KeyEvent (нажатие или отпускание клавиши).

• MouseEvent (нажатие, отпускание кнопки мыши, перемещение курсора мыши,перетаскивание курсора, т.е. его перемещение при нажатой кнопке).

• MouseWheelEvent (вращение колесика мыши).

• FocusEvent (получение или потеря фокуса ввода.).

• WindowEvent (изменение состояния окна).

В табл. 8.1 приведены наиболее важные интерфейсы обработчиков, события и ис�точники событий.

Page 24: 8354 Глава 8. Обработка событий Например, если пользователь щелкает на кнопке с надписью Yellow, вызывается

372 Глава 8. Обработка событий

Таблица 8.1. Объекты и интерфейсы, участвующие в обработке событий

Интерфейс Методы Параметр/Методы доступа

Источниксобытия

ActionListener actionPerformed ActionEvent AbstractButton

• getActionCommand JComboBox

• getModifiers JTextField

Timer

AdjustmentListener adjustmentValue-Changed

AdjustmentEvent JScrollbar

• getAdjustable

• getAdjustmentType

• getValue

ItemListener itemStateChanged ItemEvent AbstractButton

• getItem JComboBox

• getItemSelectable

• getStateChange

FocusListener focusGained FocusEvent ComponentfocusLost • isTemporary

KeyListener keyPressed KeyEvent ComponentkeyReleased • getKeyCharkeyTyped • getKeyCode

• getKeyModifiersText

• getKeyText

• isActionKey

MouseListener mousePressed MouseEvent ComponentmouseReleased • getClickCountmouseEntered • getXmouseExited • getYmouseClicked • getPoint

• translatePoint

MouseMotionListener mouseDragged MouseEvent ComponentmouseMoved

MouseWheelListener mouseWheelMoved MouseWheelEvent Component

• getWheelRotation

• getScrollAmount

WindowListener windowClosing WindowEvent WindowwindowOpened • getWindowwindowIconified

windowDeiconified

windowClosed

windowActivated

windowDeactivated

WindowFocusListener windowGainedFocus WindowEvent WindowwindowLostFocus • getOppositeWindow

WindowStateListener windowStateChanged WindowEvent Window

• getOldState

• getNewState

Page 25: 8354 Глава 8. Обработка событий Например, если пользователь щелкает на кнопке с надписью Yellow, вызывается

Семантические и низкоуровневые события в библиотеке AWT 373

Взаимосвязь основных понятий, используемых при обработке событий Рассмотрим еще раз механизм делегирования событий, чтобы убедиться, что вы

правильно понимаете отношения между классами событий, интерфейсами обработ�чиков и классами�адаптерами.

Источники событий — это компоненты пользовательского интерфейса, окна и меню.Операционная система извещает источник события о поведении интересующих его объ�ектов, например о перемещении мыши или нажатии клавиши. Источник события описы�вает природу события в объекте события. Кроме того, он хранит набор обработчиков —объектов, которые следует вызвать при наступлении определенного события (рис. 8.6).Затем источник события вызывает соответствующий метод, объявленный в интерфейсеобработчика, для того, чтобы доставить информацию о событии требуемому объекту. С этойцелью источник передает объект события методу обработчика. Обработчик анализируетобъект события, извлекая из него информацию о выполненном действии. Например, что�бы обнаружить источник события, можно вызвать метод getSource(), а для того чтобыопределить местонахождение курсора мыши — методы getX() и getY().

<<множество из одного или нескольких элементов>>

1... *

<<реализует>>

Интерфейсобработчика

Источниксобытия

Обработчиксобытия

Рис. 8.6. Взаимосвязь между источниками событий и обработчиками

Обратите внимание на то, что интерфейсы MouseListener и MouseMotion-Listener отличаются друг от друга. Это сделано для повышения эффективности —есть много событий, связанных с мышью, которые не требуют реакции, напримербесцельное перемещение курсора мыши по экрану. Поэтому часто обработчик дол�жен заботиться лишь о нажатии кнопки мыши и не обращать внимания на ее произ�вольные перемещения.

Все низкоуровневые события являются потомками класса ComponentEvent. Этоткласс содержит метод getComponent(), сообщающий о компоненте, породившемданное событие. Этот метод можно использовать вместо метода getSource().Он возвращает то же значение, что и метод getSource(), но при этом возвращаемоезначение приводится к типу Component. Например, если при вводе текста произой�дет событие, связанное с нажатием клавиши, метод getComponent() вернет ссылкуна соответствующее поле ввода.

java.awt.event.ComponentEvent 1.0

• Component getComponent()

Возвращает ссылку на компонент, являющийся источником события. Точно такаяже ссылка возвращается при выполнении выражения (Component)getSources().

Page 26: 8354 Глава 8. Обработка событий Например, если пользователь щелкает на кнопке с надписью Yellow, вызывается

374 Глава 8. Обработка событий

Типы низкоуровневых событийВ этом разделе мы детально обсудим события, не связанные с конкретными компо�

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

События, связанные с клавиатуройНажимая клавишу, пользователь порождает событие KEY_PRESSED, а отпуская

ее — KEY_RELEASED. Оба этих события принадлежат классу KeyEvent. Их можноперехватить с помощью методов keyPressed() и keyReleased() любого класса,реализующего интерфейс KeyListener. В частности, эти методы можно использо�вать для отслеживания ошибочно нажатых клавиш. Третий метод, keyTyped, пред�ставляет собой сочетание двух предыдущих: он сообщает, какой символ соответствуетнажатой клавише.

Перед тем как переходить к рассмотрению конкретного примера, уточним терми�нологию. В языке Java существует различие между символами (symbols) и виртуальны�ми кодами клавиш (virtual key codes). Виртуальные коды клавиш обозначаются с помо�щью префикса VK_, например VK_A или VK_SHIFT. Они соответствуют клавишам кла�виатуры; так, код VK_A означает клавишу <A>. Виртуальный код соответствуетклавише, прописные и строчные буквы не различаются.

Виртуальные коды клавиш соответствуют их сканкодам.

Предположим, что пользователь ввел прописную букву “А”, нажав клавишу <Shift>и клавишу <A>. При этом происходит пять событий.

1. Нажатие клавиши <Shift> (вызывается метод keyPressed() с параметромVK_SHIFT).

2. Нажатие клавиши <A> (вызывается метод keyPressed() с параметром VK_A).

3. Ввод буквы “A” (вызывается метод keyTyped() с параметром “A”).

4. Отпускание клавиши <A> (вызывается метод keyReleased() с параметромVK_A).

5. Отпускание клавиши <Shift> (вызывается метод keyReleased() с параметромVK_SHIFT).

Если же пользователь вводит строчную букву “а”, нажимая клавишу <A>, произой�дут только три события.

1. Нажатие клавиши <A> (вызывается метод keyPressed() с параметром VK_A).

2. Ввод буквы “а” (вызывается метод keyTyped() с параметром “A”).

3. Отпускание клавиши <A> (вызывается метод keyReleased() с параметром VK_A).

Итак, процедура keyTyped() сообщает, какой символ был введен (“A” или “a”), в товремя как методы keyPressed() и keyReleased() сообщают, какие клавиши былинажаты.

Page 27: 8354 Глава 8. Обработка событий Например, если пользователь щелкает на кнопке с надписью Yellow, вызывается

Типы низкоуровневых событий 375

При работе с методами keyPressed и keyReleased нужно сначала проверить кодклавиши.

public void keyPressed(KeyEvent event){ int keyCode = event.getKeyCode(); ...}

Код клавиши равен одной из следующих констант (их имена вполне очевидны).Эти константы определены в классе KeyEvent.

VK_A . . . VK_ZVK_0 . . . VK_9VK_COMMA, VK_PERIOD, VK_SLASH, VK_SEMICOLON, VK_EQUALSVK_OPEN_BRACKET, VK_BACK_SLASH, VK_CLOSE_BRACKETVK_BACK_QUOTE, VK_QUOTEVK_GREATER, VK_LESS, VK_UNDERSCORE, VK_MINUSVK_AMPERSAND, VK_ASTERISK, VK_AT, VK_BRACELEFT, VK_BRACERIGHTVK_LEFT_PARENTHESIS, VK_RIGHT_PARENTHESISVK_CIRCUMFLEX, VK_COLON, VK_NUMBER_SIGN, VK_QUOTEDBLVK_EXCLAMATION_MARK, VK_INVERTED_EXCLAMATION_MARKVK_DEAD_ABOVEDOT, VK_DEAD_ABOVERING, VK_DEAD_ACUTEVK_DEAD_BREVEVK_DEAD_CARON, VK_DEAD_CEDILLA, VK_DEAD_CIRCUMFLEXVK_DEAD_DIAERESISVK_DEAD_DOUBLEACUTE, VK_DEAD_GRAVE, VK_DEAD_IOTA, VK_DEAD_MACRONVK_DEAD_OGONEK, VK_DEAD_SEMIVOICED_SOUND, VK_DEAD_TILDE,VK_DEAD_VOICED_SOUNDVK_DOLLAR, VK_EURO_SIGNVK_SPACE, VK_ENTER, VK_BACK_SPACE, VK_TAB, VK_ESCAPEVK_SHIFT, VK_CONTROL, VK_ALT, VK_ALT_GRAPH, VK_METAVK_NUM_LOCK, VK_SCROLL_LOCK, VK_CAPS_LOCKVK_PAUSE, VK_PRINTSCREENVK_PAGE_UP, VK_PAGE_DOWN, VK_END, VK_HOME, VK_LEFT, VK_UP, VK_RIGHT,VK_DOWNVK_F1 . . .VK_F24VK_NUMPAD0 . . . VK_NUMPAD9VK_KP_DOWN, VK_KP_LEFT, VK_KP_RIGHT, VK_KP_UPVK_MULTIPLY, VK_ADD, VK_SEPARATER [sic], VK_SUBTRACT, VK_DECIMAL,VK_DIVIDEVK_DELETE, VK_INSERTVK_HELP, VK_CANCEL, VK_CLEAR, VK_FINALVK_CONVERT, VK_NONCONVERT, VK_ACCEPT, VK_MODECHANGEVK_AGAIN, VK_ALPHANUMERIC, VK_CODE_INPUT, VK_COMPOSE, VK_PROPSVK_STOPVK_ALL_CANDIDATES, VK_PREVIOUS_CANDIDATEVK_COPY, VK_CUT, VK_PASTE, VK_UNDOVK_FULL_WIDTH, VK_HALF_WIDTHVK_HIRAGANA, VK_KATAKANA, VK_ROMAN_CHARACTERSVK_KANA, VK_KANJIVK_JAPANESE_HIRAGANA, VK_JAPANESE_KATAKANA, VK_JAPANESE_ROMANVK_WINDOWS, VK_CONTEXT_MENUVK_UNDEFINED

Page 28: 8354 Глава 8. Обработка событий Например, если пользователь щелкает на кнопке с надписью Yellow, вызывается

376 Глава 8. Обработка событий

Чтобы определить текущее состояние клавиш <Shift>, <Control>, <Alt> и <Meta>,можно, конечно, отслеживать коды VK_SHIFT, VK_CONTROL и VK_META, но это оченьсложно. Достаточно использовать методы isShiftDown(), isControlDown(),isAltDown() и isMetaDown().

Например, следующий фрагмент кода проверяет, нажимается ли клавиша со стрел�кой вправо при нажатой клавише <Shift>.

public void keyPressed(KeyEvent event){ int keyCode == event.getKeyCode(); if (keyCode == keyEvent.VK_RIGTH && event.isShiftDown()) { ... }}

В методе keyTyped() вызывается метод getKeyChar(), позволяющий опреде�лить, какой символ был введен.

Метод keyTyped() можно применять не для всех клавиш. Он предназначен лишь дляотслеживания таких клавиш, которые генерируют символ в кодировке Unicode. Чтобыпроверять действия с управляющими клавишами, например вызывающими переме+щение курсора, надо использовать метод keyPressed().

Программа, представленная в листинге 8.3, иллюстрирует обработку нажатия кла�виш. Результат ее работы показан на рис. 8.7. Программа представляет собой простуюреализацию игрушки Etch�A�Sketch и позволяет создавать простые эскизы.

Перо можно перемещать вверх, вниз, влево и вправо, пользуясь клавишами сострелками. Если при этом нажать клавишу <Shift>, перо переместится на большее рас�стояние. Тот, кто имеет опыт работы с редактором vi, может переназначить клавишии для перемещения пера по экрану использовать буквы нижнего регистра: “h”, “j”, “k”и “l”. Те же буквы, но верхнего регистра (“H”, “J”, “K” и “L”), перемещают перо набольшее расстояние. Отслеживание состояния клавиш осуществляется с помощьюметода keyPressed(), а получение символов — посредством метода keyTyped().

Рис. 8.7. Программа рисования

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

Page 29: 8354 Глава 8. Обработка событий Например, если пользователь щелкает на кнопке с надписью Yellow, вызывается

Типы низкоуровневых событий 377

Листинг 8.3. Содержимое файла Sketch.java

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

public class Sketch{ public static void main(String[] args) { SketchFrame frame = new SketchFrame(); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.setVisible(true); }}

/** Фрейм с панелью для рисования фигур.*/class SketchFrame extends JFrame{ public SketchFrame() { setTitle("Sketch"); setSize(DEFAULT_WIDTH, DEFAULT_HEIGHT);

// Добавление панели к фрейму.

SketchPanel panel = new SketchPanel(); add(panel); }

public static final int DEFAULT_WIDTH = 300; public static final int DEFAULT_HEIGHT = 200;}

/** Панель для рисования с помощью клавиатуры.*/class SketchPanel extends JPanel{ public SketchPanel() { last = new Point2D.Double(100, 100); lines = new ArrayList<Line2D>(); KeyHandler listener = new KeyHandler(); addKeyListener(listener); setFocusable(true); }

/** Добавление нового отрезка к рисунку. @param dx Перемещение в направлении x @param dy Перемещение в направлении y

Page 30: 8354 Глава 8. Обработка событий Например, если пользователь щелкает на кнопке с надписью Yellow, вызывается

378 Глава 8. Обработка событий

*/ public void add(int dx, int dy) { // Вычисление конца нового отрезка. Point2D end = new Point2D.Double(last.getX() + dx, last.getY() + dy);

// Добавление нового отрезка. Line2D line = new Line2D.Double(last, end); lines.add(line); repaint();

// Запоминается очередная конечная точка. last = end; }

public void paintComponent(Graphics g) { super.paintComponent(g); Graphics2D g2 = (Graphics2D) g;

// Рисование всех линий. for (Line2D l : lines) g2.draw(l); }

private Point2D last; private ArrayList<Line2D> lines;

private static final int SMALL_INCREMENT = 1; private static final int LARGE_INCREMENT = 5;

private class KeyHandler implements KeyListener { public void keyPressed(KeyEvent event) { int keyCode = event.getKeyCode();

// Установка расстояния. int d; if (event.isShiftDown()) d = LARGE_INCREMENT; else d = SMALL_INCREMENT;

// Добавление нового отрезка. if (keyCode == KeyEvent.VK_LEFT) add(-d, 0); else if (keyCode == KeyEvent.VK_RIGHT) add(d, 0); else if (keyCode == KeyEvent.VK_UP) add(0, -d); else if (keyCode == KeyEvent.VK_DOWN) add(0, d); }

public void keyReleased(KeyEvent event) {}

public void keyTyped(KeyEvent event) {

Page 31: 8354 Глава 8. Обработка событий Например, если пользователь щелкает на кнопке с надписью Yellow, вызывается

Типы низкоуровневых событий 379

char keyChar = event.getKeyChar();

// Установка расстояния. int d; if (Character.isUpperCase(keyChar)) { d = LARGE_INCREMENT; keyChar = Character.toLowerCase(keyChar); } else d = SMALL_INCREMENT;

// Добавление нового отрезка. if (keyChar == 'h') add(-d, 0); else if (keyChar == 'l') add(d, 0); else if (keyChar == 'k') add(0, -d); else if (keyChar == 'j') add(0, d); } }}

java.awt.event.KeyEvent 1.1

• char getKeyChar()

Возвращает символ, введенный пользователем.

• int getKeyCode()

Возвращает виртуальный код клавиши, соответствующий данному событию.

• boolean isActionKey()

Возвращает значение true, если данное событие связано с одной из следующихклавиш: <Home>, <End>, <Page Up>, <Page Down>, <Up>, <Down>, <Left>,<Right>, <F1 ... F24>, <Print Screen>, <Scroll Lock>, <Caps Lock>, <Num Lock>,<Pause>, <Insert>, <Delete>, <Enter>, <Backspace> и <Tab>.

• static String getKeyText(int keyCode)

Возвращает строку, описывающую клавишу. Например, вызовgetKetText(KeyEvent.VK_END) возвращает строку "END".

• static String getKeyModifiersText(int modifiers)

Возвращает строку, описывающую модификаторы, например <Shift> или<Ctrl>+<Shift>.

Параметр modifiers Состояние модификаторов

java.awt.event.InputEvent 1.1

• int getModifiers()

Возвращает целое число, описывающее состояние модификаторов <Shift>,<Ctrl> <Alt> и <Meta>. Этот метод применяется как для клавиатуры, так и длямыши. Для того чтобы проверить, установлен ли определенный разряд, при�

Page 32: 8354 Глава 8. Обработка событий Например, если пользователь щелкает на кнопке с надписью Yellow, вызывается

380 Глава 8. Обработка событий

меняются маски SHIFT_MASK, CTRL_MASK, META_MASK или вызывается один изперечисленных ниже методов.

• boolean isShiftDown()• boolean isControlDown()• boolean isAltDown()• boolean isAltGraphDown() 1.2• boolean isMetaDown()

Эти методы возвращают значение true, если клафиша�модификатор в моментгенерации данного события была нажата.

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

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

В данном разделе мы рассмотрим простой графический редактор, позволяющийсоздавать, перемещать и стирать квадраты на холсте (рис. 8.8).

Рис. 8.8. Программа, демонстрирующая обработку событий, связанных с мышью

Когда пользователь щелкает мышью, вызываются три метода объекта�обработчи�ка: mousePressed(), если кнопка мыши была нажата, mouseReleased(), если кноп�ка была отпущена, и mouseClicked. Если надо отследить только сам щелчок, первыедва метода использовать не обязательно. Пользуясь методами getX() и getY()объектаMouseEvent, передаваемого в качестве параметра, можно определить координатыкурсора мыши в момент щелчка. Если нужно различать обычный, двойной и тройной(!) щелчки, используется метод getClickCount().

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

BUTTON2_MASK == ALT_MASKBUTTON3_MASK == META_MASK

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

Page 33: 8354 Глава 8. Обработка событий Например, если пользователь щелкает на кнопке с надписью Yellow, вызывается

Типы низкоуровневых событий 381

туре. Однако в пакете JDK 1.4 использован другой подход. Теперь в нем предусмотре�но несколько масок.

BUTTON1_DOWN_MASKBUTTON2_DOWN_MASKBUTTON3_DOWN_MASKSHIFT_DOWN_MASKCTRL_DOWN_MASKALT_DOWN_MASKALT_GRAPH_DOWN_MASKMETA_DOWN_MASK

Метод getModifiersEx()позволяет распознавать события, связанные с нажати�ем кнопок мыши при нажатых клавишах.

Обратите внимание на то, что маска BUTTON3_DOWN_MASK позволяет проверятьнажатие правой кнопки мыши в среде Windows. Например, для определения, нажатали правая клавиша, можно воспользоваться следующим кодом:

if ((event.getModifiersEx() & InputEvent.BUTTON3_DOWN_MASK) != 0) // Код, обрабатывающий нажатие правой кнопки

В программе, которая будет рассмотрена ниже, используются методыmousePressed() и mouseClicked(). Если щелкнуть кнопкой мыши на пикселе, не при�надлежащем ни одному нарисованному квадрату, на экране появится новый квадрат.Эта процедура реализована в методе mousePressed(), поэтому компьютер отреагируетна щелчок мыши немедленно, не дожидаясь освобождения кнопки мыши. Двойной щел�чок внутри какого�нибудь квадрата приведет к тому, что он будет стерт. Эта процедура реа�лизована в методе mouseClicked(), поскольку щелчки мыши нужно подсчитывать.

public void mousePressed(MouseEvent event){ current = find(event.getPoint()); if (current == null) // Вне квадрата. add(event.getPoint());}

public void mouseClicked(MouseEvent event){ current = find(event.getPoint()); if (current != null && event.getClickCount() >= 2) remove(current);}

При перемещении мыши по окну последнее получает постоянный поток событий,связанных с движением мыши. В большинстве приложений эти события игнорируют�ся. Однако в нашем примере программа отслеживает события, чтобы изменить видкурсора (на крестообразный), как только он выйдет за пределы квадрата. Эта проце�дура реализована в методе getPredefinedCursor() в классе Cursor. В табл. 8.2приведены константы, используемые в этом методе, и показаны виды курсоров, пре�дусмотренные в среде Windows.

Page 34: 8354 Глава 8. Обработка событий Например, если пользователь щелкает на кнопке с надписью Yellow, вызывается

382 Глава 8. Обработка событий

Таблица 8.2. Виды курсоров

Пиктограмма Константа

DEFAULT_CURSOR

CROSSHAIR_CURSOR

HAND_CURSOR

MOVE_CURSOR

TEXT_CURSOR

WAIT_CURSOR

N_RESIZE_CURSOR

NE_RESIZE_CURSOR

E_RESIZE_CURSOR

SE_RESIZE_CURSOR

S_RESIZE_CURSOR

SW_RESIZE_CURSOR

W_RESIZE_CURSOR

NW_RESIZE_CURSOR

(Обратите внимание на то, что некоторые виды курсоров выглядят одинаково.На других платформах они, возможно, будут различаться.)

Изображения курсоров можно найти в каталоге jre/lib/images/cursors. В файлеcursor.properties определены точки, являющиеся указателями. Например, есликурсор имеет вид руки, то указателем считается кончик указательного пальца. Есликурсор имеет вид лупы, то указателем является центр этой лупы.

Page 35: 8354 Глава 8. Обработка событий Например, если пользователь щелкает на кнопке с надписью Yellow, вызывается

Типы низкоуровневых событий 383

Рассмотрим метод mouseMoved(), объявленный в интерфейсе MouseMotionListenerи реализованный в нашей программе.

public void mouseMoved(MouseEvent event){ if (find(event.getPoint()) == null) setCursor(Cursor.getDefaultCursor()); else setCursor(Cursor.getPredefinedCursor (Cursor.CROSSHAIR_CURSOR));}

Используя метод createCustomCursor()из класса Toolkit, можно определить но+вую форму курсора.

Toolkit tk = Toolkit.getDefaultToolkit();Image img = tk.getImage("dynamite.gif");Cursor dynamiteCursor = tk.createCustomCursor(img, new Point(10, 10), "dynamite stick");

Первый параметр метода createCustomCursor() задает изображение курсора.Второй параметр определяет координаты точки, используемой в качестве указателя.Третий параметр — строка, описывающая курсор. Эту строку можно использоватьдля вспомогательных целей, например, сообщить голосом вид курсора. Такая воз+можность важна при адаптации программ для пользователей с нарушениями зрения.

Если перемещение мыши осуществляется при нажатой кнопке, вместоmouseClicked() вызывается метод mouseDragged(). В нашем примере квадратыможно перетаскивать по экрану. При этом квадрат перемещается так, чтобы егоцентр располагался в той точке, на которую указывает мышь. Содержимое холста пе�рерисовывается.

public void mouseDragged(MouseEvent event){ if (cursor >= null) { int x = event.getX(); int y = event.getY();

current.setFrame( x - SIDELENGTH / 2, y - SIDELENGTH / 2, SIDELENGTH, SIDELENGTH); repaint(); }}

Метод mouseMoved() вызывается, только если указатель мыши находится внутрикомпонента. Однако метод mouseDragged() вызывается даже тогда, когда указательмыши пребывает вне компонента.

Page 36: 8354 Глава 8. Обработка событий Например, если пользователь щелкает на кнопке с надписью Yellow, вызывается

384 Глава 8. Обработка событий

Есть еще два метода, обрабатывающих события, связанные с мышью: mouseEn-tered() и mouseExited(). Эти методы вызываются, когда указатель мыши входитв пределы компонента и выходит из него.

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

В нашей программе нас интересуют оба типа событий, связанных с мышью. В нейопределены два внутренних класса: MouseHandler и MouseMotionHandler. КлассMouseHandler расширяет класс MouseAdapter, поскольку он определяет только дваиз пяти методов класса MouseListener. Класс MouseMotionHandler реализует ин�терфейс MouseMotionListener и определяет оба его метода. Текст программы при�веден в листинге 8.4.

Листинг 8.4. Содержимое файла MouseTest.java

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

public class MouseTest{ public static void main(String[] args) { MouseFrame frame = new MouseFrame(); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.setVisible(true); }}

/** Фрейм, содержащий панель, предназначенную для проверки операций с мышью.*/class MouseFrame extends JFrame{ public MouseFrame() { setTitle("MouseTest"); setSize(DEFAULT_WIDTH, DEFAULT_HEIGHT);

// Добавление панели к фрейму.

MousePanel panel = new MousePanel(); add(panel); }

public static final int DEFAULT_WIDTH = 300; public static final int DEFAULT_HEIGHT = 200;

Page 37: 8354 Глава 8. Обработка событий Например, если пользователь щелкает на кнопке с надписью Yellow, вызывается

Типы низкоуровневых событий 385

}

/** Панель, поддерживающая добавление и удаление квадратов с помощью мыши.*/class MousePanel extends JPanel{ public MousePanel() { squares = new ArrayList<Rectangle2D>(); current = null;

addMouseListener(new MouseHandler()); addMouseMotionListener(new MouseMotionHandler()); }

public void paintComponent(Graphics g) { super.paintComponent(g); Graphics2D g2 = (Graphics2D) g;

// Рисование всех квадратов. for (Rectangle2D r : squares) g2.draw(r); }

/** Нахождение первого квадрата, который содержит заданную точку. @param p Точка @return Первый квадрат, содержащий точку p */ public Rectangle2D find(Point2D p) { for (Rectangle2D r : squares) { if (r.contains(p)) return r; } return null; }

/** Добавление квадрата к набору. @param p Центр квадрата */ public void add(Point2D p) { double x = p.getX(); double y = p.getY();

current = new Rectangle2D.Double( x - SIDELENGTH / 2, y - SIDELENGTH / 2, SIDELENGTH, SIDELENGTH); squares.add(current);

Page 38: 8354 Глава 8. Обработка событий Например, если пользователь щелкает на кнопке с надписью Yellow, вызывается

386 Глава 8. Обработка событий

repaint(); }

/** Удаление квадрата из набора. @param s Удаляемый квадрат */ public void remove(Rectangle2D s) { if (s == null) return; if (s == current) current = null; squares.remove(s); repaint(); }

private static final int SIDELENGTH = 10; private ArrayList<Rectangle2D> squares; private Rectangle2D current; // Квадрат, в пределах которого находится курсор.

private class MouseHandler extends MouseAdapter { public void mousePressed(MouseEvent event) { // Если курсор находится за пределами всех квадратов, // добавляется новый квадрат current = find(event.getPoint()); if (current == null) add(event.getPoint()); }

public void mouseClicked(MouseEvent event) { // После двойного щелчка текущий квадрат удаляется. current = find(event.getPoint()); if (current != null && event.getClickCount() >= 2) remove(current); } }

private class MouseMotionHandler implements MouseMotionListener { public void mouseMoved(MouseEvent event) { // Если курсор находится в пределах квадрата, // он принимает крестообразный вид.

if (find(event.getPoint()) == null) setCursor(Cursor.getDefaultCursor()); else

setCursor(Cursor.getPredefinedCursor(Cursor.CROSSHAIR_CURSOR)); }

Page 39: 8354 Глава 8. Обработка событий Например, если пользователь щелкает на кнопке с надписью Yellow, вызывается

Типы низкоуровневых событий 387

public void mouseDragged(MouseEvent event) { if (current != null) { int x = event.getX(); int y = event.getY();

// Перетаскивание текущего квадрата в точку (x, y) current.setFrame( x - SIDELENGTH / 2, y - SIDELENGTH / 2, SIDELENGTH, SIDELENGTH); repaint(); } } }}

java.awt.event.MouseEvent 1.1

• int getX()• int getY()• Point getPoint()

Возвращает горизонтальную (x) и вертикальную (y) координаты или точку, со�ответствующую событию. Отсчет производится от левого верхнего угла компо�нента, являющегося источником события.

• void translatePoint(int x, int y)

Преобразует координаты события, перемещая точку на x единиц горизонталь�но и y единиц вертикально.

• int getClickCount()

Возвращает количество последовательных щелчков мыши, связанных с даннымсобытием. (Интервал времени, в пределах которого подсчитываются щелчки,зависит от операционной системы.)

java.awt.event.InputEvent 1.1

• int getModifiersEx() 1.4

Возвращает модификаторы для события. При проверке возвращаемого значе�ния используются перечисленные ниже маски.

BUTTON1_DOWN_MASKBUTTON2_DOWN_MASKBUTTON3_DOWN_MASKSHIFT_DOWN_MASKCTRL_DOWN_MASKALT_DOWN_MASKALT_GRAPH_DOWN_MASKMETA_DOWN_MASK

Page 40: 8354 Глава 8. Обработка событий Например, если пользователь щелкает на кнопке с надписью Yellow, вызывается

388 Глава 8. Обработка событий

• static int getModifiersExText(int modifiers) 1.4

Возвращает строку, описывающую модификаторы, например Shift+Button1.

java.awt.Toolkit 1.0

• public Cursor createCustomCursor(Image image, Point hotSpot, String name) 1.2

Создает новый объект курсора.

Параметры image Изображение активного курсораhotSpot Точка, считающаяся указателемname Описание курсора

java.awt.Component 1.0

• public void setCursor(Cursor cursor) 1.1

Изменяет внешний вид курсора.

События, связанные с фокусом вводаПользуясь мышью, можно указать любой объект на экране. Однако при вводе тек�

ста нажатие клавиши должно передаваться конкретному объекту на экране. Оконныйдиспетчер направляет все нажатия клавиш активному окну (active window). Обычно ак�тивное окно отличается от остальных цветом строки заголовка. Активным можетбыть лишь одно окно.

Предположим, что активным окном управляет программа, написанная на языке Java.Это окно получает информацию о нажатии клавиш и направляет ее конкретному компо�ненту. В таком случае говорят, что этот компонент обладает фокусом ввода. Аналогичнотому, как активное окно выделяется среди остальных цветом строки заголовка, компо�нент, имеющий фокус, визуально отличается от других. В поле ввода текста мигает кур�сор, активная кнопка выделяется прямоугольником, обрамляющим ее заголовок, и т.д.Если фокус принадлежит полю редактирования, в нем можно вводить текст. Если фоку�сом обладает кнопка, на ней можно щелкнуть, нажав клавишу пробела.

В каждый момент времени фокус ввода может принадлежать только одному ком�поненту. Компонент может потерять фокус ввода, если пользователь выбрал другойкомпонент. Этому компоненту передается фокус ввода. Компонент получает фокус,если пользователь щелкнул на нем мышью. Для этой цели можно воспользоватьсятакже клавишей <Tab>, которая перемещает фокус с одного компонента на другой.Таким образом, можно пройтись по всем компонентам, которые потенциально могутобладать фокусом. По умолчанию перемещение по компонентам библиотеки Swingосуществляется слева направо, а затем сверху вниз в соответствии с их расположени�ем в контейнере. Порядок обхода компонентов можно изменить (подробная инфор�мация об этом приведена в следующей главе).

К счастью, большинство прикладных программистов не слишком беспокоятся обобработке событий, связанных с фокусом. До появления пакета JDK 1.4 этот механизмобычно использовался для проверки возможных ошибок или верификации данных.Допустим, что в текстовое поле вводится номер кредитной карточки и после завер�шения редактирования пользователь переходит в новое поле. В этот момент можно

Page 41: 8354 Глава 8. Обработка событий Например, если пользователь щелкает на кнопке с надписью Yellow, вызывается

Типы низкоуровневых событий 389

перехватить событие, связанное с потерей фокуса. Если номер кредитной карточкине соответствует ожидаемому формату, можно вывести сообщение об ошибке и вер�нуть фокус предыдущему полю ввода. Однако в пакете JDK 1.4 есть более простыеи надежные механизмы верификации. Мы обсудим их в главе 9.

К сожалению, в ранних версиях пакета JDK обработка событий, связанных с фокусомввода, была реализована не слишком удачно. Пользователи обнаружили больше сот+ни разных ошибок. Это явление объясняется двумя причинами. Во+первых, фокускомпонента взаимодействует с фокусом окна, который находится в компетенцииоконного диспетчера. Следовательно, поведение фокуса зависит от платформы, накоторой выполняется приложение. Во+вторых, реализация кода, очевидно, быстровышла из+под контроля, особенно в сочетании с неудовлетворительной реализациеймеханизма отслеживания фокуса в пакете JDK 1.3.

Уинстон Черчилль (Winston Churchill) однажды сказал: “Американцы всегда поступают пра+вильно... после того как исчерпают все остальные возможности”. Очевидно, то же самоеотносится и к группе разработчиков языка Java. Они все же реализовали правильный ме+ханизм отслеживания фокусов в пакете JDK 1.4. В этом пакете полностью переработан кодобработки фокуса и подробно описано его ожидаемое поведение, включая неизбежнуюзависимость от платформы.

Спецификацию механизма обработки фокуса ввода можно найти на Web+страницеhttp://java.sun.com/j2se/~1.4/docs/api/java/awt/doc-files/FocusSpec.html.

Некоторые компоненты, например метки и панели, по умолчанию не получаютфокус ввода, поскольку предполагается, что они используются для отображения со�общений или объединения нескольких компонентов в одно целое. Если вы хотите на�писать приложение, которое позволяло бы рисовать на панели, нажимая клавиши, тонеобходимо сделать так, чтобы панель могла обладать фокусом. Работая с JDK 1.4, дляэтого достаточно вызвать метод setFocusable().

panel.setFocusable(true);

В старых версиях JDK переопределение метода isFocusTraversable(), принадле+жащего компоненту, приводило к такому же результату. Однако в старых пакетахиспользовалась другая концепция фокуса ввода. Это различие вводило пользовате+лей в заблуждение, поэтому метод isFocusTraversable() был объявлен как не ре+комендованный к применению.

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

В пакете JDK 1.4 используются следующие понятия.

• Владелец фокуса (focus owner), т.е. компонент, обладающий фокусом ввода.

• Окно, содержащее владельца фокуса.

• Активное окно (active window), т.е фрейм или диалоговое окно, содержащее вла�дельца фокуса.

Page 42: 8354 Глава 8. Обработка событий Например, если пользователь щелкает на кнопке с надписью Yellow, вызывается

390 Глава 8. Обработка событий

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

Чтобы получить эту информацию о владельце, об окне, содержащем его, и активномокне, сначала надо получить диспетчер фокуса клавиатуры.

KeyboardFocusManager manager = KeyboardFocusManager.getCurrentKeyboardFocusManager();

Затем следует вызвать приведенные ниже методы.

Component owner = manager.getFocusOwner();Window focused = manager.getFocusedWindow();Window active = manager.getActiveWindow(); // Фрейм или диалоговое окно

Для регистрации перемещений фокуса ввода необходимо связать обработчикисобытий с компонентами или окнами. Обработчик должен реализовывать два метода:focusGained() и focusLost(). Эти методы вызываются тогда, когда источниксобытия получает фокус ввода или теряет фокус. Каждому из этих методов передаетсяпараметр FocusEvent. В этом классе также есть два полезных метода: методgetComponent() сообщает, обладает ли компонент фокусом ввода, а метод isTempo-rary() возвращает значение true, если фокус ввода передан временно. Временнаяпередача фокуса происходит тогда, когда компонент теряет управление, но впослед�ствии автоматически получает его обратно. Например, если пользователь переходитк другому окну, а затем возвращается к предыдущему, исходный компонент снова по�лучает фокус ввода.

В JDK 1.4 реализован механизм доставки событий, связанных с фокусом ввода дляокна. Для этого с окном связывается класс WindowFocusListener и переопределя�ются методы windowGainedFocus() и windowLostFocus().

Кроме того, в пакете JDK 1.4 введено понятие “дополняющего”, или “противопо�ложного”, компонента или окна. Когда компонент или окно теряют фокус ввода,“противоположный” компонент или окно получают фокус. И наоборот, когда исход�ный компонент или окно вновь получает фокус, их “противоположное” окно теряетего. Метод getOppositeComponent() класса FocusEvent возвращает “противопо�ложный” компонент, а метод getOppositeWindow() класса WindowEvent — “про�тивоположное” окно.

Фокус ввода можно запрограммировать с помощью метода requestComponent()класса Component. Однако поведение фокуса существенно зависит от платформы, накоторой выполняется приложение. Для создания платформенно�независимых про�грамм в пакете JDK 1.4 предусмотрен метод requestFocusInWindow() классаComponent. Этот метод работает, только если компонент содержится внутри окна.

Не следует считать, что ваш компонент обладает фокусом ввода лишь на основаниитого, что метод requestFocus() или requestFocusInWindow() вернул значениеtrue. Лучше дождаться события FOCUS_GAINED.

Некоторые программисты неверно понимают суть события FOCUS_LOST и пытаютсяпредотвратить получение фокуса другим компонентом, вызывая обработчикfocusLost. Однако к этому времени компонент уже потерял фокус ввода. Если вам

Page 43: 8354 Глава 8. Обработка событий Например, если пользователь щелкает на кнопке с надписью Yellow, вызывается

Типы низкоуровневых событий 391

необходимо отслеживать перехват фокуса конкретным компонентом, необходимоинсталлировать “обработчик, обладающий правом вето” и запретить изменениясвойства focusOwner. Подробно данный механизм описан в главе 8 тома 2.

java.awt.Component 1.0

• void requestFocus()

Запрашивает передачу фокуса данному компоненту.

• Boolean requestFocusInWindow() 1.4

Запрашивает передачу фокуса данному компоненту. Возвращает значениеfalse, если данный компонент не содержится в окне или запрос невозможновыполнить по другой причине. Возвращает значение true, если запрос можновыполнить.

• void setFocusable(boolean b) 1.4• boolean isFocusable() 1.4

Устанавливает или определяет способность компонента обладать фокусом вво�да. Если параметр b имеет значение true, значит, компонент может получатьфокус.

• boolean isFocusOwner() 1.4

Возвращает значение true, если компонент в данный момент обладает фоку�сом ввода.

java.awt.KeyboardFocusManager 1.4

• static KeyboardFocusManager getCurrentKeyboardFocusManager()

Определяет текущий диспетчер фокуса.

• Component getFocusowner()

Определяет компонент, владеющий фокусом ввода, или возвращает ссылку null,если компонент, который обладает фокусом, не управляется диспетчером.

• Window getFocusWindow()

Определяет окно, которое содержит компонент, владеющий фокусом ввода,или возвращает ссылку null, если компонент, который обладает фокусом,не управляется диспетчером.

• Window getActiveWindow()

Определяет диалоговое окно или фрейм, содержащее окно, в котором нахо�дится компонент, владеющий фокусом ввода. Если окно не управляется дис�петчером, возвращает ссылку null.

java.awt.Window 1.0

• boolean isFocused() 1.4

Возвращает значение true, если окно содержит элемент, обладающий фокусомввода.

Page 44: 8354 Глава 8. Обработка событий Например, если пользователь щелкает на кнопке с надписью Yellow, вызывается

392 Глава 8. Обработка событий

• boolean isActive() 1.4

Возвращает значение true, если фрейм или диалоговое окно является актив�ным. Строка заголовка активного фрейма или диалогового окна обычно выде�ляется цветом.

java.awt.event.FocusEvent 1.1

• Component getOppositeComponent() 1.4

Возвращает компонент, который потерял фокус в обработчике focusGainedили получил фокус в обработчике focusLost.

java.awt.event.WindowEvent 1.4

• Component getOppositeWindow() 1.4

Возвращает окно, потерявшее фокус в обработчике windowGainedFocus, либоокно, которое получило фокус в обработчике windowLostFocus, либо окно,активизированное в обработчике windowActivated, или окно, деактивизиро�ванное в обработчике windowDeactivated.

java.awt.event.WindowFocusListener 1.4

• void windowGainedFocus(WindowEvent event)

Вызывается, когда окно, являющееся источником события, получает фокусввода.

• void windowLostFocus(windowEvent event)

Вызывается, когда окно, являющееся источником события, теряет фокус ввода.

ДействияОдну и ту же команду можно выполнить разными способами. Пользователь может

выбрать пункт меню, нажать соответствующую клавишу или щелкнуть на кнопке панелиинструментов. В этом случае очень удобна рассматриваемая нами модель событий: дос�таточно связать все эти события с одним и тем же обработчиком. Допустим, чтоblueAction — это объект некоего класса (скажем, ColorAction), реализующего ин�терфейс ActionListener и изменяющего цвет фона на синий. Можно связать одини тот же объект с разными источниками событий, перечисленными ниже.

• Кнопка Blue на панели инструментов.

• Пункт меню Blue.

• Нажатие клавиш <Ctrl+B>.

Команда, изменяющая цвет, выполняется одинаково, независимо от того, что при�вело к ее выполнению — щелчок на кнопке, выбор пункта меню или нажатие клавиш.

В пакете Swing предусмотрен очень полезный механизм, позволяющий инкапсу�лировать команды и связывать их с несколькими источниками событий. Этот меха�низм представляет собой интерфейс Action. Действие (action) — это объект, инкапсу�лирующий описание команды (в виде текстовой строки или пиктограммы) и парамет�ры, необходимые для выполнения программы (например, нужный цвет).

Page 45: 8354 Глава 8. Обработка событий Например, если пользователь щелкает на кнопке с надписью Yellow, вызывается

Действия 393

Интерфейс Action содержит следующие методы:

Void actionPerformed(ActionEvent event)void setEnabled(boolean b)boolean isEnabled()void pitValue(String key, Object value)Object getValue(String key)void addPropertyChangeListener(PropertyChangeListener listener)void removePropertyChangeListener(PropertyChangeListener listener)

Первый метод похож на метод интерфейса ActionListener: действительно, ин�терфейс Action расширяет интерфейс ActionListener. Следовательно, вместообъекта класса, реализующего интерфейс ActionListener, можно использоватьобъект класса, реализующего интерфейс Action.

Следующие два метода позволяют блокировать и разблокировать действие, а так�же проверить, является ли указанное действие разблокированным (т.е. доступным) вданный момент. Если пункт меню или кнопка панели инструментов связаны с забло�кированным действием, они выделяются светло�серым цветом.

Методы putValue() getValue() позволяют записывать и извлекать из памятипроизвольные пары имя–значение объектов класса, реализующего интерфейс Action.Предопределенные строки, такие как Action.NAME и Action.SMALL_ICON, содержатимена и пиктограммы объектов действий.

Action.putValue(Action.NAME, "Blue");action.putValue(Action.SMALL_ICON, new ImageIcon("blue-ball.gif"));

В табл. 8.3 приведены предопределенные имена.

Таблица 8.3. Заранее определенные имена действий

Имя Значение

NAME Имя действия. Отображается на кнопке и в названии пункта меню

SMALL_ICON Место для хранения пиктограммы, которая изображается накнопке, в пункте меню или на панели инструментов

SHORT_DESCRIPTION Краткое описание пиктограммы, отображается в строке подсказки

LONG_DESCRIPTION Подробное описание пиктограммы. Может использоваться дляподсказки. Не используется ни одним компонентом из библиоте�ки Swing

MNEMONIC_KEY Мнемоническое сокращение. Отображается в пункте меню(см. главу 9)

ACCELERATOR_KEY Место для хранения сочетания клавиш. Не используется ни од�ним компонентом из библиотеки Swing

ACTION_COMMAND_KEY Ранее использовалось в теперь уже устаревшем методеregisteredKeyBoardAction()

DEFAULT Может быть полезным для хранения разнообразных объектов.Не используется ни одним компонентом из библиотеки Swing

Если в меню или к панели инструментов добавляется какое�то действие, его имя ипиктограмма автоматически извлекаются из памяти и отображаются в меню и на па�нели. Значение SHORT_DESCRIPTION выводится в строке подсказки.

Page 46: 8354 Глава 8. Обработка событий Например, если пользователь щелкает на кнопке с надписью Yellow, вызывается

394 Глава 8. Обработка событий

Последние два метода интерфейса Action позволяют извещать другие объекты,в частности меню и панели инструментов, об изменении свойств действий. Например,если меню создано как обработчик, предназначенный для отслеживания изменениясвойств действия, и это действие впоследствии было заблокировано, то при отображе�нии меню на экране соответствующее имя действия может быть выделено серым цве�том. Обработчики изменения свойств представляют собой часть модели JavaBeans.Подробно компоненты JavaBeans и их свойства будут рассматриваться во втором томе.

Обратите внимание на то, что Action является интерфейсом, а не классом. Любойкласс, реализующий этот интерфейс, должен реализовать семь методов, которые мытолько что рассмотрели. К счастью, это сделать достаточно легко, поскольку все они,кроме первого метода, содержатся в классе AbstractAction. Класс AbstractActionпредназначен для хранения пар, состоящих из имен и значений, а также для управленияобработчиками изменения свойств. Все, что нужно для этого сделать для практическогоиспользования, — это создать подкласс и добавить метод actionPerformed().

Попробуем создать действие, изменяющее цвет фона. Поместим в память имякоманды, соответствующую пиктограмму и желаемый цвет. Код цвета запишем в таб�лицу, состоящую из пар имя–значение, предусмотренных классом AbstractAction.Ниже приводится класс ColorAction. Конструктор задает пары, состоящие из имени значений, а метод ActionPerformed() изменяет цвет фона.

public class ColorAction extends AbstractAction{ public ColorAction(String name, Icon icon, Color c) { putValue(Action.NAME, name); putValue(Action.SMALL_ICON, icon); putValue("color", c); putValue(Action.SHORT_DESCRIPTION, "Set panel color to " + name.toLowerCase()); }

public void actionPerformed(ActionEvent event) { Color c = (Color) getValue("color"); setBackground(c); }}

В программе создаются три объекта этого класса.

Action blueAction = new ColorAction("Blue", new ImageIcon("blue-ball.gif"), Color.BLUE);

Теперь свяжем это действие с кнопкой. Сделать это можно с помощью конструк�тора JButton, получающего объект Action в качестве параметра.

JButton blueButton = new JButton(blueAction);

Этот конструктор считывает имя и пиктограмму действия, помещает короткоеописание в строку подсказки и регистрирует объект Action в качестве обработчика.Пиктограмма и строка подсказки показаны на рис. 8.9.

Как мы увидим в следующей главе, точно так же легко можно добавлять действияв меню.

Page 47: 8354 Глава 8. Обработка событий Например, если пользователь щелкает на кнопке с надписью Yellow, вызывается

Действия 395

Рис. 8.9. Кнопки с пиктограммами из объекта класса ColorAction,реализующего интерфейс Action

Кроме того, нужно связать действия с нажатием клавиш. Для этого необходимо навремя погрузиться в технические детали. Нажатия кнопок передаются компонентам,обладающим фокусом ввода. В нашем примере предусмотрены три компонента —три кнопки на панели. Следовательно, в любой момент одна из этих трех кнопок можетвладеть фокусом. Каждая из этих кнопок должна обрабатывать события, связанныес нажатием клавиш, и реагировать на сочетания клавиш <Ctrl+Y>, <Ctrl+B> и <Ctrl+R>.

Эта проблема имеет универсальный характер, и разработчики пакета Swing нашлиее удачное решение.

На самом деле в версии 1.2 пакета JDK были предложены два решения, позволяющих свя+зать клавиши с действиями: метод registerKeyboardAction() класса JComponent имеханизм отображения клавиш для команд JTextComponent. В версии JDK 1.3 решениябыли объединены. В данном разделе описывается именно этот унифицированный подход.

Для того чтобы связать действия с нажатием клавиш, сначала нужно создатьобъект класса KeyStroke. Это удобный класс, инкапсулирующий описание клавиш.Чтобы создать объект этого класса, не нужно вызывать конструктор. Вместо него сле�дует использовать статический метод getKeyStroke() класса KeyStroke. Теперьнеобходимо указать виртуальный код клавиш и флаги (например, для сочетаний, в со�став которых входят клавиши <Shift> или <Ctrl>).

KeyStroke ctrlBKey = KeyStroke.getKeyStroke(KeyEvent.VK_B, InputEvent.CTRL_MASK);

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

KeyStroke ctrlBKey = KeyStroke.getKeyStroke("ctrl B");

Каждый объект класса JComponent содержит три отображения ввода (input map),связывающих объекты класса KeyStroke с действиями. Эти отображения соответст�вуют различным условиям (табл. 8.4).

Таблица 8.4. Условия, соответствующие трем отображениям ввода

Флаг Условие

WHEN_FOCUSED Когда данный компонент находится в фокусе кла�виатуры

WHEN_ANCESTOR_OF_FOCUSED_COMPONENT Когда данный компонент содержит другой компо�нент, находящийся в фокусе клавиатуры

WHEN_IN_FOCUSED_WINDOW Когда данный компонент содержится в том же окне,что и компонент, находящийся в фокусе клавиатуры

Page 48: 8354 Глава 8. Обработка событий Например, если пользователь щелкает на кнопке с надписью Yellow, вызывается

396 Глава 8. Обработка событий

При нажатии клавиши отображения ввода обрабатываются в следующем порядке.

1. Проверяется отображение WHEN_FOCUSED компонента, владеющего фокусомввода. Если предусмотрена реакция на нажатие клавиши, выполняется соответ�ствующее действие, если действие не заблокировано, процесс прекращается.

2. Начиная с компонента, содержащего фокус ввода, выполняется проверка усло�вия WHEN_ANCESTOR_OF_FOCUSED_COMPONENT его родительского компонента.Как только условие будет выполнено, производится соответствующее действие.Если действие не заблокировано, процесс прекращается.

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

Отображение ввода извлекается из компонента с помощью метода getInputMap().

InputMap imap = panel.getInputMap(JComponent.WHEN_FOCUSED);

Условие WHEN_FOCUSED означает, что данное отображение проверяется, если компо�нент обладает фокусом ввода. В нашем случае это условие не проверяется, поскольку фоку�сом владеет одна кнопка, а не панель в целом. Каждое из оставшихся двух отображенийтакже позволяет очень легко изменить цвет фона в ответ на нажатие клавиш. В программемы будем использовать WHEN_ANCESTOR_OF_FOCUSED_COMPONENT.

Класс InputMap не связывает напрямую объекты класса KeyStroke с объектамикласса ColorAction, реализующего интерфейс Action. Вместо этого объектам клас�са KeyStroke он ставит в соответствие произвольные объекты, а с помощью второгоотображения, реализованного классом ActionMap, связывает их с действиями.Это облегчает связывание одного и того же действия с разными клавишами, зареги�стрированными в разных отображениях ввода.

Итак, каждый компонент имеет три отображения ввода и одно отображение, свя�зывающее нажатие клавиш с действиями. Чтобы объединить их, нужно присвоитькаждому действию имя.

imap.put(KeyStroke.getKeyStroke("ctrl Y"), "panel.yellow");ActionMap amap = panel.getActionMap();amap.put("panel.yellow", yellowAction);

Если нужно задать “пустое” действие, можно использовать строку "none". Это по�зволяет легко заблокировать клавишу.

imap.put(KeyStroke.getKeyStroke("ctrl C", "none");

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

Page 49: 8354 Глава 8. Обработка событий Например, если пользователь щелкает на кнопке с надписью Yellow, вызывается

Действия 397

Итак, чтобы одна и та же операция выполнялась в ответ на щелчок на кнопке,выбор пункта меню или нажатие клавиши, надо сделать следующее

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

2. Создать объект класса, реализующего интерфейс Action.

3. Создать кнопку или пункт меню на основе объекта класса, реализующегоинтерфейс Action.

4. Для действий, которые выполняются в ответ на нажатие клавиш, нужно сделатьодин дополнительный шаг. Сначала следует идентифицировать компонент ок�на верхнего уровня, например панель, содержащую все остальные компоненты.

5. Проверить отображение WHEN_ANCESTOR_OF_FOCUSED_COMPONENT компо�нента верхнего уровня. Создать объект класса KeyStroke для нужного нажатияклавиш. Создать объект, соответствующий нажатию клавиш, например строку,описывающую нужное действие. Добавить пару (нажатие клавиши, действие)к отображению ввода.

6. Получить информацию о соответствии действий и клавиш для компонентаверхнего уровня. Добавить пару (клавиша, действие) в отображение.

В листинге 8.5 приведен полный текст программы, задающей соответствие междукнопками, нажатием клавиш и действиями. Действия выполняются в ответ на активи�зацию кнопок или нажатие клавиш <Ctrl+Y>, <Ctrl+B> или <Ctrl+R>.

Листинг 8.5. Содержимое файла ActionTest.java

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

public class ActionTest{ public static void main(String[] args) { ActionFrame frame = new ActionFrame(); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.setVisible(true); }}

/** Фрейм с панелью, демонстрирующий изменение цвета.*/class ActionFrame extends JFrame{ public ActionFrame() { setTitle("ActionTest"); setSize(DEFAULT_WIDTH, DEFAULT_HEIGHT);

// Добавление панели к фрейму.

ActionPanel panel = new ActionPanel();

Page 50: 8354 Глава 8. Обработка событий Например, если пользователь щелкает на кнопке с надписью Yellow, вызывается

398 Глава 8. Обработка событий

add(panel); }

public static final int DEFAULT_WIDTH = 300; public static final int DEFAULT_HEIGHT = 200;}

/** Панель, цвет которой можно изменять щелчком на кнопке или нажатием клавиш.*/class ActionPanel extends JPanel{ public ActionPanel() { // Определение действий.

Action yellowAction = new ColorAction("Yellow", new ImageIcon("yellow-ball.gif"), Color.YELLOW); Action blueAction = new ColorAction("Blue", new ImageIcon("blue-ball.gif"), Color.BLUE); Action redAction = new ColorAction("Red", new ImageIcon("red-ball.gif"), Color.RED);

// Кнопки, предназначенные для выполнения операций.

add(new JButton(yellowAction)); add(new JButton(blueAction)); add(new JButton(redAction));

// Связывание клавиш Y, B и R с именами.

InputMap imap = getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);

imap.put(KeyStroke.getKeyStroke("ctrl Y"), "panel.yellow"); imap.put(KeyStroke.getKeyStroke("ctrl B"), "panel.blue"); imap.put(KeyStroke.getKeyStroke("ctrl R"), "panel.red");

// Связывание имен с действиями

ActionMap amap = getActionMap(); amap.put("panel.yellow", yellowAction); amap.put("panel.blue", blueAction); amap.put("panel.red", redAction); }

public class ColorAction extends AbstractAction { /** Создание действий для изменения цвета. @param name Надпись на кнопке @param icon Пиктограмма на кнопке @param c Цвет фона

Page 51: 8354 Глава 8. Обработка событий Например, если пользователь щелкает на кнопке с надписью Yellow, вызывается

Действия 399

*/ public ColorAction(String name, Icon icon, Color c) { putValue(Action.NAME, name); putValue(Action.SMALL_ICON, icon); putValue(Action.SHORT_DESCRIPTION, "Set panel color to " + name.toLowerCase()); putValue("color", c); }

public void actionPerformed(ActionEvent event) { Color c = (Color) getValue("color"); setBackground(c); } }}

javax.swing.Action 1.2

• void setEnabled(boolean b)

Блокирует и разблокирует действия.

• boolean isEnabled()

Возвращает значение true, если действие разблокировано.

• void putValue(String key, Object value)

Помещает пару (имя, значение) в объект действия.

Параметры key Имя, которое должно сохраняться в объекте действия.Оно может быть строкой, но несколько имен имеютспециальное значение

value Объект, связанный с именем

• Object getValue(String key)

Возвращает значение из сохраненной пары (имя, значение).

javax.swing.JMenu 1.2

• JMenuItem add(Action a)

Добавляет в меню пункт, вызывающий данное действие. Возвращает добавлен�ный пункт меню.

javax.swing.KeyStroke 1.2

• static KeyStroke getKeyStroke(char keyChar)

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

• static KeyStroke getKeyStroke(int keyCode, int modifiers)• static KeyStroke getKeyStroke(int keyCode, int modifiers, boolean onRelease)

Page 52: 8354 Глава 8. Обработка событий Например, если пользователь щелкает на кнопке с надписью Yellow, вызывается

400 Глава 8. Обработка событий

Создает объект класса KeyStroke, инкапсулирующий нажатие клавиши, кото�рое соответствует событию KEY_PRESSED или KEY_RELEASED.

Параметры keyCode Виртуальный код клавишиmodifiers Одно из сочетаний: InputEvent.SHIFT_MASK,

InputEvent.CONTROL_MASK,InputEvent.ALT_MASK илиInputEvent.META_MASK

onRelease Значение true, если код соответствует отпущен�ной клавише

• static KeyStroke getKeyStroke(String description)

Создает объект класса KeyStroke по описанию, представленному в виде стро�ки. Строка состоит из лексем, разделенных пробелами, в следующем формате.

1. Лексемы shift control ctrl meta alt button1 button2 button3преобразовываются в соответствующую битовую маску.

2. Лексема typed должна сопровождаться символом, например "typed a".3. Лексема pressed или released означает, что клавиша нажата или отпуще�

на. (По умолчанию предполагается, что клавиша нажата.)4. В противном случае лексема с префиксом VK_ должна соответствовать кон�

станте KeyEvent, например INSERT соответствует KeyEvent.VK_INSERT.

Так, строка "released ctrl Y" соответствует выражению getKeyStrokeKeyEvent.VK_Y, Event.CTRL_MASK, true).

javax.swing.JComponent 1.2

• ActionMap getActionMap() 1.3

Возвращает отображения клавиш в действия.

• InputMap getInputMap(int flag) 1.3

Получает отображение ввода, которое переводит ключи действий в объектыдействий.

Параметр flag Одно из значений, представленных в табл. 8.4

Многоадресная передача событийВ предыдущем разделе мы связали несколько источников событий с одним обра�

ботчиком. Теперь попробуем сделать противоположное. Все источники событийв библиотеке AWT поддерживают модель многоадресной передачи событий (multicastmodel). Это означает, что одно и то же событие может передаваться несколькимобъектам�обработчикам. Многоадресная модель передачи событий полезна, когда со�бытие потенциально представляет интерес для нескольких объектов. Нужно лишь до�бавить несколько обработчиков для одного и того же источника событий, и все заре�гистрированные объекты смогут реагировать на эти события.

Рассмотрим простое приложение, в котором используется многоадресная переда�ча событий. Предположим, что, щелкая на кнопке New, в некоем фрейме, пользова�тель открыл несколько окон. Все окна закрываются кнопкой Close all (рис. 8.10).

Page 53: 8354 Глава 8. Обработка событий Например, если пользователь щелкает на кнопке с надписью Yellow, вызывается

Многоадресная передача событий 401

Как сказано в документации JDK, “API не гарантирует определенного порядка, в ко+тором события передаются обработчикам, зарегистрированным для данного источ+ника”. Поэтому нельзя при создании программ реализовывать логику, зависящую отпоследовательности передачи событий.

Обработчик события, связанного с кнопкой New, — объект newListener, создан�ный конструктором класса MulticastPanel, — формирует новый фрейм в ответ нащелчок на кнопке.

Однако кнопка Close all класса MulticastPanel имеет несколько обработчиков.При каждом вызове метод makeNewFrame() добавляет к кнопке Close all еще одногообработчика. Когда пользователь щелкает на кнопке Close all, активизируются всеобработчики, и каждый из них закрывает свой фрейм.

Метод actionPerformed() удаляет обработчик, связанный ранее с кнопкойClose, поскольку в нем больше нет необходимости (фрейм закрыт).

Полный текст программы приведен в листинге 8.6.

Рис. 8.10. Все фреймы реагируют на команду Close all

Page 54: 8354 Глава 8. Обработка событий Например, если пользователь щелкает на кнопке с надписью Yellow, вызывается

402 Глава 8. Обработка событий

Листинг 8.6. Содержимое файла MulticastTest.java

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

public class MulticastTest{ public static void main(String[] args) { MulticastFrame frame = new MulticastFrame(); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.setVisible(true); }}

/** Фрейм с кнопками, предназначенными для создания и удаления вторичных фреймов.*/class MulticastFrame extends JFrame{ public MulticastFrame() { setTitle("MulticastTest"); setSize(DEFAULT_WIDTH, DEFAULT_HEIGHT);

// Добавление панели к фрейму.

MulticastPanel panel = new MulticastPanel(); add(panel); }

public static final int DEFAULT_WIDTH = 300; public static final int DEFAULT_HEIGHT = 200;}

/** Панель с кнопками для создания и удаления фреймов.*/class MulticastPanel extends JPanel{ public MulticastPanel() { // Добавление кнопки New

JButton newButton = new JButton("New"); add(newButton); final JButton closeAllButton = new JButton("Close all"); add(closeAllButton);

ActionListener newListener = new ActionListener() { public void actionPerformed(ActionEvent event) {

Page 55: 8354 Глава 8. Обработка событий Например, если пользователь щелкает на кнопке с надписью Yellow, вызывается

Реализация источников событий 403

BlankFrame frame = new BlankFrame(closeAllButton); frame.setVisible(true); } };

newButton.addActionListener(newListener); }}

/** Пустой фрейм, который закрывается щелчком на кнопке.*/class BlankFrame extends JFrame{ /** Создание пустого фрейма. @param closeButton Кнопка для закрытия фрейма */ public BlankFrame(final JButton closeButton) { counter++; setTitle("Frame " + counter); setSize(DEFAULT_WIDTH, DEFAULT_HEIGHT); setLocation(SPACING * counter, SPACING * counter);

closeListener = new ActionListener() { public void actionPerformed(ActionEvent event) { closeButton.removeActionListener(closeListener); dispose(); } }; closeButton.addActionListener(closeListener); }

private ActionListener closeListener; public static final int DEFAULT_WIDTH = 200; public static final int DEFAULT_HEIGHT = 150; public static final int SPACING = 40; private static int counter = 0;}

Реализация источников событийВ последнем разделе данной главы мы покажем, как реализуется класс, генерирую�

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

В качестве источника событий будет выступать объект PaintCountPanel, под�считывающий число вызовов метода paintComponent. При каждом увеличениисчетчика PaintCountPanel оповещает всех обработчиков. В нашем примере мысвяжем с источником один обработчик, который будет изменять заголовок фрейма(рис. 8.11).

Page 56: 8354 Глава 8. Обработка событий Например, если пользователь щелкает на кнопке с надписью Yellow, вызывается

404 Глава 8. Обработка событий

Рис. 8.11. Подсчет числа перерисовок панели

Определяя источник событий, надо указать следующие сведения.

• Тип события. Мы можем определить собственный класс события, но для про�стоты воспользуемся существующим классом PropertyChangeEvent.

• Интерфейс обработчика. Допустимо определить собственный интерфейс, но мыиспользуем существующий интерфейс PropertyChangeListener. В немобъявлен единственный метод.

public void propertyChange(PropertyChangeEvent event)

• Методы для связывания обработчиков и их удаления. В классе PaintCountPanel мыопределим следующие два метода:

public void addPropertyChangeListener (PropertyChangeListener listener)

public void removePropertyChangeListener (PropertyChangeListener listener)

Как убедиться в том, что информация о событиях передана обработчикам? За опо�вещение отвечает источник. Именно он при возникновении события должен создатьсоответствующий объект и передать его зарегистрированным обработчикам.

Задачу управления событиями приходится решать достаточно часто, поэтому в биб�лиотеке Swing предусмотрен класс EventListenerList. Этот класс упрощает добавле�ние и удаление обработчиков, а также генерацию событий. Он также берет на себя всюсложную работу, связанную с обработкой нескольких потоков, пытающихся одновремен�но добавлять, удалять или передавать события.

Поскольку некоторые источники событий допускают использование несколькихтипов обработчиков, каждый обработчик в списке событий соответствует конкрет�ному классу. Методы add() и remove() предназначены для реализации методовaddXxxlistener.

public void addPropertyChangeListener (PropertyChangeListener listener){ listenerList.add(PropertyChangeListener.class, listener);}

Page 57: 8354 Глава 8. Обработка событий Например, если пользователь щелкает на кнопке с надписью Yellow, вызывается

Реализация источников событий 405

public void removePropertyChangeListener (PropertyChangeListener listener){ listenerList.remove(PropertyChangeListener.class, listener);}

Может возникнуть вопрос, почему класс EventListenerList не проверяет, какойинтерфейс реализуется объектом обработчика. Однако объект может реализовыватьнесколько интерфейсов. Например, может оказаться, что объект, на который ссыла+ется параметр listener, одновременно реализует интерфейсы PropertyChange-Listener и ActionListener, а программист захочет, чтобы при вызове методаaddPropertyChangeListener() добавлялся только обработчик PropertyChange-Listener. Класс EventListenerList должен обеспечить возможность такого выбора.

При вызове метода paintComponent() класс PaintCountPanel создает объектPropertyChangeEvent, указывая в нем источник события, имя свойства, его староеи новое значения. Затем происходит вызов вспомогательного методаfirePropertyChangeEvent().

public void paintComponent(Graphics g){ int oldPaintCount = paintCount; paintCount++; firePropertyChangeEvent(new PropertyChangeEvent(this, "paintCount", oldPaintCount, paintCount)); super.paintComponent(g);}

Метод firePropertyChangeEvent() обнаруживает всех зарегистрированныхобработчиков и для каждого из них вызывает метод propertyChange().

public void firePropertyChangeEvent(PropertyChangeEvent event){ EventListener[] listeners = listenerList.getListeners(PropertyChangeListener.class); for (EventListener l : listeners) ((PropertyChangeListener) l).propertyChange(event);}

В листинге 8.7 приведен исходный код программы, основу которой составляет классPaintCountPanel. Конструктор фрейма связывает с панелью обработчик, реагирую�щий на изменение свойства. Этот обработчик обновляет строку заголовка фрейма.

panel.addPropertyChangeListener(new PropertyChangeListener() { public void propertyChange(PropertyChangeEvent event) { setTitle("EventSourceTest - " + event.getNewValue()); } });

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

Page 58: 8354 Глава 8. Обработка событий Например, если пользователь щелкает на кнопке с надписью Yellow, вызывается

406 Глава 8. Обработка событий

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

Листинг 8.7. Содержимое файла CustomEventTest.java

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

public class EventSourceTest{ public static void main(String[] args) { EventSourceFrame frame = new EventSourceFrame(); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.setVisible(true); }}

/** Фрейм, содержащий панель, которая допускает рисование.*/class EventSourceFrame extends JFrame{ public EventSourceFrame() { setTitle("EventSourceTest"); setSize(DEFAULT_WIDTH, DEFAULT_HEIGHT);

// Добавление панели к фрейму.

final PaintCountPanel panel = new PaintCountPanel(); add(panel);

panel.addPropertyChangeListener(new PropertyChangeListener() { public void propertyChange(PropertyChangeEvent event) { setTitle("EventSourceTest - " + event.getNewValue()); } }); }

public static final int DEFAULT_WIDTH = 400; public static final int DEFAULT_HEIGHT = 200;}

/** Панель, подсчитывающая число перерисовок.*/

Page 59: 8354 Глава 8. Обработка событий Например, если пользователь щелкает на кнопке с надписью Yellow, вызывается

Реализация источников событий 407

class PaintCountPanel extends JPanel{ public void paintComponent(Graphics g) { int oldPaintCount = paintCount; paintCount++; firePropertyChangeEvent(new PropertyChangeEvent(this, "paintCount", oldPaintCount, paintCount)); super.paintComponent(g); }

/** Добавление обработчика событий. @param listener Обработчик */ public void addPropertyChangeListener (PropertyChangeListener listener) { listenerList.add(PropertyChangeListener.class, listener); }

/** Удаление обработчика событий. @param listener Обработчик */ public void removePropertyChangeListener (PropertyChangeListener listener) { listenerList.remove(PropertyChangeListener.class, listener); }

public void firePropertyChangeEvent(PropertyChangeEvent event) { EventListener[] listeners = listenerList.getListeners (PropertyChangeListener.class); for (EventListener l : listeners) ((PropertyChangeListener) l).propertyChange(event); }

public int getPaintCount() { return paintCount; }

private int paintCount;}

javax.swing.event.EventListenerList 1.2

• void add(Class t, EventListener l)

Добавляет обработчик событий и его класс в список. Класс хранится таким обра�зом, чтобы методы, генерирующие события, могли выбирать требуемый класс.Обычно данный метод используется при построении метода addXxxListener().

Page 60: 8354 Глава 8. Обработка событий Например, если пользователь щелкает на кнопке с надписью Yellow, вызывается

408 Глава 8. Обработка событий

public void addXxxListener(XxxListener l) { listenerList.add(XxxListener.class, l); }

Параметры t Тип обработчикаl Обработчик

• void remove(Class t, EventListener l)

Удаляет обработчик события и его класс из списка. Обычно данный метод ис�пользуется для создания метода removeXxxListener().

public void removeXxxListener(XxxListener l) { listenerList.remove(XxxListener.class, l); }

Параметры t Тип обработчикаl Обработчик

• EventListener[] getListeners(Class t) 1.3

Возвращает массив, содержащий всех обработчиков заданного типа. Гаранти�руется, что метод не вернет значение null.

• Object[] getListenerList()

Возвращает массив, в котором элементами с четными индексами являютсяклассы, а с нечетными — объекты�обработчики. Гарантируется, что метод невернет значение null.

java.beans.PropertyChangeEvent 1.1

• PropertyChangeEvent(Object source, String name, ObjectoldValue, Object newValue)

Создает событие, соответствующее изменению свойства.

Параметры source Источник события — объект, сообщающийоб изменении свойства

name Имя свойстваoldValue Значение свойства до измененияnewValue Значение свойства после изменения

java.beans.PropertyChangeListener 1.1

• void propertyChange(PropertyChangeEvent event)

Вызывается при изменении значения свойства.