Материал: Портянкин И. Swing

Внимание! Если размещение файла нарушает Ваши авторские права, то обязательно сообщите нам

40

ГЛАВА 2

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

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

Внутренние классы

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

//InnerClassEvents.java

//Внутренние классы для обработки событий import javax.swing.*;

import java.awt.*; import java.awt.event.*;

public class InnerClassEvents extends JFrame { private JTextField text;

private JButton button; public InnerClassEvents() { super("InnerClassEvents"); // при закрытии окна - выход

setDefaultCloseOperation(EXIT_ON_CLOSE);

Модель событий

41

//последовательное расположение setLayout(new FlowLayout());

//добавим текстовое поле add(text = new JTextField(10));

//и кнопку

add(button = new JButton("Нажмите"));

//будем следить за нажатиями кнопки button.addActionListener(new ButtonL());

//выводим окно на экран

pack();

setVisible(true);

}

// класс - слушатель нажатия на кнопку class ButtonL implements ActionListener {

public void actionPerformed(ActionEvent e) { System.out.println(text.getText());

}

}

public static void main(String[] args) { SwingUtilities.invokeLater(

new Runnable() {

public void run() { new InnerClassEvents(); } });

}

}

В примере показана классическая ситуация: имеется текстовое поле и кнопка — мы добавили их в наше окно, предварительно установив для него последовательное расположение компонентов (подробнее про расположение компонентов будет рассказано в главе 7, а про окна в главе 6). Пользователь что-то вводит в поле и щелкает на кнопке, а программа должна обработать введенные им данные. Использование внутреннего класса для обработки события щелчка на кнопке (слушателя ActionListener) дает нам возможность без помех получить доступ к текстовому полю text и содержащийся в нем текст. Используй мы отдельный класс, нам пришлось бы каким-то образом заполучить ссылку на объект нашего окна, более того, в классе окна InnerClassEvents нам пришлось бы либо объявить текстовое поле открытым для доступа (public), либо добавить новый метод, возвращающий текст, набранный в текстовом поле.

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

42

ГЛАВА 2

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

Быстро и грязно

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

//AnonymousClassEvents.java

//Анонимные классы для обработки событий import javax.swing.*;

import java.awt.event.*; import java.awt.*;

public class AnonymousClassEvents extends JFrame { public AnonymousClassEvents() {

super("AnonymousClassEvents");

//анонимный класс присоединяется прямо на месте

//выход из приложения при закрытии окна addWindowListener(new WindowAdapter() {

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

}

});

//добавим кнопку

JButton button = new JButton("Нажмите меня"); getContentPane().add(button);

//слушатель создается в методе button.addActionListener(getButtonL());

//выводим окно на экран

Модель событий

43

pack();

setVisible(true);

}

// этот метод создает слушателя для кнопки public ActionListener getButtonL() {

return new ActionListener() {

public void actionPerformed(ActionEvent e) { System.out.println("ActionListener");

}

};

}

public static void main(String[] args) { SwingUtilities.invokeLater(

new Runnable() {

public void run() { new AnonymousClassEvents(); } });

}

}

В этом очень простом примере создается окно, в которое помещается кнопка. Для обработки закрытия окна мы создаем собственного слушателя оконных событий WindowEvent, и делаем это с помощью анонимного класса, который наследует от класса WindowAdapter и при закрытии окна (методом windowClosing()) завершает работу приложения. Все происходит прямо на месте: и регистрация слушателя, и создание слушателя, и его описание. Пожалуй, быстрее обработать событие невозможно. Однако видно, что получавшийся код весьма запутан и плохо управляем: нет никакой возможности получить ссылку на объект-слушатель, нельзя унаследовать от него, анонимный класс не может получить доступ к переменным внешней области видимости, которые не были объявлены неизменными (final). Есть более удобный способ работы с анонимными слушателями — их можно создавать в специальных методах. В нашем примере — это метод getButtonL(), возвращающий слушателя нажатий кнопки (который просто выводит сообщение о нажатии в стандартный поток вывода). Он предоставляет чуть больше возможностей и удобства: класс находится в отдельном методе, метод легко найти, его можно переопределить.

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

Диспетчеризация

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

44

ГЛАВА 2

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

впарадигму объектно-ориентированного программирования). Есть и другой способ обработки событий, в котором используется противоположная идея: обработка событий происходит в одном классе (или в нескольких, но не в таком умопомрачительном количестве, как в предыдущих вариантах). Техника эта называется диспетчеризацией (dispatching), или перенаправлением (forwarding), и довольно часто используется в визуальных средствах разработки интерфейса.

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

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

//ForwardingEvents.java

//Техника диспетчеризации событий

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

public class ForwardingEvents extends JFrame { public ForwardingEvents() {

super("ForwardingEvents");

//при закрытии окна - выход setDefaultCloseOperation(EXIT_ON_CLOSE);

//последовательное расположение getContentPane().setLayout(new FlowLayout());

//добавим пару кнопок

button1 = new JButton("ОК"); button2 = new JButton("Отмена"); getContentPane().add(button1); getContentPane().add(button2);

//будем следить за нажатиями кнопок

Forwarder forwarder = new Forwarder(); button1.addActionListener(forwarder); button2.addActionListener(forwarder);

//выводим окно на экран

pack();

setVisible(true);

}

JButton button1, button2;

// класс - слушатель нажатия на кнопку class Forwarder implements ActionListener {

public void actionPerformed(ActionEvent e) {

Источник: https://studfile.net/preview/15935977/