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

import java. awt. event.*;

import javax. swing.*;

// LowLevelEvents.Java Наблюдение за основными

//низкоуровневыми событиями

public class TestEvents extends JFrame {

// сюда мы будем выводить информацию:

JTextArea out;

TestEvents () {

super("LowLevelEvents");

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

JButton button = new JButton("Источник событий");

getContentPane().add(button, "South");

// добавим текстовое поле

getContentPane().add(new JScrollPane(

out = new JTextArea(30,1)), "North");

TestEvents. OurListener ol = new TestEvents. OurListener();

// регистрируем нашего слушателя

button.addKeyListener(ol);

button. addMouseListener(ol);

button. addMouseMotionListener(ol);

button. addFocusListener(ol);

setSize(800, 600);

setVisible(true); // выводим окно на экран

}

// внутренний класс - слушатель событий

class OurListener implements MouseListener, KeyListener, MouseMotionListener,

MouseWheelListener, FocusListener {

public void mousePressed(MouseEvent e) { out. append(e. toString() + "\n"); }

public void mouseReleased(MouseEvent e) { out. append(e. toString() + "\n"); }

public void mouseEntered( MouseEvent e) { out. append(e. toString() + "\n"); }

public void mouseClicked( MouseEvent e) { out. append(e. toString() + "\n"); }

public void mouseExited( MouseEvent e) { out. append(e. toString() + "\n"); }

public void keyTyped(KeyEvent e) { out. append(e. toString() + "\n"); }

public void keyPressed( KeyEvent e) { out. append(e. toString() + "\n"); }

public void keyReleased(KeyEvent e) { out. append(e. toString() + "\n"); }

public void mouseDragged(MouseEvent e) { out. append(e. toString() + "\n"); }

public void mouseMoved(MouseEvent e) { out. append(e. toString() + "\n"); }

public void focusGained(FocusEvent e) { out. append(e. toString() + "\n"); }

public void focusLost(FocusEvent e) { out. append(e. toString() + "\n"); }

public void mouseWheelMoved(MouseWheelEvent e) {

out. append(e. toString() + "\n");}

}

public static void main(String[] args) {

new TestEvents();

}

}

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

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

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

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

Таблица 2. Наиболее часто используемые высокоуровневые события

Краткое описание события

Методы слушателя

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

Событие PropertyChangeEvent. Обеспечивает работу механизма привязанных свойств JavaBeans

propertyChange (PropertyChangeEvent)

Практически все графические компоненты JavaBeans (в том числе все компоненты Swing)

Событие ChangeEvent. Запускается некоторыми компонентами и моделями для сообщения о своих изменениях

stateChanged (ChangeEvent)

Некоторые компоненты Swing. Многие модели используют это событие для связи с UI-представителями

Событие ActionEvent. Сообщает о действии над компонентом

actionPerformed (ActionEvent)


Компоненты Swing, у которых есть какое-то «главное» действие (например, у кнопки – нажатие)

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

НЕ нашли? Не то? Что вы ищете?

PropertyChangeEvent – это основополагающее событие архитектуры JavaBeans, оно юзволяет следить за тем, как и какие свойства меняются в компоненте (так назы-темые привязанные свойства). Все свойства компонентов Swing являются привязанными. Это не только позволяет использо-зать их в визуальных средствах, но и дает возможность моделям и UI-представителям эффективно взаимодействовать друг с другом. Событие ChangeEvent – это более простое событие, которое также дает знать об изменениях состояния компонента или модели. Довольно часто модели запускают это событие, сообщая об вменениях в данных.

В таблицу наиболее часто используемых событий также попало событие Action Event. Оно на самом деле встречается практически в каждом приложении, в основном благодаря тому, что почти в каждом приложении есть кнопки и/или меню. Впрочем, это событие возникает не только при щелчке на кнопке, оно часто используется и тогда, когда нужно сообщить о каком-то важном действии (выбор элемента в раскрывающемся списке, конец ввода в текстовом поле, срабатывание таймера и т. п.). Содержимое табл. 1 и 2 не нужно запоминать, но эта информация может пригодиться, если понадобится обработать событие, которое прежде обрабатывать не приходилось.

Техника написания слушателей

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

Адаптеры

Если вы посмотрите на интерфейсы некоторых слушателей, то обнаружите в них не один, а несколько методов. В некоторых слушателях методов довольно много (например, в слушателе оконных событий WindowListener). Это вполне логично – каждый метод отражает свое небольшое изменение в компоненте. Создание для каждого простого события отдельного слушателя привело бы к появлению невообразимого количества интерфейсов с расплывчатым предназначением, а обработка сложного события в одном методе неудобна – придется предварительно выяснять, что же именно произошло.

Однако оказывается, что использование интерфейсов слушателей с несколькими методами тоже не совсем удобно. Чаще всего при обработке события какого-либо типа нас интересует нечто конкретное (например, щелчок мыши или закрытие окна), а не все возможные варианты происходящего события. Тем не менее при реализации интерфейса Java обязывает определить все методы этого интерфейса, даже те, которые нас вообще не интересуют. Это не только неудобно, но и вносит некоторую путаницу в код, в котором появляются «пустые», ничего не выполняющие методы.

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

// Adapters. java

// Использование адаптеров вместо интерфейсов

import javax. swing.*;

import java. awt. event.*;

import java. awt.*;

public class Adapters extends JFrame {

public Adapters() {

super("Adapters");

// при закрытии окна - выход

setDefaultCloseOperation(EXIT_ON_CLOSE);

// регистрируем слушателя

addMouseListener(new MouseL());

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

setSize(200, 200);

setVisible(true);

}

// наследуем от адаптера

class MouseL extends MouseAdapter {

// следим за щелчками мыши в окне

@Override

public void mouseClicked(MouseEvent e) {

System. out. println(e);

}

}

public static void main(String[] args) {

SwingUtilities. invokeLater(

new Runnable() {

public void run() {

new Adapters();

}

});

}

}

В примере создается небольшое окно, к которому добавляется слушатель событий от мыши MouseListener. Однако реализовывать интерфейс слушателя мы не стали, а создали новый внутренний класс MouseL, унаследованный от класса адаптера MouseAdapter. Вместо того чтобы определять все методы слушателя (а их ни много, ни мало – целых пять, можете убедиться в этом, заглянув в табл. 1), мы переопределили только один. Здесь нас интересовало только, когда пользователь щелкает кнопками мыши в окне, так что нам пригодился метод mouseClicked(). Остальные четыре метода остались в стороне, как будто их и не было.

Для всех низкоуровневых событий из пакета java. awt. event, слушатели которых состоят более чем из одного метода, имеются адаптеры. Чаще всего они и используются при обработке событий, и только в тех редких случаях, когда программу интересуют все события определенного рода, задействуются интерфейсы. Узнать название класса адаптера очень просто: если слушатель называется XXXListener, то имя адаптера выглядит как XXXAdapter. Но вот для высокоуровневых событий компонентов Swing имеются только слушатели, адаптеров для них вы не найдете. Видимо, разработчики решили, что если хоть какое-то событие от компонента обрабатывается, то информация о нем нужна полная. Довольно странное решение.

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

MouseClicked(MouseEvent e)

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

Каждому событию – по слушателю

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

Система событий 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);

// последовательное расположение

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();

}

});

}

}

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

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

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

Заголовок этого раздела может показаться странным, но техника, с которой мы сейчас познакомимся, никаких других ассоциаций и не вызывает: код программы превращается в пеструю смесь, словно по нему прошелся кто-то в гигантских сапогах, оставляя повсюду расплывчатые грязные следы. Кроме того, что внутренние классы можно определять внутри класса, просто вкладывая один класс в другой, существует еще один способ создавать классы. Это самый быстрый и самый неопрятный способ, а создаваемые классы называются анонимными (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());

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

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(), возвращающий слушателя нажатий кнопки (который просто выводит сообщение о нажатии s стандартный поток вывода). Здесь уже чуть больше возможностей и удобства: сласс находится в отдельном методе, метод легко найти, его можно переопределить.

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

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

Вне всяких сомнений, техника снабжения каждого события собственным слушателем, располагающимся в отдельном классе, является самой распространенной, и по праву: она действительно разделяет места возникновения и обработки события и позволяет создавать кристально чистый код. Но справедливости ради стоит отметить, что эта техника не единственная и не всем она по душе (хотя она идеально вписывается в парадигму объектно-ориентированного программирования). Есть и другой способ обработки событий, в котором используется противоположная идея: обработка событий происходит в одном классе (или в нескольких, но не в таком умопомрачительном количестве, как в предыдущих вариантах). Техника эта называется диспетчеризацией (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) {

// рассылаем события по методам

if (e.getSource() == button1) onOK(e);

if (e. getSource() == button2) onCancel(e);

}

}

// обработка события от кнопки "ОК"

public void onOK(ActionEvent e) {

System.out.println("onOK()");

}

// обработка события от кнопки "Отмена"

public void onCancel(ActionEvent e) {

System. out. println("onCancel()");

}

public static void main(String[] args) {

SwingUtilities. invokeLater(

new Runnable() {

public void run() {

new ForwardingEvents();

}

});

}

}

В данном примере создается окно, в которое помещено две кнопки. К каждой кнопке присоединен слушатель событий Forwarder, следящий за нажатиями кнопок, причем слушатель этот создается только один раз (что без сомнений позволяет экономить память). В самом слушателе проделывается немудреная работа: при возникновении события от кнопки выясняется, в какой именно кнопке произошло это событие, после чего вызывается метод с соответствующим названием. Слушатель Forwarder можно расширить, чтобы он поддерживал гораздо большее число кнопок, и при этом не придется создавать новые классы – достаточно будет лишь определить новые методы. Если в дальнейшем понадобится модифицировать работу приложения, это будет несложно сделать: надо унаследовать новый класс от класса окна и переопределить интересующие нас методы, например onOk().

Диспетчеризация имеет свои преимущества: код получается более компактным и в некотором смысле более привычным для тех программистов, что перешли на Java с других языков программирования, где обработка событий осуществляется именно в методах, а не в отдельных классах. Именно такая иллюзия и создается в результате использования такой техники: мы видим несколько методов, вызываемых при возникновении событий, и реализуем обработку этих событий, переопределяя методы. Однако есть здесь и свои ограничения: то, как события рассылаются по методам, целиком зависит от классов слушателей, подобных Forwarder, и если событие от какого-то компонента не обрабатывается этим классом, вам остается лишь развести руками и писать слушатель самому. Если компонентов в интерфейсе достаточно много и для каждого из них создается свой метод, збрабатывающий некоторое событие, получится гигантский класс, «битком наби-гый» методами, а работать с такими классами всегда неудобно; более того, появ-нение таких классов свидетельствует о нарушении основополагающего правила объектно-ориентированного программирования: каждый класс решает собственную небольшую задачу.

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

Проблема висячих ссылок

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

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

Теперь надо вспомнить, как в Java работает сборщик мусора. Хорошо известно, что созданные объекты в Java не нужно явно удалять: об этом заботится сборщик мусора. Он работает в фоновом режиме параллельно с программой, периодически включается и производит удаление объектов, на которые не осталось явных ссылок. Здесь-то нас и поджидает сюрприз – все те графические компоненты которые визуальное средство удалило из контейнера, не удаляются сборщиком мусора, потому что в них еще имеются явные ссылки на слушателей событий, ранее присоединенных визуальным средством. И чем больше будет работать программа, чем интенсивнее пользователь будет создавать интерфейсы, тем меньше останется памяти, и в конце концов все может завершиться аварийным завершением программы с потерей несохраненных данных.

Описанная ситуация возникает не так уж и редко, потому что Swing прекрасно подходит для программ с динамическим пользовательским интерфейсом: ш ничего проще добавления компонентов в контейнер и присоединения к ним слушателей прямо «на ходу». Поэтому, если вы пишете программу с заранее неизвестным количеством компонентов, не забывайте отсоединять от удаляемых компонентов ранее присоединенных слушателей методом removeXXXListener(). В обычных же программах, вроде тех, что мы рассматривали в этой главе, необходимости строго следить за слушателями нет – количество компонентов ограничено, и все они остаются на месте на время работы программы.

Кстати, висячие ссылки – это проблема не системы обработки событий Swing, а побочный эффект использования шаблона проектирования «наблюдатель». Везде, где он применяется, в том или ином виде возникают висячие ссылки (субъект и наблюдатель чаще всего имеют разное время жизни) и с ними приходится бороться. Тем не менее дефект это не такой уж серьезный, и достоинств наблюдателя перевешивают его недостатки.

Таблица 3.

Компонент

Назначение компонента

1

JLabel

"Метка" - вывод однострочного неформатированного текста

2

JButton

"Кнопка" - кнопка с текстом и/или с картинкой

3

JToggleButton

"Защелкивающаяся кнопка" - кнопка с фиксацией. Может быть одной из нескольких таких кнопок в группе, в этом случае нажатие одной кнопки вызывает отпускание другой. Работа группы обеспечивается компонентом ButtonGroup, который должен быть перетащен на форму, а затем назначен свойству buttonGroup.

4

JCheckBox

"Чекбокс" - пункт выбора с независимой фиксацией.

5

JRadioButton

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

6

ButtonGroup

Обеспечивает работу групп компонентов JToggleButton или JradioButton.

7

JComboBox

"Комбобокс" - выпадающий список.

8

JList

Прокручивающийся список.

9

JTextField

"Текстовое поле" - однострочный пункт ввода и редактирования текста.

10

JTextArea

"Текстовая область" - многострочный пункт ввода и редактирования текста.

11

JPanel

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

12

JTabbedPane

"Панель с закладками" - каждый положенный на нее компонент показывается в отдельной закладке. Чтобы разместить на одной закладке несколько компонентов, сначала положите на панель с закладками обычную панель. Для того, чтобы создать последующие закладки, выделите панель с закладками, вызовите правой кнопкой мыши всплывающее меню, пункт Add From Palette ("добавить из палитры"), и добавьте еще одну панель или другой компонент.

13

JScrollBar

Независимая полоса прокрутки. Используется редко - для программно управляемой прокрутки содержимого компонентов, для которых отсутствуют встроенные полосы прокрутки.

14

JScrollPane

"Панель с полосами прокрутки"

15

JMenuBar

"Меню формы" - предназначено для расположения в нем компонентов типа JMenu (заголовков меню).

16

JPopupMenu

"Всплывающее меню" - предназначено для расположения в нем компонентов типа JMenuItem (пунктов меню).

17

JSlider

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

18

JProgressBar

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

19

JSplitPane

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

20

JFormattedTextField

"Поле ввода форматированного текста"

21

JPasswordField

"Поле ввода пароля" - вводимый текст отображается звездочками.

22

JSpinner

"Спиннер" - поле ввода числа с кнопками увеличения/уменьшения.

23

JSeparator

"Сепаратор" - разделительная линия. Используется в декоративных целях для разделения рабочих областей формы и других группирующих компонентов.

24

JTextPane

"Текстовая панель". По умолчанию автоматически переносит текст на новую строку. А не располагает в одну строку с показом горизонтального скроллера, как это делает JTextArea.

25

JEditorPane

"Панель текстового редактора"

26

JTree

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

27

JTable

"Таблица" - показ текстовой таблицы. Имеет возможность заполнения значениями по умолчанию на этапе проектирования.

28

JToolBar

"Тулбар" - панель инструментов. Обычно на нем размещают кнопки JToggleButton, для которых назначены иконки.

29

JInternalFrame

"Дочернее окно" - окно многооконного приложения. Его можно перемещать в пределах родительского окна - главного окна приложения. В настоящее время такой стиль приложений практически не используется.

30

JLayeredPane

"Панель с абсолютным позиционированием элементов"

31

JDesktopPane

"Панель - рабочий стол". Еще один тип панели с абсолютным позиционированием элементов.

32

JOptionPane

"Диалоговая панель" - предназначена для показа диалоговых форм. В отличие от большинства других компонентов работа идет с помощью методов класса. Имеются вызовы диалогов:

    С сообщением:

·  javax. swing. JOptionPane. showMessageDialog(null, "Кнопку нажали");

JOptionPane. showMessageDialog(null,"Привет!", "Заголовок сообщения", JOptionPane. INFORMATION_MESSAGE);

    С подтверждением:

int option=javax. swing. JOptionPane. showConfirmDialog(null,"Продолжить?");

Проверка, какую кнопку нажали или диалог закрыли без осуществления выбора, осуществляется сравнением с константами javax. swing. JOptionPane. NO_OPTION, CANCEL_OPTION, CLOSED_OPTION, OK_OPTION, YES_OPTION

    С предложением ввести значение:

String input=javax. swing. JOptionPane. showInputDialog(null,"Введите значение:");

- при отказе от ввода возвращается null.

Первый параметр - имя формы, в которой показывается диалог. Если он null - используется форма по умолчанию (главная форма приложения).

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

33

JColorChooser

"Диалог выбора цвета" - предназначен для выбора пользователем цвета.

34

JFileChooser

"Диалог выбора файла" - предназначен для выбора пользователем файлов. Перед использованием требуется положить его на какую-нибудь диалоговую форму ( JDialog, JFrame ) или какой-нибудь группирующий компонент формы.

35

JFrame

"Экранная форма". Показывается вызовом вида jFrame1.setVisible(true) ;

36

JDialog

"Диалоговая форма". Показывается вызовом вида jDialog1.setVisible(true) ;

Сведения по перечню методов этих компонентов и их аргументам следует искать в документации по JDK.

Из за большого объема этот материал размещен на нескольких страницах:
1 2 3 4