Возбуждение событий

В

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

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

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

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

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

Пример программы, посылающей событие

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

Чтобы создать программу, создайте новый проект, а внутри его элементарное приложение JBuilder, как было показано в главе 3. После этого выберите пункт меню File ∣ New Class, добавьте в проект новый класс JDiaiog И назовите его MessageSender.

MessageSender Будет посылать простое сообщение в Framel, A Framel Будет ото­бражать это сообщение пользователю с помощью элемента JTextArea. В Некотором смысле MessageSender Является сервером, поставляющим данные, a Framel Кли­ентом, использующим эти сгенерированные данные.

примечаниеЧтобы MessageSender Мог посылать сообщения Framel, Вначале нужно среди закладок просмотра файла внизу панели редактора выбрать закладку Bean. В Сред­ствах Bean есть пять закладок — выберите из них Events.

Если ваша версия JBuiIder не поддерживает BeansExpress, вам придется ввести этот код события вручную. В этом случае вам может понадобиться какое-то сред­ство для хранения полезных фрагментов кода, чтобы можно было легко находить их и вставлять в свои классы.

рис. 15.1. средства, с помощью которых класс может посылать и принимать
сообщения, находятся в окнах beansexpress, которые
поставляются вместе с редакциями se и enterprise

Страница Events поделена на два раздела, как показано на рис. 15.1. Слева нахо­дятся события, которые этот класс будет посылать, а справа — события, которые класс будет принимать или ожидать.

Выберите верхнее событие в левой секции, что значит, что этот класс будет посы­лать событие Action. В результате в файл MessageSender. Java Будет добавлен сле­дующий код:

Public synchronized void SddActionListener(ActionListener 1)

1

Vector v = actionListeners ≈ null ? new Vector (2) :

(Vector) actionListeners. clone();

If (!v. contains(1))

1

V. addElement(1); actionListeners = v;

}

1

Public synchronized void removeActionListener(ActionListener 1)

{

If (actionListeners!= null && actionListeners. contains(1))

1

Vectorv= (Vector) actionListeners. clone(); v. removeElement (1) ; actionListeners = v;

)

1

Protected void fireActionPerformed(ActionEvent e)

{

If (actionListeners!= null)

{

Vector listeners = actionListeners; iɪɪt count = listeners. size () ;

For (int i = О; I < count; i++)

{

((ActionListener) listeners. elementAt(i)).actIonPerformed(e);

}

}

}

Это довольно объемный код, тем не менее, он не труден для понимания. Первый метод, AddActionListener, Вы уже вызывали много раз. Теперь вы видите, как его реализовать. Это метод, вызываемый классом-слушателем, когда он должен внести себя в список получателей сообщений. Как видите, этот код просто добавляет вызы­ваемый класс во внутренний вектор, или список классов:

V.AddElement(1);

Весь остальной код — лишь обрамление этой главной строки. Вряд ли можно придумать что-либо проще.

Следующий метод, TemoveActionListener, Позволяет отменить регистрацию класса (или "разрегистрироваться"):

V.TemoveElement(1);

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

Последний метод, FireActionPerformed, В некотором смысле наиболее важен и интересен. Это метод, который вы будете вызывать, когда надо будет отправить со­общение. Этот метод просто перебирает весь свой список классов и для каждого класса вызывает метод SctionPerformed:

((ActionListener) listeners. elementAt(i)).SCtionPerformed(е);

Поэтому каждый класс-слушатель должен поддерживать интерфейс ActionListener. Если бы он не поддерживал этот интерфейс, то классы из списка невозможно было бы привести к типу ActionListener, А, значит, и невозможно было бы вызвать их метод SctionPerformed. Вот полное объявление интерфейса ActionListener:

Public Interfsce ActionListener extends EventListener

{

Public void SCtionPerformed (ActionEvent e) ;

}

Из этого интерфейса следует, что любой поддерживающий его класс должен со­держать метод SctionPerformed, Который принимает в качестве единственного па­раметра ActionEvent. Если собрать все эти части воедино, то вы увидите, что систе­ма потрясающе проста. В ней нет ничего сложного — если вы по-настоящему пони­маете суть объектно-ориентированного программирования (ООП). Если вы ничего не знаете об интерфейсах, приведении типов и полиморфизме, то тогда, конечно, все это выглядит несколько загадочно. Но если вы понимаете ООП, то это очень простой и наглядный пример применения его концепций. Все выглядит так, как будто бы разработчики поставили себе цель создать наиболее простую демонстра­цию, иллюстрирующую базовые принципы интерфейсов и полиморфизма.

Метод, посылающий сообщение

Последний вопрос с посылающей стороны уравнения — как же все-таки послать сообщение. Вот соответствующий код:

Void jButtonl_actionPerformed(ActionEvent Е)

{

String message = "Чем более спел пшеничный колос, \п" +

" тем ниже он склоняет свою голову. ";

ActionEvent event = new ActionEvent(this, 1, message) ; fireActionPerformed(event);

}

Глядя на заголовок метода, видно, что это простой обработчик щелчка кнопкой мыши. Метод начинается с объявления сообщения типа String, Которое он от­правляет клиенту. Следующий шаг — создание экземпляра класса ActionEvent. Это Класс, который на самом деле будет послан сервером клиенту при вызове метода FireActionPerformed.*

Protected void fireActionPerformed(ActionEvent Е)

{

. . . здесь пропущена часть кода

((ActionListener) listeners. elementAt(i)).actionPerformed(е);

. . . здесь пропущена часть хода

}

У класса ActionEvent Есть три конструктора. Чаще всего используется тот, кото­рый показан ниже. Он принимает три параметра:

Public ActionEvent(Object source, int id, String command)

Первый параметр — это экземпляр объекта, посылающего сообщение. Как вы помните, ActionEvent Поддерживает метод Getsource. Мы уже пользовались им в предыдущей главе, в программе StringTestOl, Для определения, какой из компо­нентов отправил сообщение. ActionEvent. getsource() Просто возвращает метод, который передается в этом первом параметре.

Второй параметр — это идентификационный номер (ID), который вы присваи­ваете событию. C помощью этого ID можно определить источник и назначение по­сланного сообщения. Точно так же, как с помощью Getsource Мы определяли, какой конкретно компонент послал сообщение, вы можете использовать этот пара­метр для определения типа сообщения, отправленного тем или иным компонен­том. Если компонент посылает сообщения только одного типа, как в данном слу­чае, то польза от этого параметра не очень велика. Конечно, если посылаемое вами сообщение само является целым числом, вы можете использовать этот параметр для пересылки сообщения.

Последний параметр, Command Это строка, содержащая посылаемое сообще­ние. В нашем случае мы послали старинную японскую пословицу, восхваляющую скромность. Именно здесь помещается параметр команды.

Как я уже упоминал, у класса ActionEvent Имеется еще два конструктора. Эти конструкторы позволяют добавить время отправки сообщения и информацию о том, каки? клавиши были при этом нажаты. В большинстве случаев эти дополни­тельные параметры не нужны, так что можно пользоваться первым конструктором.

Когда сообщение полностью подготовлено, оно может быть послано классу-кли­енту C помощью вызова FireActionPerformed:

ActionEvent event = new ActionEvent(this, 1, message); fireActionPerformed(event);

Среди различных методов отклика на событие кнопки, которые мы видели в предыдущей главе, вы познакомились с одним способом, когда класс-клиент может ожидать сообщения, которые посылает класс, подобный MessageSender. java. Эта техника подразумевает создание класса-адаптера, вызов метода AddActionListener Класса-"сервера" и, наконец, создание метода обработки этого вызова.

Однако существует и другой способ приема сообщений: Framei Сам может под­держивать интерфейс ActionListener. Код, обеспечивающий такую возможность Framel, Легко написать, хотя для создания обработчика события вы можете вос­пользоваться и средствами BeansExpress. На рис. 15.2 показана страница Events в BeansExpress.

рис. 15.2. c помощью beansexpress можно предложить классу ожидать события конкретного вида

Вместо того чтобы Framel Генерировал событие, мы предложим ему ожидать со­бытия. После отметки флажка Action в правой колонке, в Framel вставляется следу­ющий код:

Public class Framel extends JFrame implements ActionListener

{

.. . Здесь пропущена часть кода

Public void actionPerformed(ActionEvent Е)

{

1

}

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

Public void actionPerformed(ActionEvent Е)

∕**6todo implement this java.awt.event.actionlistener method*/{

Throw new java. Iang-UnsupportedoperationException(

"Method actionPerformed() " + ,, not yet implemented. ") ;

примечание}

Ввиду того что имя actionPerformed не очень-то информативно, мне часто хотелось переименовать этот метод. Но вы, видимо, понимаете, что переименова­ние actionPerformed было бы серьезной ошибкой. Мы знаем, что интерфейс ActionPerfonaed задает, что производный от него класс должен реализовать метод actionPerfonned. Если переименовать этот метод, то класс уже не будет поддерживать интерфейс и поэтому, во-первых, не будет компилироваться и, во-вторых, даже если и скомпилируется, не будет принимать сообщения так, как надобно. Если это вам не понят­но, посмотрите еще раз на интерфейс ActionListener:

Public interface ActionListener extends EventListener {

Public void actionPerformed(ActionEvent e);

}

Сказать, что класс реализует этот интерфейс — все равно что сказать, что в нем должен присутствовать метод actionPerformed. Это все соглашения между классом, реализу­ющим интерфейс, и самим интерфейсом. Реализация интерфейса полезна тем, что она гарантирует, что некоторый класс содержит определенный набор методов. Если подобная Гарантия не может быть получена, то метод FireActionPerformed работать не будет.

Но просто поддержки интерфейса ActionListener Далеко не достаточно. Нужно еще зарегистрировать класс Framel С классом MessageSender C помощью вызова AddActionListener. В классе Framel Программы SendSimpieMessage Эта задача вы­полнена следующим образом:

Void jButtonl_actionPerformed(ActionEvent e)

{

MessageSender messageSender = new MessageSender(this);

∕∕ Регистрация этого класса в качестве получателя // событий ActionPerformed

MessageSender. addActionListener(this);

Dimension dimension = this. getSize (); MessageSender. setBounds(O, O,

(int)dimension. getWidth(), (int)dimension. getHeight(),; messageSender. show ();

}

Этот метод сначала создает экземпляр класса MessageSender. Затем он вызывает метод AddActionListener. И, наконец, ОН Отображает класс MessageSender В Виде диалога. После всего этого все готово для обратного вызова классом MessageSender И отправки сообщений классу Framel. Эти сообщения отображаются на элементе JTextArea Интерфейса Framel:

Public void actionPerformed(ActionEvent e)

{

J TextAreal. append (e. getActionCommand () + ,,N") ;

}

Обратите внимание, что строка, связанная с ActionEvent Во время вызова его конструктора, выбирается с помощью вызова метода GetActionCommand. Не нужно быть семи пядей во лбу, чтобы догадаться, что с помощью метода getɪŋ() можно также выбрать ID, переданный конструктору. Еще раз посмотрите на чаще всего употребляемый конструктор ActionEvent:

Public ActionEventf

Object source, // выбирается с помощью GetSource

Int id, // выбирается с помощь» GetID

String command) // выбирается с помощью GetActionCommand

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

Посылка специализированных сообщений

Если вы посмотрите на рисунки 15.4 и 15.5, то увидите, что существуют различ­ные виды сообщений, которые мы можем пересылать между объектами. Стоит по­тратить некоторое время, чтобы хотя бы бегло ознакомиться с этими различными видами сообщений.

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

Для создания этой программы сначала создайте новый проект и вставьте в него новое приложение, как описано в главе 3. Как показано на рис. 15.4, с по­мощью пункта меню File ∣ New Class добавьте в свое приложение новый класс и назовите его PoetMessageSender. java. Этот класс должен быть производным от Java. Iang-Object И находиться в пакете Custommessagesender. poet.

рис. 15.3.
пример вывода программы custommessagesender

CF* C∣⅛tb Wizard

l⅛⅛CreateanewJavaciass

Fill Intheflelds below to eetthe package, name, base cι<eβ, end other options (or the Java class wl Ich will be created.

< class infbrrnatlon— ~-ri ——
s package: jcustomniessagese der.poet
u -∑j
class name. {poetmessagesendeι∣,• gaseclass: iisvajangobtect options
; f? public
: Γ* oeneratemalnmethod p osnerate headercomments
рис. 15.4. создаваемый вами новый класс должен находиться в подкаталоге вашего главного пакета poet
p generate default constructor p oyerrldeabetractmethods
cancel
help

примечаниеБолее глубокое обсуждение пакетов можно найти в части V, "Управле­Ние проектами".

Выделите новый класс в панели содержимого. Выберите среди закладок. низу редактора закладку Bean, а затем перейдите на страницу Events. Наверху этой стра­ницы вы увидите кнопку с надписью "Create Custom Event" ("Создание специализи­рованного события"). Щелкните на этой New Event Set (Новый набор событий) со­здайте новое событие с именем Poet, как показано на рис. 15.5. Создаваемый вами класс должен генерировать два со­бытия. Вы можете оставить имя первого события предложенным по умолчанию: dataChanged. Второе событие назовите PercentDone В основном именно суще­ствование этого второго метода делает ваше событие уникальным специализи­рованным событием.

Щелкните на кнопке OK в диалогом окне, приведенном на рис. 15.5. Вы вер­нетесь обратно на страницу Events в BeanExpress. Как показано на рис. 15.6, отметьте в левой колонке флажок рядом с созданным вами событием.

кнопке. в появившемся диалоговом окнерис. 15.5. заполните в диалоговом окне new event set, по крайней мере, два поля. вверху введите имя набора событий. в нижней части диалогового окна введите имена одного или более методовПосле того как вы отметите флажок возле слова Poet, JBuilder сгенерирует код обработки события. Само событие будет сохранено в файле PoetEvent. java, А его интерфейс слушателя — в файле

PoetListener. java. Первоначально класс PoetEvent Выглядит следующим об­разом:

Package Custommessagesender. poet; Import java. util.[†] [‡];

Public class PoetlEvent extends EventObject

{

Public PoetlEvent(Object source)

{

Super(source);

}

)

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

Памятуя о том, с каким трепетом современные компьютерные корпорации отно­сятся к метафизическим английским поэтам XVIl века, я решил, что будет замеча­тельно, если я превращу PoetEvent В небольшой класс, позволяющий легко встав­лять в программы некоторые из их стихов. Результат этого превращения показан в листинге 15.2. В листинге 15.3 приведен файл PoetMessageSender. java, Приспо­собленный ДЛЯ Генерации сообщений PoetEvent.

Листинг 15.1. Специализированный интерфейс слушателя, созданный для работы с классом PoetEvent1 приведенным в листинге 15.2

Package Custommessagesender. poet;

Import java. util.*;

Public interface PoetListener extends EventListener

{

Public void dataChanged(PoetEvent e) ; public void percentDone(int percent);

}

Листинг 15.2. Специализированный класс события, которое может быть отправлено

Private String sites[] = new String[] {

"http://www. Iuminarium. org∕sevenlit∕donne∕",

"http:∕∕www. Iuminarium. org∕sevenlit∕herbert∕",

"http://www. Iuminarium. org∕sevenlit∕vaughan∕",

"http://www. library. Utoronto.ɑa/uteɪ/rp/authors/traherne. html", "http://www. Iuminarium. org∕sevenlit∕cowley∕",

"Http://www. luminarium. org/sevenlit/crashaw/",

"http://www. Iuminarium. org∕sevenlit∕marvell∕",

"http:∕∕www. library. Utoronto. ca∕utel∕rp∕authors∕coleridg. html"}; private String poets[] = new String[] {"John Donne",

"George Herbert", "Henry Vaughan", "Thomas Traherne",

"Abraham Cowley", "Richard Crashaw", "Andrew Marvell",

"Samuel Taylor Coleridge");

Private String dates[] = new String[] ("1572-1631",

"1593-1633", "1622-1695", "1636-1674", "1618-1667",

"1612-1649", " 1621-1678", "1772-1834");

Private String poems[] = new String[] {

"JohnJJonne-Song. html",

"George_Herbert-Love_III. html",

"Henry_Vaughan-The_Shower. html",

"Traherne. html",

"Abraham_Cowley-The_Spring. html",

"Richard_Crashaw-Song. html",

"Andrew_Marvell-The_Mower_to_the_Glo-Worms. html",

"samuel taylor coleridge-songs Ofthepixies. html"};

Public PoetEvent(Object source)

{

Super(source);

)

Public PoetEvent(Object source, int percentDone) (

Super(source);

This. percentDone = percentDone;

)

Private int getNewPoetlndex ()

(

Return random. nextlnt(poems. length);

)

Public String getFileName()

(

Return FileBox. getCurrentDirectory() +

"poems" + FileBox. fIleSeparator + poems[poetlndex];

//return "rohan∕temp∕" + poems[poetlndex];

)

Public URL getURL()

{

Poetlndex = getNewPoetlndex();

String opening = "file://";

String CurrentFile = getFileName();

URL url = null;

Try

<

Url = new URL(opening + currentFile);

)

Catch (MalformedURLException ex)

{

Ex. printStackTrace ();

)

Return url;

Public String getPoet()

{

Return poets[poetlnde×];

}

Public String[] getPoets()

{

Return poets;

}

Public int getPoetlndex()

{

Return poetlndex;

}

Public String getDate()

<

Return dates[poetlndex];

>

Public String[] getDates()

{

Return dates;

}

Public String[] getSites()

{

Return sites;

}

Public String getSite()

1

Return sites! poetlndex];

}

Public int getPercentDone() {

Return percentDone;

}

}

Private void runApp()

{

Date d = new Date();

Long time = d. getTime() — saveTime;

System. out. println("Время: " + time);

Try

{

Thread. sleep(1000);

}

Catch (XnterruptedException ex)

{

Ex. printStackTrace();

}

Float percentLeft = ((float) time ∕ SwitchTime) * 100; PoetEvent pe = new PoetEvent(this, (int)percentLeft); if ((time > swxtchTime) (firstTime))

{

FirstTime = false;

System. out. println("отправка сообщения");

Date newDate = new Date(); saveTime = newDate. getTime(); fIreDataChanged(pe); fIrePercentDone(pe);

}

Else

(

FIrePercentDone(pe);

}

}

Public void run ()

{

While(true)

{

RunApp();

J

>

Public synchronized void removePoetListener(PoetListener 1) {

If (poetListeners! = null && poetListeners. contains(1))

{

Vector v = (Vector) poetListeners. clone(); v. removeElement(1); poetListeners = v;

>

)

Public synchronized void addPoetListener(PoetListener 1)

{

Vector v — poetListeners == null? new Vector(2) :

(Vector) poetListeners. clone();

If (∙v. contains(1))

{

V. addElement(1); poetListeners = v;

)

}

Protected void fIreDataChanged(PoetEvent e)

{

If (poetListeners ∙= null)

{

Vector listeners = poetListeners; int count = listeners. size(); for (int i = O; i < count; i++)

{

((FoetListener) listeners. elementAt(i)).dataCħanged(e);

)

1

I

Protected void firePercentDone(PoetEvent e)

(

If (poetListeners!= null)

{

Vector listeners = poetListeners; int count = listeners. size(); for (int i = O; i < count; i++)

{

((PoetListener) listeners. elementAt(i)).ρercentDone(e);

)

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

Специализированный класс PoetEvent

примечаниеКроме громадной пользы для команды разработчиков редакции Enterprise, класс PoetEvent Также иллюстрирует, как создавать специализированное событие. Код, приведенный в листинге 15.3, содержит несколько методов, предназначенных для выборки информации о метафизических поэтах. Он выбирает стихи, имена поэтов, даты их жизни и URL-адреса сайтов, где можно получить исчерпывающую инфор­мацию об этих поэтах.

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

Я не хочу долго обсуждать особенности класса PoetEvent. Тем не менее, хотелось бы обратить ваше внимание на то, что при вызове его метода getURL выдается URL — адрес случайно выбранного стихотворения. Все стихи можно включены в состав со­провождающих книгу материалов. Их можно найти в подкаталоге poems каталога программы CustoinMessageSender.

Класс PoetEvent Использует случайное число для выбора следующего стихотво­рения, отображаемого пользователю. C помощью этого же случайного числа вы мо­жете узнать также имя поэта, написавшего стихотворение, даты его жизни и URL — адрес Web-сайта, где можно почерпнуть дополнительную информацию о нем.

В общем, синтаксис класса PoetEvent Довольно прост. Но в данной главе нас больше интересует не сам класс, а отправка этого класса в качестве события.

Возбуждение специализированного события в потоке

Класс PoetMessageSender. java Генерирует события PoetEvent. Большая часть кода этого класса создана в результате отметки флажка возле события Poet (см. рис. 15.6). Чтобы разобраться в остальной части метода, вам необходима дополнитель­ная информация О назначении программы CustoinMessageSender

Программа CustoniMessageSender Не требует участия пользователя в своей рабо­те. Вместо этого она отображает часто сменяемую информацию, которую может наблю­дать пользователь. А именно, каждые Х секунд пользователю представляется новое сти­хотворение. К этому стихотворения прилагается информация о написавшем его поэте.

Назначение класса CustoniMessageSender Состоит в посылке каждые Х секунд но­вого стихотворения. Время, через которое меняются сообщения, задается в конст­рукторе объекта. Кроме того, каждую секунду программа посылает уведомление, на­сколько долго будет видна текущая информация, до того как будет послано следую­щее сообщение. Framel Всего лишь принимает события и отображает пользователю их содержимое.

примечаниеВажно понять, что необходимость создания специализированного класса следу­ет из того, что класс посылает два различных вида сообщений. Если бы класс посы­лал только события PoetEvent, То для их посылки можно было бы использовать приведение типа и одно из стандартных событий Java. Но поскольку этот класс по­сылает два различных вида информации, то мне пришлось создать специализиро­ванные сообщения, предназначенные конкретно для данной ситуации.

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

рис. 15.6. для создания с помощью beansexpress кода, генерирующего новое событие, вам нужно лишь отметить это событие

Создание таймера с помощью потоков

Есть несколько различных способов сделать так, чтобы класс PoetMessageSender Генерировал события через заданные промежутки времени. Техника, которую при­меняю я, предполагает запуск класса PoetMessageSender В потоке (thread). Этот по­ток создан для возбуждения событий PoetEvent Каждые Х секунд.

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

Public interface Runnable

{

Public abstract void run() ;

}

Чтобы класс PoetMessageSender Поддерживал этот интерфейс, вам нужно вы­полнить почти те же действия, что и при обработке интерфейса ActionListener. Конкретнее, необходимо изменить заголовок класса и вставить в него метод Run:

Public class PoetMessageSender implements Runnable

∕∕ … Здесь пропущен большой фрагмент кода public void run()

}

Чтобы PoetMessageSender Поддерживал интерфейс Runnable, Можно воспользо­ваться мастером создания интерфейса или впечатать требуемый код вручную. Я уже описывал работу с мастером создания интерфейса, ручной ввод текста тоже не пред­ставляет особых трудностей, поэтому я оставляю этот шаг на ваше усмотрение.

Суть интерфейса Runnable Очень проста. Сначала создается экземпляр JDK- класса Thread, При этом его конструктору через параметр передается PoetMessageSender:

PoetMessageSender poetSender = new PoetMessageSender();

Thread t ~ new Thread (poetSender) ; t. start() ;

примечаниеЗатем вызывается метод Start Этого потока. После этого ваш поток автоматичес­ки вызовет метод PoetMessageSender. run (). Конечно, этот вызов будет оформлен в виде отдельного потока.

Другой способ состоит в порождении PoetMessageSender От класса Java. Iang. Thread. Если вы выберете этот способ, метод PoetMessageSender. run() будет выглядеть точно так же, как и в классе, реализующем Runnable. На самом деле сам класс Thread Реализует Runnable. Заголовок создаваемого класса будет выгля­деть следующим образом:

Public class PoetMessageSender extends Thread

При запуске этого класса код будет выглядеть так:

PoetMessageSender poetMessageSender = new poetMessageSender(); PoetMessageSender. start();

Преимущество реализации интерфейса Runnable перед порождением класса, до­чернего Thread, состоит просто в том, что ваш класс не будет перегружен дополни­тельным кодом, присущим любому классу, порожденному от Thread, и его также можно породить от какого-то другого класса. Таким образом, класс, дочерний в от­ношении Thread, следует порождать, только если необходимо перекрыть некоторые методы класса Thread, отличные от run(). В большинстве случаев лучше просто реализовать интерфейс Runnabie, как показано в рассматриваемом примере про­граммы.

Вот реализация метода Run Класса PoetMessageSender:

Public void run()

<

While(true)

{

RunApp();

}

}

Private void runApp()

{

Date d = new Date () ;

Long time = d. getTime() — saveTime;

System. out. println("Время: " + time); try

{

Thread. sleep (IOOO);

)

Catch (InterruptedException ex)

{

Ex. printStackTrace();

}

Float percentLeft = ((float) time ∕ switchTime) * 100;

PoetEvent pe = new PoetEvent(this, (int)percentLeft); if ((time > switchTime) ∣ ∣ (firstTime))

{

FirstTime = false;

Systern. out. println("отправка сообщения”);

Date newDate = new Date () ; saveTime = newDate. getTime(); fIreDataChanged(pe); firePercentDone(pe);

)

Else

{

FirePercentDone(pe);

)

}

Метод run () в бесконечном цикле обращается к методу runApp(). В этом нет ничего страшного, т. к. этот класс выполняется в своем собственном потоке, и един­ственное, для чего предназначен этот класс — это для вызова метода runApp(). Ко­роче, он может зациклиться, но именно это от него и требуется! Ясно, что этот цикл прекращается при закрытии программы.

Регулярные вызовы метода Thread. sleep каждый раз переводят поток в "спя­щее" состояние. Это значит, что этот поток почти все время "крепко сПйт" и ничего

Не делает. То есть, крутясь в бесконечном цикле, он не следит за временем. Когда он "просыпается", он проверяет, сколько прошло времени. Если это произошло при первом выполнении цикла или прошло достаточное количество времени, он посы­лает сообщение PoetEvent и уведомление, что осталось 100% времени:

If ((time > switchTime) ∣∣ (firstTime))

(

FirstTime = false;

Systern. out. println("отправка сообщения");

Date newDate = new Date () ; saveTime ≈ newDate. getTime() ; fIreDataChanged(ре); fIrePercentDone(ре);

)

Else

{

FIrePercentDone(ре);

}

примечаниеНаше специализированное событие поддерживает не один, а два метода. Пер­вый метод — dataChanged, а второй — PercentDone. Как вы знаете, метод DataChanged Содержит полностью загруженное и заново инициализированное со­бытие PoetEvent, А метод PercentDone Сообщает, сколько времени осталось до от­правки следующего PoetEvent.

Обратите внимание, что кроме создания и генерации события PoetEvent, метод runApp () также сбрасывает счетчик времени на текущее время. За­тем этот метод каждую секунду проверяет, прошло ли Х секунд со времени его после­Днего вызова. Если время вышло, он посылает следующее сообщение PoetEvent.

, Сообщения DataChanged ПОСЫЛЯЮТСЯ Каждые X секунд. Методы PercentDone Посылаются каждую секунду. Эта программа также "спит" одну секунду между итера­циями цикла. Поэтому, даже если и не пришло время возбуждать событие DataChanged, Все-таки надо уведомить главную программу, сколько времени оста­лось до генерации следующего события PoetEvent. Для этого код runApp вызывает метод fIrePercentDone Для запуска события PoetEventv которое содержит процент времени, прошедшего после последнего вызова FireDataChanged.

Protected void fireDataChanged(PoetEvent Е)

{

If (poetListeners!= null)

{

Vector listeners ≈ poetListeners; int count = listeners. size(); for (int i = O; i < count; i++)

{

((PoetListener) listeners. elementAt(i)).dataChanged(e);

}

)

Protected void firePercentDone(PoetEvent Е)

{

If (poetListeners! = null)

{

Vector listeners = poetListeners; int count = listeners. size(); for (int i = O; i < count; i++)

((PoetListener) listeners. elementAt(i)).percentDone(e);

примечание}

Если вы просмотрите события, возбуждаемые во всех доступных Java- интерфейсах слушателей, вы увидите, что они принимают всегда одни и те же парамет­ры. В нашем случае все методы из интерфейса PoetListener принимают в качестве параметра событие PoetEvent:

Public void dataChanged(PoetEvent Е) ; Public void percentDone(PoetEvent Е) ;

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

Protected void fireDataChanged(PoetEvent Е, Int percent)

(

If (poetListeners! = null)

{

Vector listeners = poetListeners; int count ≈ listeners. size(); for (int i = O; i < count; i++)

{

((PoetListener) listeners. elementAt(i)).dataChanged(e, percent);

}

Как видите, я слегка изменил метод fireDataChanged, чтобы он принимал не один па­раметр, а два. В этом случае один метод — dataChanged — может выполнить ту же за­дачу, которую в нашем интерфейсе решают методы dataChanged и percentDone. Как я уже сказал, синтаксически это полностью верно. Но в мире Java подобные вещи не при­няты Вместо этого нужно делать так, как это сделано в нашем примере. Стандарты очень важны, а в мире Java является стандартом поступать так, как сделано в программе CustoinMessageSender.

Особенности архитектуры события PoetEvent

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

■ Классы PoetEvent, PoetListener И PoetMessageSender Работают вместе, образуя один функциональный коллектив. Поэтому они собраны в свой собственный пакет. Вот почему я попросил вас поместить класс PoetMessageSender В отдельный пакет. Классы PoetEvent И PoetListener Автоматически создаются в том же самом пакете. Наличие этого пакета под­черкивает, что эти три класса работают как единое целое. Если вы захотите сделать их повторное использование еще более простым, вам следует помес­тить их в отдельный JAR-файл. В результате вы получите JavaBean-компонент, но эта тема будет рассматриваться ниже в данной книге.

■ Второй важный момент состоит в ТОМ, Что класс PoetMessageSender возбуж­дает события, но у него нет открытых классов, кроме конструктора, метода run и механизма ожидания событий Это может выглядеть как очень большая открытость, но на самом деле все, что вы можете сделать с этим классом — это создать его, поместить в поток и зарегистрировать другой класс в качестве его слушателя. Все эти действия выполняются один раз, и затем вы запускаете класс. Поток информации идет от PoetMessageSender к классу-слушателю, а обратного потока нет. Вы вызываете некоторые get-методы класса PoetEvent, но вы не вызываете никаких set-методов этого класса. Я обращаю на это ваше внимание потому, что при этом получается очень устойчивая ар­хитектура. Поскольку поток направлен только в одну сторону, то после запус­ка класса количество возможных ошибочных ситуаций ограничено. Но если вы начнете посылать сообщения и в обратную сторону, могут возникнуть про­блемы. В этом нет никаких ошибок, код полностью верен. Но в нем имеется тенденция к возникновению проблем. Подобные вопросы будут обсуждаться в части ΓV, "Архитектура проекта". Там вы увидите еще одну программу, FileRename, которая дополнительно иллюстрирует некоторые из упомянутых здесь принципов.

Ожидание специализированных событий

Чтобы класс ожидал события PoetEvent, и только их, JBuilder создал интерфейс PoetListener. Как вы знаете, класс PoetListener. java служит только для одной цели: он предназначен для объявления метода или методов, которые должны под­держиваться любым классом, желающим ожидать событий PoetEvent. Как можно наиболее легко использовать этот интерфейс, чтобы Framel смог ожидать события PoetEvent?

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

Public class Framel extends JFrame implements PoetListener

. . . Здесь пропущен большой фрагмент кода

Public void dataChanged(PoetEvent Е)

{

}

Public void percentDone(PoetEvent Е)

{

}

Третий способ — воспользоваться BeansExpress, если это средство доступно в ва­шей версии продукта. Выберите среди закладок просмотра файла внизу панели ре­дактора закладку Bean. Когда вы перейдете на страницу Events для Framel, вы не

->• import event setрис. 15.7. выбор специализированного класса poetlistener, чтобы framel смог ожидать события этого типаУвидите созданное вами специали­зированное событие. Вместо этого вам потребуется щелкнуть на кноп­ке Import Event Set (Импорт набора событий). После этого появится диалоговое окно с запросом имени класса, который вы хотите импор­тировать. Найдите в пакете Custommessagesenderpoet класс

рис. 15.8. новое специализированное событие добавлено на страницу bean events для framel х ⅜ ftamel × .*j foetevenr,.sender,create cu⅛utm ty∙ji
......
,import event seι.,m∣ ∖poet
-m1 —awt eventr (java,awt.event.*>- i□ action
sl dadjusimeni i dcomponent □container 3 dforus d∣tem й dkey ! qmouse 1. dmousemoiion
,i poel
-awt events (∣ava∙awt.event.*)- iaction !adjustment .component !container !focus iltem key
!mouse
imousemoiion
ubtn ...........
,ceeerat fropettier,source design man uml doc history,fjt- jbx⅜⅛r 7 . j⅛¾ne⅝r⅛vert.⅝tc⅛rt Λ<⅝∣rx⅛7lund⅛ικ ∙ι⅛√qιstoo∣mι>s⅜a⅛⅛3 wderittrfaciimnmestrayt-'

PoetListener, как показано на рис. 15.7. Два раза щелкните на кнопке OK, чтобы вер­нуться на страницу Bean Events. Теперь она должна выглядеть как показано на рис. 15.8, где вверху списка появилось новое событие. В правой колонке Listen for these events (Ожидать следующие события) щелкните на строке Poet. Как вы знаете, вы также можете организовать поддержку для Framel Интерфейса PoetListener С по­мощью мастера создания интерфейса.

Независимо от того, ввели ли вы код самостоятельно, с помощью мастера созда­ния интерфейса или воспользовались BeansExpress, Framel Теперь должен содер­жать два новых метода, которые могут ожидать события PoetEvent. Он также отме­чен как реализующий интерфейс PoetListener, Как показано во фрагменте кода в начале текущего раздела.

Теперь в Framel Необходимо добавить код, который создает экземпляр класса PoetMessageSender, Регистрирует Framel Как слушателя и затем запускает новый класс в отдельном потоке. Далее вам нужно заполнить код отклика на события PoetEvent:

// Отправка сообщении каждые 5000 миллисекунд, т. е. pas в 5 секунд PoetMessageSender poetSender = new PoetMessageSender(5000) ;

. . . здесь пропущен фрагмент кода

Public Framel()

{

. . . Здесь пропущен фрагмент кода poetSender. addPoetListener(this);

Thread t = new Thread (poetSender) ; t. start () ;

I

.. . здесь пропущен фрагмент кода private void SetPoetPage(PoetEvent е)

{

URL url = е. getURL();

System. out. println("Получено событие: ,, + url); jTextFieldPoet. setText(e. getPoet()); jTextFieldDates. setText(е. getDate()); jTextFieIdWebSite. setText(е. getSite()); try

{

If (jEditorPanel!= null)

{

JEditorPanel. setPage(url);

)

}

Catch (IOException ex)

{

System. err. printin("Страница установки исключения"); ex. printStackTrace();

}

}

Public void dataChanged(PoetEvent e)

{

Java. net. URL url = e. getURL();

System. out. println("Получено событие: " + url);

JTextFieldPoet. setText(e. getPoet());

JTextFieldDates. setText(e. getDate()); jTextFieldWebSite. setText(e. getSite()); try

<

JEditorPanel. setPage(url);

}

Catch (IOException ex)

{

)

)

Public void perCentDone(PoetEvent poetEvent)

{

Int percent = poetEvent. getPercentDone();

Systern. out. println(,’Процент: " + percent); jProgressBarl. setValue(percent);

)

Код, создающий ПОТОК, Находится В конструкторе Framel. События PoetEvent попадают в метод datachanged каждые 5 секунд. (Если вам интересно, откуда взя­лись 5 секунд, посмотрите на вызов конструктора класса PoetMessageSender в нача­ле этого листинга.) Метод percentDone вызывается каждую секунду, уведомляя пользователя, сколько времени пройдет до посылки следующего события.

Интерфейс программы CustoinMessageSender Содержит три элемента JTextField И один JEditorPane, Как показано на рис. 15.3. Текстовые поля пред­назначены для отображения стихотворения или части текста, связанной с поэтом. В нашем случае стихотворения хранятся в HTML-файлах. К счастью, элемент JEditorPane Может отображать не очень сложные HTML-тексты, так что у нас есть возможность создавать страницы, привлекательные для пользователя. Имя файла, который должен быть отображен, передается в объекте Java. net.Url. Этот класс очень гибок и может использоваться не только для работы с текстовыми файлами, но и для отображения на JEditiorPane Страниц, выгруженных из Internet.

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

Резюме

В данной главе вы узнали, как генерировать события и создавать специализи­рованные события. Первый пример программы возбуждал простое событие ActionEvent. При изучении этой программы вы узнали, как пользоваться сред­ством BeansExpress среды JBuilder для создания методов, применяемых для регист­рации и отмены регистрации классов, которые предназначены для ожидания ваших событий, а также познакомились с методом, запускающим сами события. Вы также увидели, как написать код, который создает экземпляр ActionEvent И затем вызы­вает интерфейс FireActionEvent.

В следующем разделе было показано, как создать программу, которая запускает не ActionEvent, А ваше собственное специализированное событие. Вы увидели, как написать простой класс PoetEvent, Который можно отправлять в виде события. Вы узнали, что многие специализированные события поддерживают не один, а два и более методов. Интерфейс PoetListener Задает два метода, определяющие события PoetEvent. Один содержит само событие, а другой сообщает, сколько времени оста­лось до возбуждения следующего PoetEvent. Далее глава содержит объяснение, что происходит при обработке этих двух методов, когда нужно запускать события.

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

Вы, видимо, уже поняли, что обработка событий является одной из наиболее важных частей языка Java. События играют важную роль во многих средствах Java, среди которых и классы Swing. Но это еще не все. Как было предложено в данной главе, события могут быть использованы для создания устойчивых компонентов, которые легко сделать повторно используемыми. Программы, использующие собы­тия, не всегда легко создавать, но когда они готовы, их легко сопровождать. Эта тема будет обсуждаться в части ΓV, "Архитектура проекта", особенно в главе 22, "Модуль­ность.

Чарли Калверт

Глава 16

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *