1 В избранное 0 Ответвления 0

OSCHINA-MIRROR/wizardforcel-thinking-in-java-zh

Присоединиться к Gitlife
Откройте для себя и примите участие в публичных проектах с открытым исходным кодом с участием более 10 миллионов разработчиков. Приватные репозитории также полностью бесплатны :)
Присоединиться бесплатно
В этом репозитории не указан файл с открытой лицензией (LICENSE). При использовании обратитесь к конкретному описанию проекта и его зависимостям в коде.
Клонировать/Скачать
13.16 新型AWT.md 120 КБ
Копировать Редактировать Web IDE Исходные данные Просмотреть построчно История
Отправлено 11.03.2025 09:15 d56454c

13.16 Новое AWT

Одним из значительных изменений в Java 1.1 стало совершенствование новой модели AWT. Большинство изменений связано с использованием нового событийного моделирования в Java 1.1: старая модель была плохой, громоздкой и небезопасной для объектно-ориентированного программирования, тогда как новая модель является одной из лучших, которую я видел. Бывает трудно понять, как такая плохая (старое AWT) и такая хорошая (новая событийная модель) могут происходить из одного и того же источника. Новый подход к событиям кажется прекращённым спором, поэтому он больше не становится препятствием и легко проникает в наше сознание; вместо этого это инструмент, который помогает нам проектировать наши системы. Это также ключевой аспект Java Beans, о котором мы будем говорить позже в этой главе.

Новая методология проектирования объектов представляет их как "источники событий" и "приёмники событий", что отличается от старого небезопасного способа AWT, использующего последовательность условий. Как мы увидим, внутренние классы используются для интеграции новых событий, основанных на объектно-ориентированном состоянии. Кроме того, события теперь представляются как часть классовой иерархии, а не как единственный класс, и мы можем создавать свои собственные типы событий.Мы также заметим, что если мы будем использовать старую версию AWT, Java 1.1 может создать некоторые странные преобразования имен. Например, метод setsize() был заменен на resize(). Когда мы будем изучать Java Beans, это станет более логичным, поскольку Beans используют уникальную систему названий. Имена должны быть изменены, чтобы создать новые стандартные компоненты AWT в Beans.

Поддержка операций с буфером обмена также была добавлена в Java Yöntem "setsize()" был заменён на "resize()". Когда мы будем изучать Java Beans, это станет более логичным, поскольку Beans используют уникальную систему названий. Имена должны быть изменены, чтобы создать новые стандартные компоненты AWT в Beans.

Поддержка операций с буфером обмена также была добавлена в Java 1.1, хотя поддержка перетаскивания будет "добавлена в следующих версиях". Мы можем получить доступ к цветовым схемам рабочего стола, чтобы наш Java был согласован со всем остальным на рабочем столе. Поддерживаются выпадающие меню, а также улучшено представление изображений и графики. Также поддерживаются действия мыши. Есть простые API для печати и базовая поддержка прокрутки.

13.16.1 Новая событийная модельКомпоненты новой событийной модели могут начинать события. Каждый тип события представлен отдельным классом. После начала события, оно передается одному или нескольким приемникам событий. Таким образом, источник события и адрес обработчика события могут быть разделены. Каждый обработчик событий представляет собой объект-класс, который реализует определенный тип интерфейса обработчика событий. Поэтому, как разработчику программ, нам нужно создать объект обработчика и зарегистрировать его в компоненте, вызывающем события. Компонент event-firing вызывает метод addXXXListener(), чтобы завершить регистрацию, где XXX описывает тип события, которое принимается. Мы можем легко понять, что методы с названием addXXXListener() информируют нас о том, какие типы событий могут быть обработаны; если мы попытаемся получить события, то столкнемся с ошибками при компиляции. Java Beans также используют методы с названием addListener, чтобы определить, какой код может быть запущен. Логика всех наших событий будет помещена в класс-приемник. Когда мы создаем класс-приемник, единственным ограничением является необходимость выполнения специального интерфейса.Мы можем создать глобальный класс-приёмник, что полезно при использовании внутренних классов, не только потому, что они предоставляют теоретическую группу классов-приёмников для UI или бизнес-логики, но также потому, что (как мы увидим позже в этой главе) внутренний класс поддерживает ссылку на его родительский объект, обеспечивая хороший способ вызова методов через границы классов и подсистем.Пример с простыми кнопками поможет проиллюстрировать это. В то же время подумайте о различиях между примером Button2.java, приведённым ранее в этой главе, и этим примером.

//: Button2New.java
// Понимание нажатий кнопок
import java.awt.*;
import java.awt.event.*; // Должно быть добавлено
import java.applet.*;

public class Button2New extends Applet {
  Button
    b1 = new Button("Кнопка 1"),
    b2 = new Button("Кнопка 2");
  public void init() {
    b1.addActionListener(new B1());
    b2.addActionListener(new B2());
    add(b1);
    add(b2);
  }
  class B1 implements ActionListener {
    public void actionPerformed(ActionEvent e) {
      getAppletContext().showStatus("Кнопка 1");
    }
  }
  class B2 implements ActionListener {
    public void actionPerformed(ActionEvent e) {
      getAppletContext().showStatus("Кнопка 2");
    }
  }
  /* Старый способ:
  public boolean action(Event evt, Object arg) {
    if(evt.target.equals(b1))
      getAppletContext().showStatus("Кнопка 1");
    else if(evt.target.equals(b2))
      getAppletContext().showStatus("Кнопка 2");
    // Дать базовому классу обработать это:
    else
      return super.action(evt, arg);
    return true; // Мы обработали это здесь
  }
  */
} ///:~

Мы можем сравнить эти два подхода, старый код представлен как комментарий слева. В методе init() есть одна единственная правка — добавление двух строк:

b1.addActionListener(new B1());
b2.addActionListener(new B2());
```При нажатии кнопки метод `addActionListener()` сообщает объекту кнопки, что она активирована. Классы `B1` и `B2` являются внутренними классами, реализующими интерфейс `ActionListener`. Этот интерфейс включает единственный метод `actionPerformed()` (что означает, что этот метод будет выполнен при активации события). Обратите внимание, что метод `actionPerformed()` не является обычным событием, а скорее специальным типом события `ActionEvent`. Если нам требуется извлечь информацию из специального события `ActionEvent`, нам не нужно специально тестировать и преобразовывать параметры. Самым лучшим для программиста является то, что `actionPerformed()` очень прост в использовании. Это метод, который можно вызвать. В отличие от старого метода `action()`, где нам приходилось указывать, что произошло и какие действия следует предпринять, а также беспокоиться о том, чтобы вызвать версию базового класса `action()` и вернуть значение, указывающее, было ли событие обработано, в новой модели событий мы знаем, что все проверки событий выполняются автоматически, поэтому нам не нужно указывать, что произошло; мы просто указываем это, и оно автоматически происходит. Если мы ещё не предлагали использовать новый метод для замены старого, мы скоро сделаем это.## 13.16.2 События и типы приемниковВсе компоненты AWT были изменены таким образом, чтобы они содержали методы `addXXXListener()` и `removeXXXListener()`, благодаря чему конкретные типы слушателей могут быть добавлены и удалены из каждого компонента. Мы заметим, что `XXX` представляет собой параметры метода, такие как `addFooListener(FooListener fl)`. Ниже приведена таблица, которая сводит краткий список событий, слушателей, методов добавления и удаления:

| События, слушатели событий и методы добавления/удаления | Компоненты, поддерживающие это событие |
|--------------------------------------------------------|---------------------------------------|
| `ActionEvent`                                          | `addActionListener()`                 |
|                                                        | `removeActionListener()`              |
|                                                        | `Button`, `List`, `TextField`, `MenuItem` и его производные, включая `CheckboxMenuItem`, `Menu` и `PopupMenu` |
| `AdjustmentEvent`                                      | `addAdjustmentListener()`            |
|                                                        | `removeAdjustmentListener()`          |
|                                                        | `Scrollbar`                           |
|                                                        | Любой объект, реализующий интерфейс `Adjustable` |
| `ComponentEvent`                                       | `addComponentListener()`             |
|                                                        | `removeComponentListener()`           |
|                                                        | `Component` и его производные, включая `Button`, `Canvas`, `Checkbox`, `Choice`, `Container`, `Panel`, `Applet`, `ScrollPane`, `Window`, `Dialog`, `FileDialog`, `Frame`, `Label`, `List`, `Scrollbar`, `TextArea` и `TextField` |
| `ContainerEvent`                                       | `addContainerListener()`             |
|                                                        | `removeContainerListener()`           |
|                                                        | `Container` и его производные, включая `Panel`, `Applet`, `ScrollPane`, `Window`, `Dialog`, `FileDialog` и `Frame` |
| `FocusEvent`                                           | `addFocusListener()`                 |
|                                                        | `removeFocusListener()`               |
|                                                        | `Component` и его производные, включая `Button`, `Canvas`, `Checkbox`, `Choice`, `Container`, `Panel`, `Applet`, `ScrollPane`, `Window`, `Dialog`, `FileDialog`, `Frame`, `Label`, `List`, `Scrollbar`, `TextArea` и `TextField` |
| `KeyEvent`                                             | `addKeyListener()`                   |

| Слушатель событий | Метод добавления слушателя |
|-------------------|----------------------------|
| `KeyListener`     | `addKeyListener()`         |+   `addKeyListener()`
 +   `removeKeyListener()`
     +   `Component` и его производные, включая `Button`, `Canvas`, `Checkbox`, `Choice`, `Container`, `Panel`, `Applet`, `ScrollPane`, `Window`, `Dialog`, `FileDialog`, `Frame`, `Label`, `List`, `Scrollbar`, `TextArea` и `TextField`
 +   `MouseEvent` (`для нажатий и перемещений`)
 +   `MouseListener`
 +   `addMouseListener()`
 +   `removeMouseListener()`
     +   `Component` и его производные, включая `Button`, `Canvas`, `Checkbox`, `Choice`, `Container`, `Panel`, `Applet`, `ScrollPane`, `Window`, `Dialog`, `FileDialog`, `Frame`, `Label`, `List`, `Scrollbar`, `TextArea` и `TextField`
 +   `MouseEvent` (`для нажатий и перемещений`)
 +   `MouseMotionListener`
 +   `addMouseMotionListener()`
 +   `removeMouseMotionListener()`
     +   `Компонент` и его производные, включая `Кнопка`, `Полотно`, `Чекбокс`, `Выбор`, `Контейнер`, `Панель`, `Апплет`, `СкроллПанель`, `Окно`, `Диалог`, `ФайлДиалог`, `Рамка`, `Метка`, `Список`, `Полоса прокрутки`, `Текстовая область` и `Текстовое поле`
 +   `СобытиеОкна`
 +   `ПрослушивательОкна`
 +   `ДобавитьПрослушивателюОкна()`
 +   `УдалитьПрослушивателяОкна()`
     +   `Окно` и его производные, включая `Диалог`, `ФайлДиалог` и `Рамка`
 +   `СобытиеТекста`
 +   `ПрослушивательТекста`
 +   `ДобавитьПрослушивателюТекста()`
 +   `УдалитьПрослушивателяТекста()`
     +   Любое производное от `TextComponent`, включая `TextArea` и `TextField`: Хотя это может показаться так, на самом деле нет события `MouseMotionEvent` (событие движения мыши). Одиночный клик и движение объединены в событие `MouseEvent`, поэтому альтернативное поведение этого события в таблице не является ошибкой.Как видно, каждый тип компонента поддерживает определённые типы событий. Это помогает нам выявить события, поддерживаемые каждым компонентом, как показано в следующей таблице: | **Компонентный тип** | **Поддерживаемые события** |
 |----------------------|----------------------------|
 | `Adjustable`         | `AdjustmentEvent`          |
 | `Applet`             | `ContainerEvent`, `FocusEvent`, `KeyEvent`, `MouseEvent`, `ComponentEvent` |
 | `Button`             | `ActionEvent`, `FocusEvent`, `KeyEvent`, `MouseEvent`, `ComponentEvent` |
 | `Canvas`             | `FocusEvent`, `KeyEvent`, `MouseEvent`, `ComponentEvent` |
 | `Checkbox`           | `ItemEvent`, `FocusEvent`, `KeyEvent`, `MouseEvent`, `ComponentEvent` |
 | `CheckboxMenuItem`   | `ActionEvent`, `ItemEvent` |
 | `Choice`             | `ItemEvent`, `FocusEvent`, `KeyEvent`, `MouseEvent`, `ComponentEvent` |
 | `Component`          | `FocusEvent`, `KeyEvent`, `MouseEvent`, `ComponentEvent` |
 | `Container`          | `ContainerEvent`, `FocusEvent`, `KeyEvent`, `MouseEvent`, `ComponentEvent` |
 | `Dialog`             | `ContainerEvent`, `WindowEvent`, `FocusEvent`, `KeyEvent`, `MouseEvent`, `ComponentEvent` |
 | `FileDialog`         | `ContainerEvent`, `WindowEvent`, `FocusEvent`, `KeyEvent`, `MouseEvent`, `ComponentEvent` |
 | `Frame`              | `ContainerEvent`, `WindowEvent`, `FocusEvent`, `KeyEvent`, `MouseEvent`, `ComponentEvent` |
 | `Label`              | `FocusEvent`, `KeyEvent`, `MouseEvent`, `ComponentEvent` |
 | `List`               | `ActionEvent`, `FocusEvent`, `KeyEvent`, `MouseEvent`, `ItemEvent`, `ComponentEvent` |
 | `Menu`               | `ActionEvent` |
 | `MenuItem`           | `ActionEvent` |
 | `Panel`              | `ContainerEvent`, `FocusEvent`, `KeyEvent`, `MouseEvent`, `ComponentEvent` |
 | `PopupMenu`          | `ActionEvent` |
 | `Scrollbar`          | `AdjustmentEvent`, `FocusEvent`, `KeyEvent`, `MouseEvent`, `ComponentEvent` |
 | `ScrollPane`         | `ContainerEvent`, `FocusEvent`, `KeyEvent`, `MouseEvent`, `ComponentEvent` |
 | `TextArea`           | `TextEvent`, `FocusEvent`, `KeyEvent`, `MouseEvent`, `ComponentEvent` |
 | `TextComponent`      | `TextEvent`, `FocusEvent`, `KeyEvent`, `MouseEvent`, `ComponentEvent` |
 | `TextField`          | `ActionEvent`, `TextEvent`, `FocusEvent`, `KeyEvent`, `MouseEvent`, `ComponentEvent` || `Окно`               | `Контейнерное событие`, `Событие окна`, `Событие фокуса`, `Клавишное событие`, `Мышечное событие`, `Событие компонента` | Как только вы узнаете, какие события поддерживаются определенным компонентом, вам больше ничего искать не придется для того, чтобы отреагировать на это событие. Вам просто нужно сделать следующее:(1) Получите имя класса события и удалите слово `Event`. В оставшейся части добавьте слово `Listener`. Это будет интерфейс слушателя, который вам нужно реализовать в вашем внутреннем классе.

(2) Реализуйте вышеупомянутый интерфейс, создав методы для событий, которые вы хотите захватывать. Например, если вы хотите захватить перемещение мыши, вам потребуется реализовать метод `mouseMoved()` для интерфейса `MouseMotionListener` (конечно, вам также потребуется реализовать некоторые другие методы, но здесь есть более простой путь, о котором мы расскажем чуть позже).

(3) Создайте объект для слушателя, созданного на шаге 2. Зарегистрируйте его с помощью вашего компонента и метода, добавив префикс `add` к имени слушателя. Например, используйте `addMouseMotionListener()`.

Ниже приведена таблица, которая представляет собой сводку всех слушательских интерфейсов:

| Интерфейс | Описание |
| --- | --- |
| ActionListener | Обрабатывает события нажатия кнопок |
| FocusListener | Обрабатывает события фокусировки |
| KeyListener | Обрабатывает события клавиш |
| MouseListener | Обрабатывает события мыши |
| MouseMotionListener | Обрабатывает события движения мыши |
| WindowListener | Обрабатывает события окна |

Эта таблица поможет вам быстро найти нужный интерфейс слушателя для конкретного типа событий.| **Слушательные интерфейсы** | **Методы в интерфейсе** |
|-----------------------------|-------------------------|
| `ActionListener`            | `actionPerformed(ActionEvent)` |
| `AdjustmentListener`        | `adjustmentValueChanged(AdjustmentEvent)` |
| `ComponentListener`         |                         |
| `ComponentAdapter`          | `componentHidden(ComponentEvent)`<br>`componentShown(ComponentEvent)`<br>`componentMoved(ComponentEvent)`<br>`componentResized(ComponentEvent)` |
| `ContainerListener`         |                         |
| `ContainerAdapter`          | `componentAdded(ContainerEvent)`<br>`componentRemoved(ContainerEvent)` |
| `FocusListener`             |                         |
| `FocusAdapter`              | `focusGained(FocusEvent)`<br>`focusLost(FocusEvent)` |
| `KeyListener`               |                         |
| `KeyAdapter`                | `keyPressed(KeyEvent)`<br>`keyReleased(KeyEvent)`<br>`keyTyped(KeyEvent)` |
| `MouseListener`             |                         |
| `MouseAdapter`              | `mouseClicked(MouseEvent)`<br>`mouseEntered(MouseEvent)`<br>`mouseExited(MouseEvent)`<br>`mousePressed(MouseEvent)`<br>`mouseReleased(MouseEvent)` |
| `MouseMotionListener`       |                         |
| `MouseMotionAdapter`        | `mouseDragged(MouseEvent)`<br>`mouseMoved(MouseEvent)` |
| `WindowListener`            |                         |
| `WindowAdapter`             | `windowOpened(WindowEvent)`<br>`windowClosing(WindowEvent)`<br>`windowClosed(WindowEvent)`<br>`windowActivated(WindowEvent)`<br>`windowDeactivated(WindowEvent)`<br>`windowIconified(WindowEvent)`<br>`windowDeiconified(WindowEvent)` |
| `ItemListener`              | `itemStateChanged(ItemEvent)` |
| `TextListener`              | `textValueChanged(TextEvent)` |Примеры использования этих интерфейсов помогут вам лучше понять, как они работают и как их использовать.### Упрощение работы с помощью адаптеров приемников

В таблице выше можно заметить, что некоторые приемники имеют только один метод. Выполнение этих методов не имеет значения, так как мы вызываем их только тогда, когда нам требуется специальный метод. Однако, если приемник имеет несколько методов, использование его становится менее удобным. Например, нам приходится всегда выполнять какие-то вещи; когда создаем приложение, мы предоставляем `WindowListener` для кадров, чтобы могли вызвать `System.exit(0)` при получении события `windowClosing()`, чтобы завершить работу приложения. Но поскольку `WindowListener` является интерфейсом, нам приходится реализовать все остальные методы, даже если они не используются для обработки событий. Это действительно неприятно.

Чтобы решить эту проблему, каждый приемник с более чем одним методом может иметь свой адаптер, название которого можно найти в таблице выше. Каждый адаптер предоставляет методы по умолчанию для каждого метода интерфейса. (`WindowAdapter` не имеет метода по умолчанию для `windowClosing()`.) Все, что нам нужно сделать, это наследовать от адаптера и переопределить только те методы, которые требуют изменения. Например, типичный `WindowListener` используется следующим образом:```java
class MyWindowListener extends WindowAdapter {
  public void windowClosing(WindowEvent e) {
    System.exit(0);
  }
}

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

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

На первый взгляд всё выглядит правильно, но на самом деле ничего не работает. Компиляция и выполнение всех событий проходят успешно — просто закрытие окна не приведёт к завершению программы. Обнаружили ли вы ошибку? В имени метода: WindowClosing(), а не windowClosing(). Просто одна ошибка в регистре букв привела к появлению нового метода. Но этот метод не вызывается при закрытии окна, поэтому, конечно же, ничего не происходит.

13.16.3 Создание окон и апплетов с использованием Java 1.1 AWT

Часто нам требуется создать класс, который может быть использован как окно, так и апплет. Для этого достаточно добавить метод main() для апплета, чтобы он создал экземпляр апплета внутри Frame (окна). Как простой пример, давайте рассмотрим, как можно изменить Button2New.java, чтобы она работала как приложение и апплет одновременно: //: Button2NewB.java // Приложение и апплет import java.awt.*; import java.awt.event.*; // Должно быть добавлено это import java.applet.*;java public class Button2NewB extends Applet { Button b1 = new Button("Кнопка 1"), b2 = new Button("Кнопка 2"); TextField t = new TextField(20); public void init() { b1.addActionListener(new B1()); b2.addActionListener(new B2()); add(b1); add(b2); add(t); } class B1 implements ActionListener { public void actionPerformed(ActionEvent e) { t.setText("Кнопка 1"); } } class B2 implements ActionListener { public void actionPerformed(ActionEvent e) { t.setText("Кнопка 2"); } } // Для закрытия приложения: static class WL extends WindowAdapter { public void windowClosing(WindowEvent e) { System.exit(0); } } // Метод main() для приложения: public static void main(String[] args) { Button2NewB applet = new Button2NewB(); Frame aFrame = new Frame("Button2NewB"); aFrame.addWindowListener(new WL()); aFrame.add(applet, BorderLayout.CENTER); aFrame.setSize(300, 200); applet.init(); applet.start(); aFrame.setVisible(true); } } ///:~ ```Внутренний класс WL и метод `main()` являются единственными двумя элементами, добавленными в апплет. Остальная часть апплета остаётся нетронутой. В действительности, мы обычно копируем и вставляем эти два небольших изменения в наши апплеты (помните, что создание внутреннего класса обычно требует внешнего класса для его обработки, что позволяет нам статически удалить эту необходимость). Мы видим, что в методе `main()`, апплет явно инициализируется и запускается, поскольку браузер не может эффективно выполнить его в этом примере. Конечно, это не предоставляет полной реализации вызова `stop()` и `destroy()` браузером, но для большинства случаев это приемлемо. Если это становится проблемой, мы можем:

(1) сделать апплет статическим классом (вместо локально изменяемого main()), а затем:

(2) вызвать applet.stop() и applet.destroy() перед вызовом System.exit() в методе WindowAdapter.windowClosing().

Обратите внимание на последнюю строчку:

aFrame.setVisible(true);
```Это изменение в Java 1.1 AWT. Метод `show()` больше не поддерживается, и вместо него используется `setVisible(true)`. Когда мы будем учиться Java Beans позже в этой главе, эти легко меняющиеся методы станут более понятными. Этот пример также использует `TextField` для изменения вместо вывода в консоль или состояние браузера. При разработке программы есть ограничение, что как фрагменты программы, так и приложения должны выбирать структуру входных и выходных данных в зависимости от того, как они выполняются.Здесь показаны другие небольшие новые возможности Java 1.1 AWT. Теперь нам больше не требуется использовать потенциально ошибочное использование строк для указания расположения компонентов в `BorderLayout`. Когда мы добавляем элемент в `BorderLayout` версии Java 1.1, мы можем сделать это следующим образом:

```java
aFrame.add(applet, BorderLayout.CENTER);

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

(2) Преобразование приемника окна в анонимный класс

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

aFrame.addWindowListener(new WL());

становится такой:

aFrame.addWindowListener(
  new WindowAdapter() {
    public void windowClosing(WindowEvent e) {
      System.exit(0);
    }
  });
```Одним из преимуществ этого подхода является то, что он не требует использования другого имени класса. Нам следует самостоятельно решать, делает ли это код более понятным или сложным. Тем не менее, анонимные внутренние классы будут обычно использоваться в качестве приемников окна в остальной части этой книги.(3) Упаковка фрагмента программы в JAR-файл

Одним из важнейших применений JAR-файлов является улучшение загрузки фрагментов программы. В Java 1.0 версии люди предпочитали помещать свой код в единственный класс фрагмента программы, чтобы клиенты могли загружать код фрагмента программы с одного сервера. Но это приводило к запутанному и труднозапоминающему коду (естественно, и к проблемам с обслуживанием), а также к невозможности сжатия файлов классов, поэтому скорость загрузки никогда не была высокой.

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

Рассмотрим вышеупомянутый пример; этот пример выглядит как `Button2NewB`, являющийся одним классом, но фактически он содержит три внутренних класса, всего четыре. Каждый раз, когда мы компилируем программу, мы используем эту команду для упаковки её в JAR-файл:

```sql
jar cf Button2NewB.jar *.class

Это предполагает наличие только одного файла .class, который находится в текущей директории, один из которых был создан из Button2NewB.java (в противном случае мы получили бы специальное упаковывание).

Теперь мы можем создать HTML-страницу, использующую новый файл-ярлык для указания JAR-файла, как показано ниже:```

<title>Пример приложения Button2NewB</title> ```

Любой другой контент, связанный с метками апплета внутри HTML-файла, остаётся без изменений.

13.16.4 Ещё раз рассмотрим старый пример

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

(1) Текстовое поле

Этот пример аналогичен TextField1.java, но он добавляет явно дополнительное поведение:```java //: TextNew.java // Текстовые поля с событиями Java 1.1 import java.awt.; import java.awt.event.; import java.applet.*;

public class TextNew extends Applet { Button b1 = new Button("Получить текст"), b2 = new Button("Установить текст");

TextField
    t1 = new TextField(30),
    t2 = new TextField(30),
    t3 = new TextField(30);

String s = new String();

public void init() {
    b1.addActionListener(new B1());
    b2.addActionListener(new B2());
    t1.addTextListener(new T1());
    t1.addActionListener(new T1A());
    t1.addKeyListener(new T1K());

    add(b1);
    add(b2);
    add(t1);
    add(t2);
    add(t3);
}

class T1 implements TextListener {
    public void textValueChanged(TextEvent e) {
        t2.setText(t1.getText());
    }
}

class T1A implements ActionListener {
    private int count = 0;

    public void actionPerformed(ActionEvent e) {
        t3.setText("t1 событие " + count++);
    }
}

class T1K extends KeyAdapter {
    public void keyTyped(KeyEvent e) {
        String ts = t1.getText();
        if (e.getKeyCode() == KeyEvent.VK_BACK_SPACE) {
            // Убедитесь, что это не пустое поле:
            if (ts.length() > 0) {
                ts = ts.substring(0, ts.length() - 1);
                t1.setText(ts);
            }
        } else {
            t1.setText(
                    t1.getText() +
                            Character.toUpperCase(
                                    e.getKeyChar()));
        }
        t1.setCaretPosition(t1.getText().length());
        // Остановите обычный символ от появления:
        e.consume();
    }
}

class B1 implements ActionListener {
    public void actionPerformed(ActionEvent e) {
        s = t1.getSelectedText();
        if (s.length() == 0)
            s = t1.getText();
        t1.setEditable(true);
    }
}

class B2 implements ActionListener {
    public void actionPerformed(ActionEvent e) {
        t1.setText("Значение установлено кнопкой 2: " + s);
        t1.setEditable(false);
    }
}

public static void main(String[] args) {
    TextNew applet = new TextNew();
    Frame aFrame = new Frame("TextNew");
    aFrame.addWindowListener(
            new WindowAdapter() {
                public void windowClosing(WindowEvent e) {
                    System.exit(0);
                }
            });
}

}

    );
     aFrame.add(applet, BorderLayout.CENTER);
     aFrame.setSize(300, 200);
     applet.init();
     applet.start();
     aFrame.setVisible(true);
   }
 }
 ///:~
 ```
 
```Когда активируется приемник действий `TextField t1`, `TextField t3` становится местом, которое следует отчитать. Мы замечаем, что приемник действий активируется только при нажатии клавиши `Enter`.````TextField t1` имеет нескольких приемников. Приемник `T1` копирует всё текстовое содержимое из `t1` в `t2`, заставляя все строки преобразовываться в верхний регистр. Мы обнаружили, что эти две операции выполняются одновременно, и если мы добавляем приемник `T1K` перед добавлением приемника `T1`, он становится менее значимым: все строки внутри поля ввода будут автоматически преобразовываться в верхний регистр. Это выглядит так, будто события клавиатуры активируются до событий компонента ввода текста, и если нам требуется сохранить исходное состояние строк в `t2`, нам придётся выполнить специальные действия.

Приемник `T1K` также имеет некоторые интересные свойства. Нам необходимо тестировать клавишу `Backspace` (поскольку теперь мы контролируем каждое событие) и выполнять удаление. Указатель ввода (`caret`) должен явно устанавливаться в конце поля ввода; в противном случае он не будет работать так, как нам это нужно. В конце концов, чтобы предотвратить обработку исходной строки стандартными механизмами, событие должно быть "потраченным" методом `consume()` объекта события. Это сообщает системе прекратить активацию остальных специальных обработчиков событий.

Этот пример также демонстрирует множество преимуществ использования внутренних классов. Обратите внимание на следующий внутренний класс:```java
class T1 implements TextListener {
    public void textValueChanged(TextEvent e) {
        t2.setText(t1.getText());
    }
}
```

`t1` и `t2` не являются частью `T1`, и до сих пор они были легко понятны, без каких-либо особых ограничений. Это потому, что объект внутреннего класса автоматически получает ссылку на внешний объект, создавший его, поэтому мы можем обращаться к методам и контенту внешнего класса. Как мы видели, это очень удобно (замечание ).

: Это также решает проблему "callback", не требуя добавления в Java раздражающих "указателей методов".

(2) Текстовая область

Самый значительный изменения в `Text Area` в версии Java 1.1  это прокрутка. Для конструктора `TextArea` можно сразу контролировать наличие прокрутки: горизонтальной, вертикальной, обоих или ни одной. Этот пример исправляет программу `TextArea1.java` из версии Java 1.0 и демонстрирует конструктор прокрутки в версии Java 1.1:```java
//: TextAreaNew.java
// Control over scrollbars with the TextArea component in Java 1.1
import java.awt.*;
import java.awt.event.*;
import java.applet.*;

public class TextAreaNew extends Applet {
    Button b1 = new Button("Text Area 1");
    Button b2 = new Button("Text Area 2");
    Button b3 = new Button("Replace text");
    Button b4 = new Button("Insert text");
    TextArea t1 = new TextArea("t1", 1, 30);
    TextArea t2 = new TextArea("t2", 4, 30);
    TextArea t3 = new TextArea("t3", 1, 30,
            TextArea.SCROLLBARS_NONE);
    TextArea t4 = new TextArea("t4", 10, 10,
            TextArea.SCROLLBARS_VERTICAL_ONLY);
    TextArea t5 = new TextArea("t5", 4, 30,
            TextArea.SCROLLBARS_HORIZONTAL_ONLY);
    TextArea t6 = new TextArea("t6", 10, 10,
            TextArea.SCROLLBARS_BOTH);

    public void init() {
        b1.addActionListener(new B1L());
        add(b1);
        add(t1);
        b2.addActionListener(new B2L());
        add(b2);
        add(t2);
        b3.addActionListener(new B3L());
        add(b3);
        b4.addActionListener(new B4L());
        add(b4);
        add(t3); add(t4); add(t5); add(t6);
    }

    class B1L implements ActionListener {
        public void actionPerformed(ActionEvent e) {
            t5.append(t1.getText() + "\n");
        }
    }

    class B2L implements ActionListener {
        public void actionPerformed(ActionEvent e) {
            t2.setText("Inserted by button 2");
            t2.append(": " + t1.getText());
            t5.append(t2.getText() + "\n");
        }
    }

    class B3L implements ActionListener {
        public void actionPerformed(ActionEvent e) {
``````markdown
      строка s = "Замена";
       t2.replaceRange(s, 3, 3 + s.length());
     }
   }
   класс B4L реализует ActionListener {
     публичный voidActionPerformed(ActionEvent e) {
       t2.insert("Вставлено", 10);
     }
   }
   публичный статический void основной(строка[] аргументы) {
     TextAreaNew апплет = новый TextAreaNew();
     Окно aFrame = новое Окно("TextAreaNew");
     aFrame.addWindowListener(
       новое WindowAdapter() {
         публичный void windowClosing(WindowEvent e) {
           System.exit(0);
         }
       });
     aFrame.add(апплет, BorderLayout.CENTER);
     aFrame.setSize(300, 725);
     апплет.init();
     апплет.start();
     aFrame.setVisible(истинно);
   }
} ///:~
```
Мы заметили, что можем контролировать полосы прокрутки только при создании `TextArea`. Также стоит отметить, что даже если `TE AR` не имеет полос прокрутки, они будут отключены (это поведение можно проверить, запустив этот пример).

(3) Чекбоксы и радиокнопки Как было отмечено ранее, флажки и радиокнопки создаются из одного и того же класса. Радиокнопки и флажки немного различаются тем, что флажки помещаются в `CheckboxGroup`. В обоих случаях интересуют события типа `ItemEvent`, для которых мы создаем слушатель событий `ItemListener`. При работе с группой флажков или радиокнопок, у нас есть несколько вариантов. Мы можем создать внутренний класс для обработки событий каждого флажка отдельно, либо создать один внутренний класс, который будет проверять, какой флажок был выбран, и регистрировать объект этого класса для каждого элемента. Ниже приведены примеры обоих подходов:

```java
//: RadioCheckNew.java
// Радиокнопки и флажки в Java 1.1
import java.awt.*;
import java.awt.event.*;
import java.applet.*;

public class RadioCheckNew extends Applet {
  TextField t = new TextField(30);
  Checkbox[] cb = {
    new Checkbox("Флажок 1"),
    new Checkbox("Флажок 2"),
    new Checkbox("Флажок 3")
  };
  CheckboxGroup g = new CheckboxGroup();
  Checkbox
    cb4 = new Checkbox("четыре", g, false),
    cb5 = new Checkbox("пять", g, true),
    cb6 = new Checkbox("шесть", g, false);

  public void init() {
    t.setEditable(false);
    add(t);
    ILCheck il = new ILCheck();
    for(int i = 0; i < cb.length; i++) {
      cb[i].addItemListener(il);
      add(cb[i]);
    }
    cb4.addItemListener(new IL4());
    cb5.addItemListener(new IL5());
    cb6.addItemListener(new IL6());
    add(cb4); add(cb5); add(cb6);
  }
}
``````md
  // Обработка источника события:
  class ILCheck implements ItemListener {
    public void itemStateChanged(ItemEvent e) {
      for(int i = 0; i < cb.length; i++) {
        if(e.getSource().equals(cb[i])) {
          t.setText("Флажок " + (i + 1));
          return;
        }
      }
    }
  }

  // против отдельного класса для каждого элемента:
  class IL4 implements ItemListener {
    public void itemStateChanged(ItemEvent e) {
      t.setText("Радиокнопка четыре");
    }
  }

  class IL5 implements ItemListener {
    public void itemStateChanged(ItemEvent e) {
      t.setText("Радиокнопка пять");
    }
  }

  class IL6 implements ItemListener {
    public void itemStateChanged(ItemEvent e) {
      t.setText("Радиокнопка шесть");
    }
  }

  public static void main(String[] args) {
    RadioCheckNew applet = new RadioCheckNew();
    Frame aFrame = new Frame("RadioCheckNew");
    aFrame.addWindowListener(
      new WindowAdapter() {
        public void windowClosing(WindowEvent e) {
          System.exit(0);
        }
      });
    aFrame.add(applet, BorderLayout.CENTER);
    aFrame.setSize(300, 200);
    applet.init();
    applet.start();
    aFrame.setVisible(true);
  }
} ///:~

`ILCheck` имеет преимущество автоматической настройки при увеличении или уменьшении количества флажков. Конечно, использование этого подхода для радио-кнопок также эффективно. Однако он используется только тогда, когда наша логика достаточно универсальна для поддержки такого метода. Если мы объявляем определённый сигнал  мы будем использовать независимый класс-приёмник, в противном случае мы завершим цепочку условий.

(4) Выпадающий список

Выпадающие списки в Java 1.1 используют `ItemListener`, чтобы сообщить нам о том, что выбор был изменён:
``````java
//: ChoiceNew.java
// Выпадающие списки с использованием Java 1.1
import java.awt.*;
import java.awt.event.*;
import java.applet.*;

public class ChoiceNew extends Applet {
  String[] description = { "Энергичный", "Тупой",
    "Непокорный", "Блестящий", "Сонливый",
    "Страшливый", "Румяный", "Гнилостный" };
  TextField t = new TextField(100);
  Choice c = new Choice();
  Button b = new Button("Добавить элементы");
  int count = 0;
  public void init() {
    t.setEditable(false);
    for(int i = 0; i < 4; i++)
      c.addItem(description[count++]);
    add(t);
    add(c);
    add(b);
    c.addItemListener(new CL());
    b.addActionListener(new BL());
  }
  class CL implements ItemListener {
    public void itemStateChanged(ItemEvent e) {
      t.setText("индекс: " + c.getSelectedIndex()
        + "   " + e.toString());
    }
  }
  class BL implements ActionListener {
    public void actionPerformed(ActionEvent e) {
      if(count < description.length)
        c.addItem(description[count++]);
    }
  }
  public static void main(String[] args) {
    ChoiceNew applet = new ChoiceNew();
    Frame aFrame = new Frame("ChoiceNew");
    aFrame.addWindowListener(
      new WindowAdapter() {
        public void windowClosing(WindowEvent e) {
          System.exit(0);
        }
      });
    aFrame.add(applet, BorderLayout.CENTER);
    aFrame.setSize(750, 100);
    applet.init();
    applet.start();
    aFrame.setVisible(true);
  }
} ///:~
```

В этом примере нет ничего особенно нового (кроме нескольких недостатков в UI-классах Java 1.1).

(5) Списки

Мы устранили один из недостатков дизайна `List` в Java 1.0, который заключается в том, что `List` не работает так, как мы хотели бы: она конфликтует с одиночным щелчком по элементу списка.```java
//: ListNew.java
// Lists in Java 1.1 are easier to use
import java.awt.*;
import java.awt.event.*;
import java.applet.*;

public class ListNew extends Applet {
  String[] flavors = { "Chocolate", "Bilberry",
    "Mint Chip", "Mocha Almond Fudge",
    "Rum Raisin", "Praline Cream",
    "Mud Pie" };
  // Displaying 6 elements and allowing multiple selections:
  List lst = new List(6, true);
  TextArea t = new TextArea(flavors.length, 30);
  Button b = new Button("test");
  int count = 0;
  public void init() {
    t.setEditable(false);
    for(int i = 0; i < 4; i++)
      lst.addItem(flavors[count++]);
    add(t);
    add(lst);
    add(b);
    lst.addItemListener(new LL());
    b.addActionListener(new BL());
  }
  class LL implements ItemListener {
    public void itemStateChanged(ItemEvent e) {
      t.setText("");
      String[] items = lst.getSelectedItems();
      for(int i = 0; i < items.length; i++)
        t.append(items[i] + "\n");
    }
  }
  class BL implements ActionListener {
    public void actionPerformed(ActionEvent e) {
      if(count < flavors.length)
        lst.addItem(flavors[count++], 0);
    }
  }
  public static void main(String[] args) {
    ListNew applet = new ListNew();
    Frame aFrame = new Frame("ListNew");
    aFrame.addWindowListener(
      new WindowAdapter() {
        public void windowClosing(WindowEvent e) {
          System.exit(0);
        }
      });
    aFrame.add(applet, BorderLayout.CENTER);
    aFrame.setSize(300,200);
    applet.init();
    applet.start();
    aFrame.setVisible(true);
  }
} ///:~
```Можно заметить, что в списке элементов нет необходимости поддерживать отдельную логику для одиночного клика. Мы просто добавляем слушатель так же, как мы это делали в других местах.(6) МенюОбработка событий для меню выигрывает от модели событий Java версии 1.1, но методы создания меню в Java часто оказываются сложными и требуют некоторого ручного программирования. Верный подход к созданию меню должен заключаться в том, чтобы представлять его как ресурс, а не просто как код. Внимание следует обращать на то, что средства разработки могут значительно облегчить задачу создания меню, если они также будут эффективно справляться с задачами поддержки. Кроме того, мы столкнемся с проблемой непоследовательной обработки событий: элементы меню используют `ActionListeners`, а флажковые элементы меню  `ItemListeners`. Объекты меню также поддерживают `ActionListeners`, но это обычно менее полезно. Как правило, нам приходится привязывать прослушиватели к каждому элементу меню или флажковому элементу меню, однако пример ниже (модификация предыдущего примера) демонстрирует способ объединения нескольких компонентов меню в одном классе-прослушивателе. Возможно, стоит либо принять этот подход, либо продолжить использовать текущий метод.

```markdown
//: MenuNew.java
// Меню в Java 1.1
import java.awt.*;
import java.awt.event.*;
```

```java
public class MenuNew extends Frame {
    String[] вкусы = {"Шоколад", "Смородина", "Ванильный крем с вишней", "Мятный чип", "Мохко-ореховый крем", "Рому соrais", "Пралине", "Булочки с корицей"};
    TextField t = new TextField("Нет вкуса", 30);
    MenuBar mb1 = new MenuBar();
    Menu f = new Menu("Файл");
    Menu m = new Menu("Вкусы");
```    Menu s = new Menu("Безопасность");
     // Альтернативный подход:
     CheckboxMenuItem[] security = {
         new CheckboxMenuItem("Защита"),
         new CheckboxMenuItem("Скрыть")
     };
     MenuItem[] file = {
         // Нет меню-шорткат:
         new MenuItem("Открыть"),
         // Добавление меню-шорткат очень просто:
         new MenuItem("Выход",
             new MenuShortcut(KeyEvent.VK_E))
     };
     // Второй панели меню для замены:
     MenuBar mb2 = new MenuBar();
     Menu fooBar = new Menu("fooBar");
     MenuItem[] other = {
         new MenuItem("Foo"),
         new MenuItem("Bar"),
         new MenuItem("Baz"),
     };
     // Код инициализации:
     {
         ML ml = new ML();
         CMIL cmil = new CMIL();
         security[0].setActionCommand("Защита");
         security[0].addItemListener(cmil);
         security[1].setActionCommand("Скрыть");
         security[1].addItemListener(cmil);
         file[0].setActionCommand("Открыть");
         file[0].addActionListener(ml);
         file[1].setActionCommand("Выход");
         file[1].addActionListener(ml);
         other[0].addActionListener(new FooL());
         other[1].addActionListener(new BarL());
         other[2].addActionListener(new BazL());
     }
     Button b = new Button("Переключить меню");
     public MenuNew() {
         FL fl = new FL();
         for (int i = 0; i < вкус.length; i++) {
             MenuItem mi = new MenuItem(вкус[i]);
             mi.addActionListener(fl);
             m.add(mi);
             // Добавляем разделители через каждые три пункта:
             if ((i + 1) % 3 == 0)
                 m.addSeparator();
         }
         for (int i = 0; i < security.length; i++)
             s.add(security[i]);
         f.add(s);
         for (int i = 0; i < file.length; i++)
             f.add(file[i]);
         mb1.add(f);
         mb1.add(m);
         setJMenuBar(mb1);
         t.setEditable(false);
         add(t, BorderLayout.CENTER);
  }```java
// Устанавливаем систему для переключения меню:
b.addActionListener(new BL());
add(b, BorderLayout.NORTH);
for (int i = 0; i < другой.length; i++) 
    fooBar.add(другой[i]);
mb2.add(fooBar);
}
class BL implements ActionListener {
public void actionPerformed(ActionEvent e) {
MenuBar m = getMenuBar();
if (m == mb1) setMenuBar(mb2);
else setMenuBar(mb1);
}
}
class ML implements ActionListener {
public void actionPerformed(ActionEvent e) {
MenuItem целевое = (MenuItem)e.getSource();
String actionCommand = 
целевое.getActionCommand();
if(actionCommand.equals("Открыть")) {
String s = t.getText();
boolean выбрано = false;
for(int i = 0; i < вкусы.length; i++)
if(s.equals(вкусы[i])) выбрано = true;
if(!выбрано)
t.setText("Выберите вкус сначала!");
else
t.setText("Открываю " + s + ".  О, ммм, мм!");
} else if(actionCommand.equals("Выход")) {
dispatchEvent(
new WindowEvent(MenuNew.this,
WindowEvent.WINDOW_CLOSING));
}
}
}
class FL implements ActionListener {
public void actionPerformed(ActionEvent e) {
MenuItem целевое = (MenuItem)e.getSource();
t.setText(целевое.getLabel());
}
}
// В качестве альтернативы вы можете создать другой
// класс для каждого(MenuItem). Затем вам
// Не придется догадываться, какой это:
class FooL implements ActionListener {
public void actionPerformed(ActionEvent e) {
t.setText("Foo выбран");
}
}
class BarL implements ActionListener {
public void actionPerformed(ActionEvent e) {
t.setText("Bar выбран");
}
}
```   Класс BazL реализует интерфейс ActionListener {
      public void actionPerformed(ActionEvent e) {
        t.setText("Baz выбран");
      }
    }
    Класс CMIL реализует интерфейс ItemListener {
      public void itemStateChanged(ItemEvent e) {
        CheckboxMenuItem целевое =
          (CheckboxMenuItem)e.getSource();
        String actionCommand =
          целевое.getActionCommand();
        if(actionCommand.equals("Защитник"))
          t.setText("Защищаем мороженое!  Защита включена " + целевое.getState());
        else if(actionCommand.equals("Скрыть"))
          t.setText("Скрываем мороженое!  Холодно ли?  " + целевое.getState());
      }
    }
    public static void main(String[] args) {
      MenuNew f = new MenuNew();
      f.addWindowListener(
        new WindowAdapter() {
          public void windowClosing(WindowEvent e) {
            System.exit(0);
          }
        });
      f.setSize(300, 200);
      f.setVisible(true);
    }
 }

```Перед началом секции инициализации (определённой правым скобочным знаком после аннотации `Initialization code:`) код полностью совпадает с версией предыдущего (Java 1. 0).  Здесь мы можем заметить, что проектные и действия рецепторы прикреплены к различным компонентам меню.Java 1.1 поддерживает "быстрые клавиши меню", поэтому мы можем выбрать пункт меню, используя клавиатуру вместо мыши. Это очень просто; нам нужно использовать перегруженный конструктор пункта меню, установив второй параметр как объект типа `MenuShortcut`. Конструктор быстрой клавиши меню устанавливает важные методы, которые отображаются на пункте меню при нажатии. В примере выше добавлена комбинация клавиш `Control-E` к пункту меню `Выход`.

Также обратите внимание на использование метода `setActionCommand()`. Это может показаться странным, так как в различных случаях "action command" полностью совпадает с меткой на компонентах меню. Почему бы не использовать эти метки вместо выборочных строк? Проблема связана с международной локализацией. Если мы перепишем этот программный код на другом языке, нам потребуется изменить только метки на компонентах меню, а не проверять весь код на наличие новых ошибок. Таким образом, это делает код, который работает со строками команд действий и компонентами меню, более простым и удобным, когда метки могут меняться, но строки команд действий остаются неизменными. Все это позволяет коду работать вместе со строками команд действий, поэтому он не зависит от изменения меток на компонентах меню. Обратите внимание, что во всех этих компонентах меню не все используют строки команд действий, поэтому они не имеют установленных строк команд действий.Большинство конструкторов аналогичны предыдущим, увеличивая количество вызовов исключений, передаваемых рецепторам. Большая часть работы происходит внутри рецепторов. В примере `BL`, меню переключается. В `ML` метод "поиск 'ring'" используется как источник события `ActionEvent`, которое преобразуется в событие пункта меню, затем получает строку команды действия и проходит через цепочку компонентов, естественно, если она была объявлена. Большинство этого аналогично предыдущему, но обратите внимание, что если выбран пункт `Exit`, то создается новое событие окна путём использования ссылки на объект-обёртку (`MenuNew.this`) и создания события `WINDOW_CLOSING`. Новое событие присваивается методу `dispatchEvent()` объекта-обёртки, затем вызывается внутренний рецептор окна (`windowClosing()`), созданный как внутренний класс в методе `main()`. Этот подход кажется "естественному" способом отправки сообщений. Благодаря этому механизму, мы можем быстро обрабатывать любую информацию в любой ситуации, что делает его мощным. Приёмник `FL` очень прост, хотя он способен обрабатывать все различные особенности специального меню.Если наша логика действительно проста и понятна, этот подход может быть полезен, но обычно нам приходится использовать его вместе с `FooL`, `BarL` и `BazL`, каждый из которых привязывается к отдельному компоненту меню, поэтому тестирование логики становится излишним, а также позволяет нам правильно идентифицировать вызывающий приёмник. Этот подход создаёт множество классов, внутренний код которых стремится стать более компактным и легким в обслуживании.(7) Диалоговое окно

В этом примере был напрямую переопределен ранний вариант программы `ToeTest.java`. В новой версии любое событие помещается внутри внутреннего класса. Хотя это полностью устраняет необходимость создания записей для каждого класса, как показывает пример `ToeTest.java`, это делает концепцию внутренних классов более доступной. Здесь внутренние классы вложены на четыре уровня глубины! Наши выбор этого дизайна определяет, стоит ли преимущества использования внутренних классов увеличением сложности. Кроме того, когда мы создаем какой-либо внутренний класс, мы связываем его с окружающим классом. Иногда отдельные классы могут быть легче переиспользованы.```java
//: ToeTestNew.java
// Demonstration of dialog boxes and creating your own components
import java.awt.*;
import java.awt.event.*;

```

```java
public class ToeTestNew extends Frame {
    public TextField rows = new TextField("3");
    public TextField columns = new TextField("3");

    public ToeTestNew() {
        setTitle("Toe Test");
        Panel p = new Panel();
        p.setLayout(new GridLayout(2, 2));
        p.add(new Label("Rows", Label.CENTER));
        p.add(rows);
        p.add(new Label("Columns", Label.CENTER));
        p.add(columns);
        add(p, BorderLayout.NORTH);

        Button b = new Button("go");
        b.addActionListener(new BL());
        add(b, BorderLayout.SOUTH);
    }

    static final int BLANK = 0;
    static final int XX = 1;
    static final int OO = 2;

    class ToeDialog extends Dialog {
        // w = number of cells in width
        // h = number of cells in height
        int move = XX; // Start with X's turn
        public ToeDialog(int w, int h) {
            super(ToeTestNew.this, "Game itself", false);
            setLayout(new GridLayout(w, h));

            for (int i = 0; i < w * h; i++) 
                add(new ToeButton());

            setSize(w * 50, h * 50);
            addWindowListener(new WindowAdapter() {
                public void windowClosing(WindowEvent e) {
                    dispose();
                }
            });
        }

        class ToeButton extends Canvas {
            int state = BLANK;
            ToeButton() {
                addMouseListener(new ML());
            }

            public void paint(Graphics g) {
                int x1 = 0;
                int y1 = 0;
            }
        }
    }

    class ML implements MouseListener {}
}
``````markdown
Целое x2 = getSize().width - 1;
Целое y2 = getSize().height - 1;
g.drawRect(x1, y1, x2, y2);
x1 = x2 / 4;
y1 = y2 / 4;
Целое ширина = x2 / 2;
Целое высота = y2 / 2;
Если (состояние == XX) {
    g.drawLine(x1, y1, x1 + ширина, y1 + высота);
    g.drawLine(x1, y1 + высота, x1 + ширина, y1);
}
Если (состояние == OO) {
    g.drawOval(x1, y1, x1 + ширина / 2, y1 + высота / 2);
}

Класс ML расширяет MouseAdapter {
    Публичный void mousePressed(MouseEvent e) {
        Если (состояние == БЛАНК) {
            Состояние = ход;
            Ход = (Ход == XX ? OO : XX);
        } 
        Ещё
            Состояние = (Состояние == XX ? OO : XX);
        Перерисовать();
    }
}

Класс BL реализует ActionListener {
    Публичный void actionPerformed(ActionEvent e) {
        Диалог d = Новый ToeDialog(Integer.parseInt(ряды.getText()), Integer.parseInt(колонки.getText()));
        D.show();
    }
}

Публичный статический void основной(Строковый[] аргументы) {
```
Обратите внимание, что код Java был преобразован в текстовое описание, чтобы соответствовать требованиям перевода. В реальном случае этот код остался бы без изменений, как указано в правиле 2.1. Однако, поскольку требуется перевод всего содержимого в текстовое описание, это было сделано таким образом.
```markdown
Frame f = new ToeTestNew();
f.addWindowListener(new WindowAdapter() {
``````java
    public void windowClosing(WindowEvent e) {
        System.exit(0);
    }
});
f.setSize(200, 100);
f.setVisible(true);
}
```
///:~Поскольку «статические» элементы могут находиться только на внешнем уровне класса, внутренние классы не могут иметь статических данных или статических вложенных классов.(8) Файловый диалог

Этот пример был создан путём прямого изменения файла `FileDialogTest.java` с использованием нового модели событий.

```java
//: FileDialogNovyi.java
// Демонстрация работы с файловыми диалогами
import java.awt.*;
import java.awt.event.*;

public class FileDialogNovyi extends Frame {
  TextField filename = new TextField();
  TextField directory = new TextField();
  Button open = new Button("Открыть");
  Button save = new Button("Сохранить");

  public FileDialogNovyi() {
    setTitle("Файловой диалог теста");
    Panel p = new Panel();
    p.setLayout(new FlowLayout());
    open.addActionListener(new OpenL());
    p.add(open);
    save.addActionListener(new SaveL());
    p.add(save);
    add(p, BorderLayout.SOUTH);

    directory.setEditable(false);
    filename.setEditable(false);
    p = new Panel();
    p.setLayout(new GridLayout(2, 1));
    p.add(filename);
    p.add(directory);
    add(p, BorderLayout.NORTH);
  }

  class OpenL implements ActionListener {
    public void actionPerformed(ActionEvent e) {
      // Два аргумента, по умолчанию — открытие файла:
      FileDialog d = new FileDialog(FileDialogNovyi.this, "Какой файл вы хотите открыть?");
      d.setFile("*.java");
      d.setDirectory("."); // Текущий каталог
      d.show();

      String yourFile = "*.*";
      if ((yourFile = d.getFile()) != null) {
        filename.setText(yourFile);
        directory.setText(d.getDirectory());
      } else {
        filename.setText("Вы нажали Cancel");
        directory.setText("");
      }
    }
  }

  class SaveL implements ActionListener {
    public void actionPerformed(ActionEvent e) {
      FileDialog d = new FileDialog(FileDialogNovyi.this, "Какой файл вы хотите сохранить?", FileDialog.SAVE);
      d.setFile("*.java");
      d.setDirectory(".");
      d.show();

      String saveFile;
      if ((saveFile = d.getFile()) != null) {
        filename.setText(saveFile);
        directory.setText(d.getDirectory());
      } else {
        filename.setText("Вы нажали Cancel");
        directory.setText("");
      }
    }
  }
```  public static void main(String[] args) {
    Frame f = new FileDialogNovyi();
    f.addWindowListener(new WindowAdapter() {
      public void windowClosing(WindowEvent e) {
        System.exit(0);
      }
    });

    f.setSize(250, 110);
    f.setVisible(true);
  }
} ///:~

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

## 13.16.5 Динамическое привязывание событий

Новая модель событий AWT предоставляет нам одну из выгод  гибкость. В старой модели мы вынуждены были трудоёмко писать код для действий нашего приложения. Но в новой модели мы можем использовать единственный метод вызова для добавления и удаления действий событий. Пример ниже демонстрирует это:

```java
//: DynamicEvents.java
// Новая модель событий Java  Yöntem 1.1 allows you to
// dynamically change the behavior of events. Also
// demonstrates multiple actions for one event.
import java.awt.*;
import java.awt.event.*;
import java.util.*;

public class DynamicEvents extends Frame {
  Vector v = new Vector();
  int i = 0;
  Button
    b1 = new Button("Button 1"),
    b2 = new Button("Button 2");

  public DynamicEvents() {
    setLayout(new FlowLayout());
    b1.addActionListener(new B());
    b1.addActionListener(new B1());
    b2.addActionListener(new B());
    b2.addActionListener(new B2());
    add(b1);
    add(b2);
  }

  class B implements ActionListener {
    public void actionPerformed(ActionEvent e) {
      System.out.println("Button was pressed");
    }
  }

  class CountListener implements ActionListener {
    int index;

    public CountListener(int i) {
      index = i;
    }

    public void actionPerformed(ActionEvent e) {
      System.out.println(
        "Count listener " + index);
    }
  }
```  class B1 implements ActionListener {
    public void actionPerformed(ActionEvent e) {
      System.out.println("Кнопка 1 нажата");
      ActionListener a = new CountListener(i++);
      v.addElement(a);
      b2.addActionListener(a);
    }
  }

  class B2 implements ActionListener {
    public void actionPerformed(ActionEvent e) {
      System.out.println("Кнопка 2 нажата");
      int end = v.size() - 1;
      if (end >= 0) {
        b2.removeActionListener(
            (ActionListener) v.elementAt(end));
        v.removeElementAt(end);
      }
    }
  }

  public static void main(String[] args) {
    Frame f = new DynamicEvents();
    f.addWindowListener(
        new WindowAdapter() {
          public void windowClosing(WindowEvent e) {
            System.exit(0);
          }
        });
    f.setSize(300, 200);
    f.show();
  }
} ///:~

```Пример использует новые подходы, включая:

(1) Прикрепление к каждому кнопочному элементу минимум одного слушателя событий. Обычно компоненты обрабатывают события как многопоточные операции, что позволяет регистрировать несколько слушателей для одного события. Когда событие обрабатывается как однопоточная операция в специальном компоненте, возникает исключение TooManyListenersException (слишком много слушателей).

(2) Динамическое добавление и удаление слушателей во время выполнения программы с помощью кнопки B2. Добавление осуществляется методами, аналогичными тем, что мы видели ранее, но каждый компонент также имеет метод removeXXXListener() для удаления различных типов слушателей.

Эта гибкость предоставляет более мощные возможности программирования.```Обратите внимание, что приемники событий не гарантируются вызовом при команде их добавления (хотя фактически большинство операций выполняется именно таким образом).## 13.16.6 Разделение бизнес-логики от логики пользовательского интерфейса

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

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

Ниже представлен пример демонстрации простоты разделения бизнес-логики от логики пользовательского интерфейса:

```java
//: Separation.java
// Разделение логики пользовательского интерфейса и бизнес-объектов
import java.awt.*;
import java.awt.event.*;
import java.applet.*;
``````public class Razdelenie extends Applet {
    TextField 
        t = new TextField(20),
        modificator = new TextField(20);
    BusinessLogic bl = new BusinessLogic(2);
    Button
        calc1 = new Button("Вычисление 1"),
        calc2 = new Button("Вычисление 2");

    public void init() {
        add(t);
        calc1.addActionListener(new Calc1L());
        calc2.addActionListener(new Calc2L());
        add(calc1); add(calc2);
        modificator.addTextListener(new ModL());
        add(new Label("Модификатор:"));
        add(modificator);
    }

    static int getValue(TextField tf) {
        try {
            return Integer.parseInt(tf.getText());
        } catch(NumberFormatException e) {
            return 0;
        }
    }

    inner class Calc1L implements ActionListener {
        public void actionPerformed(ActionEvent e) {
            t.setText(Integer.toString(bl.calculation1(getValue(t))));
        }
    }

    inner class Calc2L implements ActionListener {
        public void actionPerformed(ActionEvent e) {
            t.setText(Integer.toString(bl.calculation2(getValue(t))));
        }
    }

    inner class ModL implements TextListener {
        public void textValueChanged(TextEvent e) {
            bl.setModifier(getValue(modificator));
        }
    }
}
``````java
public static void main(String[] аргумент) {
    Разделение апплет = new Разделение();
    Окно aFrame = new Окно("Разделение");
    aFrame.addWindowListener(new WindowAdapter() {
        public void windowClosing(WindowEvent e) {
            System.выход();
        }
    });
    aFrame.add(апплет, BorderLayout.CENTER);
    aFrame.setSize(200, 200);
    апплет.init();
    апплет.start();
    aFrame.setVisible(true);
}
} ///:~
## 13.16.7 Рекомендованные методы кодирования
```Внутренние классы представляют собой новую модель событий, и фактически старая модель событий вместе с характеристиками нового библиотечного пакета хорошо поддерживаются. Внедрение старых методов программирования неизбежно привносит новый уровень путаницы. Теперь существует больше различных способов для написания некачественного кода. Кстати, этот код встречается в данной книге и примерах программ, а также даже отличается от того, что представлено компанией SUN в файлах и примерах программ. В этом разделе мы рассмотрим некоторые споры относительно использования новых AWT и закончим тем, что покажем, как можно использовать приемникные классы для решения наших потребностей в управлении событиями, за исключением случаев, когда это может быть оправдано. Поскольку этот метод является самым простым и понятным, он будет эффективной помощью при обучении его использованию.До того, как мы увидим что-либо, мы знаем, что хотя Java 1.1 обратно совместима с Java 1.0 (то есть, мы можем скомпилировать и запустить программы 1.0 в среде 1.1), мы не можем смешивать модели событий в одном и том же приложении. Другими словами, когда мы пытаемся интегрировать старый код в новое приложение, мы не можем использовать старый метод `action()` в одном и том же приложении, поэтому нам нужно решить, будем ли мы использовать старый, трудно поддерживаемый метод в новом приложении или обновим старый код. Это не вызывает много споров, так как новые методы значительно превосходят старые.```markdown
## Пример программы на Java

### Класс `GoodIdea`

Класс `GoodIdea` расширяет класс `Frame`. Внутри этого класса создаются два объекта типа `Button`.

#### Конструктор `GoodIdea`

Конструктор инициализирует компоненты формы:

- Устанавливает макет с использованием `FlowLayout`.
- Добавляет слушатель действий для кнопок `b1` и `b2`, используя внутренние классы `B1L` и `B2L`.
- Добавляет кнопки `b1` и `b2` в контейнер.

#### Внутренний класс `B1L`

Этот внутренний класс реализует интерфейс `ActionListener` для кнопки `b1`. Когда кнопка нажата, выводится сообщение "Button  Yöntem 1 pressed".

#### Внутренний класс `B2L`

Этот внутренний класс также реализует интерфейс `ActionListener`, но для кнопки `b2`. При нажатии кнопки выводится сообщение "Button 2 pressed".

#### Метод `main`

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

```java
public class GoodIdea extends Frame {
  Button
    b1 = new Button("Button 1"),
    b2 = new Button("Button 2");
```
```plaintext

Видимо, есть ошибки в тексте, такие как "Button Yöntem 1 pressed". Также замечены некорректные символы в некоторых местах. Вот исправленный вариант:

```markdown
## Пример программы на Java

### Класс `GoodIdea`

Класс `GoodIdea` расширяет класс `Frame`. Внутри этого класса создаются два объекта типа `Button`.

#### Конструктор `GoodIdea`

Конструктор инициализирует компоненты формы:

- Устанавливает макет с использованием `FlowLayout`.
- Добавляет слушатель действий для кнопок `b1` и `b2`, используя внутренние классы `B1L` и `B2L`.
- Добавляет кнопки `b1` и `b2` в контейнер.

#### Внутренний класс `B1L`

Этот внутренний класс реализует интерфейс `ActionListener` для кнопки `b1`. Когда кнопка нажата, выводится сообщение "Button 1 pressed".

#### Внутренний класс `B2L`

Этот внутренний класс также реализует интерфейс `ActionListener`, но для кнопки `b2`. При нажатии кнопки выводится сообщение "Button 2 pressed".

#### Метод `main`

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

```java
public class GoodIdea extends Frame {
  Button
    b1 = new Button("Button 1"),
    b2 = new Button("Button 2");
```  public GoodIdea() {
    setLayout(new FlowLayout());
    b1.addActionListener(new B1L());
    b2.addActionListener(new B2L());
    add(b1);
    add(b2);
  }

  public class B1L implements ActionListener {
    public void actionPerformed(ActionEvent e) {
      System.out.println("Кнопка 1 нажата");
    }
  }

  public class B2L implements ActionListener {
    public void actionPerformed(ActionEvent e) {
      System.out.println("Кнопка 2 нажата");
    }
  }

  public static void main(String[] args) {
    Frame f = new GoodIdea();
    f.addWindowListener(
      new WindowAdapter() {
        public void windowClosing(WindowEvent e){
          System.out.println("Закрытие окна");
          System.exit(0);
        }
      });
    f.setSize(300,200);
    f.setVisible(true);
  }
}
///:~```java
//: BadIdea1.java
// Некоторая литература рекомендует этот подход,
// но она игнорирует ключевые аспекты новой модели событий в Java 1.1
import java.awt.*;
import java.awt.event.*;
import java.util.*;
``````java
public class BadIdea1 extends Frame
    implements ActionListener, WindowListener {
  Button
    b1 = new Button("Button 1"),
    b2 = new Button("Button 2");
  public BadIdea1() {
    setLayout(new FlowLayout());
    addWindowListener(this);
    b1.addActionListener(this);
    b2.addActionListener(this);
    add(b1);
    add(b2);
  }
  public void actionPerformed(ActionEvent e) {
    Object source = e.getSource();
    if(source == b1)
      System.out.println("Button 1 pressed");
    else if(source == b2)
      System.out.println("Button 2 pressed");
    else
      System.out.println("Something else");
  }    
  public void windowClosing(WindowEvent e) {
    System.out.println("Window closing");
    System.exit(0);
  }
  public void windowClosed(WindowEvent e) {}
  public void windowDeiconified(WindowEvent e) {}
  public void windowIconified(WindowEvent e) {}
  public void windowActivated(WindowEvent e) {}
  public void windowDeactivated(WindowEvent e) {}
  public void windowOpened(WindowEvent e) {}  

  public static void main(String[] args) {
    Frame f = new BadIdea1();
    f.setSize(300,200);
    f.setVisible(true);
  }
} ///:~
```

Цели этого подхода становятся очевидными в следующих трех строках:

```
addWindowListener(this);
b1.addActionListener(this);
b2.addActionListener(this);
```

Так как `BadIdea1` выполняет роли действий и оконного слушателя, эти строки кода являются допустимыми. Если мы стремимся использовать минимальное количество классов для снижения нагрузки на сервер при работе, это может считаться хорошим решением. Однако: (1) Поддержка JAR-файлов в Java 1.1 позволяет нам помещать все наши файлы в один сжатый JAR-файл, что требует всего одного запроса к серверу. Теперь мы можем не ограничивать количество классов ради повышения эффективности соединения через Интернет.
(2) В вышестоящем коде меньше компонент, поэтому его сложнее захватывать и копировать. Обратите внимание, что мы должны не только реализовать различные интерфейсы для нашего основного класса, но также использовать цепочку условий в методе `actionPerformed()` для проверки выполненного действия. Это не только отходит от модели подписки, но и делает невозможной простую повторную регистрацию метода `actionPerformed()`, так как он предназначен специально для этого приложения. Сравните этот пример программы с `GoodIdea.java`, чтобы заметить, что можно легко захватить класс-подписчик и переместить его практически в любом месте без особых усилий. Кроме того, мы можем зарегистрировать несколько классов-подписчиков для одного события, позволяя ещё большую модульность в каждом классе-подписчике.

(3) Смешение методов

Второй плохой пример смешивает два подхода: использует внутренний класс-подписчик, но также реализует один или более интерфейсов подписчика как часть основного класса. Этот подход не требует объяснения в книге или документах, и я могу предположить, что некоторые разработчики Java считают необходимым применять разные методы для разных целей. Однако это не обязательно  во время программирования мы можем предпочесть использование внутреннего класса-подписчика.```java
//: BadIdea2.java
// Улучшение над BadIdea1.java, поскольку оно
// использует WindowAdapter как внутренний класс,
// вместо реализации всех методов WindowListener,
// но всё ещё пропускает ценную модульность внутренних классов
import java.awt.*;
import java.awt.event.*;
import java.util.*;
```

```markdown
## Пример программы на Java

```java
public class BadIdea2 extends Frame
    implements ActionListener {
  Button
    b1 = new Button("Кнопка 1"),
    b2 = new Button("Кнопка 2");

  public BadIdea2() {
    setLayout(new FlowLayout());
    addWindowListener(new WL());
    b1.addActionListener(this);
    b2.addActionListener(this);
    add(b1);
    add(b2);
  }

  public void actionPerformed(ActionEvent e) {
    Object source = e.getSource();
    if (source == b1)
      System.out.println("Кнопка 1 нажата");
    else if (source == b2)
      System.out.println("Кнопка 2 нажата");
    else
      System.out.println("Что-то другое");
  }

  class WL extends WindowAdapter {
    public void windowClosing(WindowEvent e) {
      System.out.println("Закрытие окна");
      System.exit(0);
    }
  }

  public static void main(String[] args) {
    Frame f = new BadIdea2();
    f.setSize(300, 200);
    f.setVisible(true);
  }
}
///:~
```
Потому что метод `actionPerformed()` завязан на основной класс, его код сложно переиспользовать. К тому же, этот код выглядит запутанным и неприятным, в отличие от внутренних классов. Недостаточно того факта, что нам приходится использовать старый подход к событиям в Java версии 1.1.

(4) Наследование компонента

Когда создается новый тип компонента, часто можно заметить изменения в работе старых методов событий. Вот пример программы, демонстрирующей новую методику:
``````java
//: GoodTechnique.java
// При переопределении компонентов ваш первым выбором должно быть установка слушателей. Код становится гораздо безопаснее, более модульным и удобным для поддержки.
import java.awt.*;
import java.awt.event.*;
``````java
class Отображение {
   public static final int 
     СОБЫТИЕ = 0, КОМПОНЕНТ = 1,
     МЫШЬ = 2, ДВИЖЕНИЕ_МЫШИ = 3,
     ФОКУС = 4, КЛЮЧ = 5, ДЕЙСТВИЕ = 6,
     ОБРАТНЫЙ_СЧЕТ = 7;
   public String[] evnt;
   Отображение() {
     evnt = new String[ОБРАТНЫЙ_СЧЕТ];
     for(int i = 0; i < ОБРАТНЫЙ_СЧЕТ; i++) 
       evnt[i] = new String();
   }
   public void показать(Graphics g) {
     for(int i = 0; i < ОБРАТНЫЙ_СЧЕТ; i++) 
       g.drawString(evnt[i], 0, 10 * i + 10);
   }
}
```

```markdown
Класс EnabledPanel расширяет Panel {
    Цвет c;
    int id;
    Отображение отображение = new Отображение();
    публичный EnabledPanel(int i, Цвет mc) {
      id = i;
      c = mc;
      установитьРазмещение(new BorderLayout());
      добавить(new MyButton(), BorderLayout.SOUTH);
      добавитьСоставнойПрослушиватель(new CL());
      добавитьПрослушивательФокуса(new FL());
      добавитьПрослушивательКлавиш(new KL());
      добавитьПрослушивательМыши(new ML());
      добавитьПрослушивательДвиженияМыши(new MML());
    }
    // Чтобы устранить мерцание:
    публичный void обновить(Graphics g) {
      рисовать(g);
    }
    публичный void рисовать(Graphics g) {
      g.setColor(c);
      Размер s = getsize();
      g.fillRect(0, 0, s.width, s.height);
      g.setColor(Color.BLACK);
      отображение.show(g);
    }
    // Для этого ничего не требуется включать:
    публичный void processEvent(AWTEvent e) {
      отображение.evnt[Отображение.EVENT] = e.toString();
      перерисовать();
      супер.processEvent(e);
    }
    класс CL реализует ComponentListener {
      публичный void componentMoved(ComponentEvent e) {
        отображение.evnt[Отображение.COMPONENT] = "Component moved";
        перерисовать();
      }
      публичный void componentResized(ComponentEvent e) {
        отображение.evnt[Отображение.COMPONENT] = "Component resized";
        перерисовать();
      }
    }
}
``````java
class CL implements ComponentListener {
    public void componentResized(ComponentEvent e) {
        отображение. evnt[Отображение. COMPONENT] = "Component resized";
        перерисовать();
    }
    public void componentHidden(ComponentEvent e) {
        отображение. evnt[Отображение. COMPONENT] = "Component hidden";
        перерисовать();
    }
    public void componentShown(ComponentEvent e) {
        отображение. evnt[Отображение. COMPONENT] = "Component shown";
        перерисовать();
    }
}
class FL implements FocusListener {
    public void focusGained(FocusEvent e) {
        отображение. evnt[Отображение. FOCUS] = "FOCUS gained";
        перерисовать();
    }
    public void focusLost(FocusEvent e) {
        отображение. evnt[Отображение. FOCUS] = "FOCUS lost";
        перерисовать();
    }
}
class KL implements KeyListener {
    public void keyPressed(KeyEvent e) {
        отображение. evnt[Отображение. KEY] = "KEY pressed: ";
        показатьКод(e);
    }
    public void keyReleased(KeyEvent e) {
        отображение. evnt[Отображение. KEY] = "KEY released: ";
        показатьКод(e);
    }
    public void keyTyped(KeyEvent e) {
        отображение. evnt[Отображение. KEY] = "KEY typed: ";
        показатьКод(e);
    }
    void показатьКод(KeyEvent e) {
        int code = e.getKeyCode();
        отображение. evnt[Отображение. KEY] += KeyEvent.getKeyText(code);
        перерисовать();
    }
}
class ML implements MouseListener {
    public void mouseClicked(MouseEvent e) {
        requestFocus(); // Получить ФОКУС при клике
        отображение. evnt[Отображение. MOUSE] = "MOUSE clicked";
        showMouse(e);
    }
}
```
```markdown
     public void mouseClicked(MouseEvent e) {
         requestFocus(); // Получить ФОКУС при клике
         отображение. evnt[Отображение. MOUSE] = "MOUSE clicked";
         showMouse(e);
     }
``````markdown
    public void mousePressed(MouseEvent e) {
        display.evnt[Display.MOUSE] = "MOUSE нажата";
        showMouse(e);
    }

    public void mouseReleased(MouseEvent e) {
        display.evnt[Display.MOUSE] = "MOUSE отпущена";
        showMouse(e);
    }

    public void mouseEntered(MouseEvent e) {
        display.evnt[Display.MOUSE] = "MOUSE вход";
        showMouse(e);
    }

    public void mouseExited(MouseEvent e) {
        display.evnt[Display.MOUSE] = "MOUSE выход";
        showMouse(e);
    }

    void showMouse(MouseEvent e) {
        display.evnt[Display.MOUSE] += ", x = " + e.getX() + ", y = " + e.getY();
        repaint();
    }
}

class MML implements MouseMotionListener {
    public void mouseDragged(MouseEvent e) {
        display.evnt[Display.MOUSE_MOVE] = "MOUSE перетаскивание";
        showMouse(e);
    }

    public void mouseMoved(MouseEvent e) {
        display.evnt[Display.MOUSE_MOVE] = "MOUSE движение";
        showMouse(e);
    }
}
``````java
void showMouse(MouseEvent e) {
    display.evnt[Display.MOUSE_MOVE] += ", x = " + e.getX() + ", y = " + e.getY();
    repaint();
}
```

```markdown
## Класс `МойКнопка` 

Этот класс расширяет базовый класс `Кнопка`. Он содержит счетчик кликов (`clickCounter`) и текстовую метку (`label`). Также реализован метод `actionPerformed`, который увеличивает счетчик кликов и обновляет текст метки при каждом действии.

### Конструктор `МойКнопка`

```java
public МойКнопка() {
    addActionListener(new АЛ());
}
```

### Метод `отрисовать`

```java
public void отрисовать(Graphics g) {
    g.setColor(Color.green);
    Dimension размер = getSize();
    g.fillRect(0, 0, размер.width, размер.height);
    g.setColor(Color.black);
    g.drawRect(0, 0, размер.width - 1, размер.height - 1);
    отрисоватьМетку(g);
}
```

### Метод `отрисоватьМетку`

```java
private void отрисоватьМетку(Graphics g) {
    FontMetrics fm = g.getFontMetrics();
    int ширина = fm.stringWidth(метка);
    int высота = fm.getHeight();
    int восхождение = fm.getAscent();
    int вертикальныйПоля = fm.getLeading();
    int горизонтальныйОтступ = 
        (getSize().width - ширина) / 2;
    int вертикальныйОтступ = 
        (getSize().height - высота) / 2;
    g.setColor(Color.red);
    g.drawString(метка, горизонтальныйОтступ,
        вертикальныйОтступ + восхождение + вертикальныйПоля);
}
```

### Внутренний класс `АЛ`

```java
class АЛ implements ActionListener {
    public void actionPerformed(ActionEvent e) {
        кликСчетчик++;
        метка = "клик #" + кликСчетчик + " " + e.toString();
        repaint();
    }
}
```
``````java
public class GoodTechnique extends Frame {
  GoodTechnique() {
    setLayout(new GridLayout(2, 2));
    add(new EnabledPanel(1, Color.cyan));
    add(new EnabledPanel(2, Color.lightGray));
    add(new EnabledPanel(3, Color.yellow));
  }
  public static void main(String[] args) {
    Frame f = new GoodTechnique();
    f.setTitle("Good Technique");
    f.addWindowListener(
      new WindowAdapter() {
        public void windowClosing(WindowEvent e) {
          System.out.println(e);
          System.out.println("The window is closing");
          System.exit(0);
        }
      });
    f.setSize(700, 700);
    f.setVisible(true);
  }
}
///:~
```Этот пример программы также демонстрирует различные события и методы получения информации о них. Это представление является способом централизованного отображения информации. Набор строк используется для получения информации о каждом типе событий, а метод `show()` устанавливает ссылку на любой объект изображения. Мы используем этот подход и прямо включаем его в код внешнего вида. Такое проектирование было намеренно создано для повторного использования при различных событиях.

Активационная панель представляет собой новый тип компонента. Это цветовая панель с кнопкой внизу, которая использует приемник класса для каждого отдельного события, чтобы захватывать все события, происходящие над ней, кроме тех, что были перегружены старым методом `processEvent()` в активационной панели (необходимо также вызвать `super.processEvent()`). Единственной причиной использования этого подхода является то, что он захватывает каждое произошедшее событие, поэтому мы можем наблюдать каждый продолжающийся процесс. Метод `processEvent()` больше не выводит строки, представляющие каждое событие; вместо этого ему пришлось бы использовать цепочку условий для определения события.В других аспектах встроенные приемники уже точно знают, какие события были найдены (предположим, что мы регистрировали их как часть компонента, нам не требуется никакого логического контроля, это будет нашей целью). Поэтому они не проверяют события; эти события просто являются их исходным материалом. Каждый приемник модифицирует строку отображения и его специфическое событие, а затем вызывает метод перерисовки `repaint()`, чтобы отобразить эту строку. Мы также можем заметить секрет, обычно используемый для сокращения мерцаний:```java
public void update(Graphics g) {
    paint(g);
}
```

Мы не всегда будем нуждаться в переопределении `update()`, но если мы напишем программу, которая мигает, и запустим её. По умолчанию последняя версия очистит фон, а затем вызовет метод `paint()` для перерисовки некоторых объектов. Этот процесс очистки часто приводит к мерцанию, но он является ненужным, так как `paint()` перерисовывает всё содержимое.

Можно заметить множество приемников  но проверка команд входящих данных должна выполняться для каждого компонента, который поддерживает события. (в отличие от `BadTechnique.java`, где это можно было бы видеть постоянно).

Экспериментирование с этой программой очень образовательно, поскольку мы узнаём многое о том, как происходят события в Java. Во-первых, это демонстрирует дизайнерскую ошибку большинства систем управления окнами: довольно сложно щелкнуть и отпустить мышь, если вы не двигаетесь, и когда мы пытаемся щёлкнуть мышью по объекту, система может принять это за перетаскивание. Одним из решений этой проблемы является использование метода `mousePressed()` при нажатии кнопки мыши и метода `mouseReleased()` при её отпускании вместо метода `mouseClicked()` при щелчке, а затем проверка необходимости вызова нашего собственного метода "true mouse click" (`mouseReallyClicked()`), учитывающего временной лаг и перемещение мыши на четыре пикселя.(5) Недопустимое наследование компонентов

Другой подход заключается в вызове метода `enableEvents()`, передаче ему модели, соответствующей желаемому событию (что часто рекомендуется многими руководствами), что приводит к отправке этих событий старым методам (хотя они являются новыми для Java 1.1), таким как `processFocusEvent()`. Также следует помнить о вызове базового класса. Вот как это выглядит:

```java
//: BadTechnique.java
// Возможно переопределение компонентов таким образом,
// но подход с использованием слушателей намного лучше,
// поэтому почему бы вам не использовать его?
import java.awt.*;
import java.awt.event.*;
``````markdown
class EnabledPanel extends Panel {
    Color c;
    int id;
    Display display = new Display();
    public EnabledPanel(int i, Color mc) {
      id = i;
      c = mc;
      setLayout(new BorderLayout());
      add(new MyButton(), BorderLayout.SOUTH);
      // Проверка типа данных потеряна. Вы можете включить и
      // обрабатывать события, которые компонент не
      // поймёт:
      enableEvents(
        // Panel не обрабатывает эти события:
        AWTEvent.ACTION_EVENT_MASK |
        AWTEvent.ADJUSTMENT_EVENT_MASK |
        AWTEvent.ITEM_EVENT_MASK |
        AWTEvent.TEXT_EVENT_MASK |
        AWTEvent.WINDOW_EVENT_MASK |
        // Panel может обрабатывать эти события:
        AWTEvent.COMPONENT_EVENT_MASK |
        AWTEvent.FOCUS_EVENT_MASK |
        AWTEvent.KEY_EVENT_MASK |
        AWTEvent.MOUSE_EVENT_MASK |
        AWTEvent.MOUSE_MOTION_EVENT_MASK |
        AWTEvent.CONTAINER_EVENT_MASK);
        // Вы можете включить событие без
        // переопределения его метода процесса.
    }
    // Для устранения мерцания:
    public void update(Graphics g) {
      paint(g);
    }
    public void paint(Graphics g) {
      g.setColor(c);
      Dimension s = getSize();
      g.fillRect(0, 0, s.width, s.height);
      g.setColor(Color.black);
      display.show(g);
    }
    public void processEvent(AWTEvent e) {
      display.evnt[Display.EVENT] = e.toString();
      repaint();
      super.processEvent(e);
    }
    public void processComponentEvent(ComponentEvent e) {
      switch(e.getID()) {
        case ComponentEvent.COMPONENT_MOVED:
          display.evnt[Display.COMPONENT] = "Компонент перемещён";
          break;
        case ComponentEvent.COMPONENT_RESIZED:
          display.evnt[Display.COMPONENT] = "Компонент изменён размером";
      }
    }
}
``````java
         break;
        case ComponentEvent.COMPONENT_HIDDEN:
          display.evnt[Display.COMPONENT] = "Компонент скрыт";
          break;
        case ComponentEvent.COMPONENT_SHOWN:
          display.evnt[Display.COMPONENT] = "Компонент показан";
          break;
        default:
      }
      repaint();
      // Всегда следует вызвать версию "super" того,
      // что вы переопределили:
      super.processComponentEvent(e);
    }
    public void processFocusEvent(FocusEvent e) {
      switch(e.getID()) {
        case FocusEvent.FOCUS_GAINED:
          display.evnt[Display.FOCUS] = "FOCUS получен";
          break;
        case FocusEvent.FOCUS_LOST:
          display.evnt[Display.FOCUS] = "FOCUS потерян";
          break;
        default:
      }
      repaint();
      super.processFocusEvent(e);
    }
    public void processKeyEvent(KeyEvent e) {
      switch(e.getID()) {
        case KeyEvent.KEY_PRESSED:
          display.evnt[Display.KEY] = "КЛЮЧ нажат: ";
          break;
        case KeyEvent.KEY_RELEASED:
          display.evnt[Display.KEY] = "КЛЮЧ отпущен: ";
          break;
        case KeyEvent.KEY_TYPED:
          display.evnt[Display.KEY] = "КЛЮЧ введен: ";
          break;
        default:
      }
      int code = e.getKeyCode();
      display.evnt[Display.KEY] += KeyEvent.getKeyText(code);
      repaint();
      super.processKeyEvent(e);
    }
    public void processMouseEvent(MouseEvent e) {
      switch(e.getID()) {
        case MouseEvent.MOUSE_CLICKED:
          requestFocus(); // Получить ФОКУС при клике
          display.evnt[Display.MOUSE] = "МЫШЬ щелчок";
          break;
        case MouseEvent.MOUSE_PRESSED:
          display.evnt[Display.MOUSE] = "МЫШЬ нажата";
          break;
        case MouseEvent.MOUSE_RELEASED:
          display.evnt[Display.MOUSE] = "МЫШЬ отпущена";
          break;
        case MouseEvent.MOUSE_ENTERED:
          display.evnt[Display.MOUSE] = "МЫШЬ вошла";
          break;
        case MouseEvent.MOUSE_EXITED:
          display.evnt[Display.MOUSE] = "МЫШЬ вышла";
          break;
        default:
      }
      display.evnt[Display.MOUSE] += 
```
```markdown
Класс `MyButton` наследуется от кнопки:
```java
class MyButton extends Button {
  int clickCounter;
  String label = "";
  public MyButton() {
    enableEvents(AWTEvent.ACTION_EVENT_MASK);
  }
  public void paint(Graphics g) {
    g.setColor(Color.green);
    Dimension s = getSize();
    g.fillRect(0, 0, s.width, s.height);
    g.setColor(Color.black);
    g.drawRect(0, 0, s.width - 1, s.height - 1);
    drawLabel(g);
  }
  private void drawLabel(Graphics g) {
    FontMetrics fm = g.getFontMetrics();
    int width = fm.stringWidth(label);
    int height = fm.getHeight();
    int ascent = fm.getAscent();
    int leading = fm.getLeading();
    int horizMargin = (getSize().width - width) / 2;
    int verMargin = (getSize().height - height) / 2;
    g.setColor(Color.red);
    g.drawString(label, horizMargin, verMargin + ascent + leading);
  }
  public void processActionEvent(ActionEvent e) {
    clickCounter++;
    label = "click #" + clickCounter + " " + e.toString();
    repaint();
    super.processActionEvent(e);
  }
}
```
```java
public class Display {
  // ...
  public void processMouseEvent(MouseEvent e) {
    if (!e.isConsumed()) {
      display.evnt[Display.MOUSE_DOWN] = 
        "МЫШЬ нажатие, x = " + e.getX() + 
        ", y = " + e.getY();
      repaint();
      super.processMouseEvent(e);
    }
    public void processMouseMotionEvent(MouseEvent e) {
      switch (e.getID()) {
        case MouseEvent.MOUSE_DRAGGED:
          display.evnt[Display.MOUSE_MOVE] = 
            "МЫШЬ перетаскивание";
          break;
        case MouseEvent.MOUSE_MOVED:
          display.evnt[Display.MOUSE_MOVE] = 
            "МЫШЬ перемещение";
          break;
        default:
      }
      display.evnt[Display.MOUSE_MOVE] += 
        ", x = " + e.getX() + 
        ", y = " + e.getY();
      repaint();
      super.processMouseMotionEvent(e);
    }
  }
}
```

```markdown
Класс `Display` обрабатывает события мыши:
```java
public class Display {
  // ...
  public void processMouseEvent(MouseEvent e) {
    if (!e.isConsumed()) {
      display.evnt[Display.MOUSE_DOWN] = 
        "МЫШЬ нажатие, x = " + e.getX() + 
        ", y = " + e.getY();
      repaint();
      super.processMouseEvent(e);
    }
  }

  public void processMouseMotionEvent(MouseEvent e) {
    switch (e.getID()) {
      case MouseEvent.MOUSE_DRAGGED:
        display.evnt[Display.MOUSE_MOVE] = 
          "МЫШЬ перетаскивание";
        break;
      case MouseEvent.MOUSE_MOVED:
        display.evnt[Display.MOUSE_MOVE] = 
          "МЫШЬ перемещение";
        break;
      default:
    }
    display.evnt[Display.MOUSE_MOVE] += 
      ", x = " + e.getX() + 
      ", y = " + e.getY();
    repaint();
    super.processMouseMotionEvent(e);
  }
}
```Класс `BadTechnique` наследуется от окна:
```java
public class BadTechnique extends Frame {
  BadTechnique() {
    setLayout(new GridLayout(2, 2));
    add(new EnabledPanel(1, Color.cyan));
    add(new EnabledPanel(2, Color.lightGray));
    add(new EnabledPanel(3, Color.yellow));
    // Также можно сделать это для Windows:
    enableEvents(AWTEvent.WINDOW_EVENT_MASK);
  }

  public void processWindowEvent(WindowEvent e) {
    System.out.println(e);
    if (e.getID() == WindowEvent.WINDOW_CLOSING) {
      System.out.println("Закрытие окна");
      System.exit(0);
    }
  }

  public static void main(String[] args) {
    Frame f = new BadTechnique();
    f.setTitle("Плохой подход");
    f.setSize(700, 700);
    f.setVisible(true);
  }
} ///:~
```

Действительно, это работает. Но этот подход слишком плох, и его сложно писать, читать, отлаживать, поддерживать и переиспользовать. Так почему же не использовать внутренний приемник-класс?

Опубликовать ( 0 )

Вы можете оставить комментарий после Вход в систему

1
https://api.gitlife.ru/oschina-mirror/wizardforcel-thinking-in-java-zh.git
git@api.gitlife.ru:oschina-mirror/wizardforcel-thinking-in-java-zh.git
oschina-mirror
wizardforcel-thinking-in-java-zh
wizardforcel-thinking-in-java-zh
master