Java Съвет 35: Създайте нови типове събития в Java

Въпреки че JDK 1.1 със сигурност е рационализирал обработката на събития с въвеждането на модела на делегиране на събития, това не улеснява разработчиците да създават свои собствени типове събития. Основната процедура, описана тук, всъщност е доста ясна. За по-голяма простота няма да обсъждам концепции за активиране на събития и маски за събития. Освен това трябва да знаете, че събитията, създадени с помощта на тази процедура, няма да бъдат публикувани в опашката на събитията и ще работят само с регистрирани слушатели.

В момента ядрото на Java се състои от 12 типа събития, дефинирани в java.awt.events:

  • ActionEvent
  • AdjustmentEvent
  • ComponentEvent
  • ContainerEvent
  • FocusEvent
  • InputEvent
  • ItemEvent
  • KeyEvent
  • MouseEvent
  • PaintEvent
  • TextEvent
  • WindowEvent

Тъй като създаването на нови типове събития е нетривиална задача, трябва да разгледате събития, които са част от основната Java. Ако е възможно, опитайте се да използвате тези типове, вместо да създавате нови.

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

Панелът на съветника реализира прост интерфейс на съветника . Компонентът се състои от панел с карти, който може да бъде усъвършенстван с помощта на бутона NEXT. Бутонът НАЗАД ви позволява да превключите към предишния панел. Предвидени са също бутони FINISH и CANCEL.

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

Има пет основни задачи при създаването на собствен тип събитие:

  • Създайте слушател на събития

  • Създайте адаптер за слушател

  • Създайте клас на събитие

  • Променете компонента

  • Управление на множество слушатели

Ще разгледаме всяка от тези задачи на свой ред и след това ще ги съберем заедно.

Създайте слушател на събития

Един от начините (а има много) за информиране на обекти, че е настъпило определено действие, е създаването на нов тип събитие, което може да бъде доставено на регистрирани слушатели. В случай на панела на съветника, слушателят трябва да поддържа четири различни случая, по един за всеки бутон.

Започвам със създаването на интерфейс за слушане. За всеки бутон определям метод на слушател по следния начин:

импортиране на java.util.EventListener; публичен интерфейс WizardListener разширява EventListener {публична резюме void nextSelected (WizardEvent e); публично резюме void backSelected (WizardEvent e); публично резюме void cancelSelected (WizardEvent e); публична абстрактна void finishSelected (WizardEvent e); }

Всеки метод взема един аргумент:, WizardEventкойто е дефиниран по-нататък. Имайте предвид, че интерфейсът се разширява EventListener, използва се за идентифициране на този интерфейс като AWT слушател.

Създайте адаптер за слушател

Създаването на адаптер за слушател е незадължителна стъпка. В AWT адаптерът за слушане е клас, който осигурява изпълнение по подразбиране за всички методи от определен тип слушател. Всички класове на адаптери в java.awt.eventпакета предоставят празни методи, които не правят нищо. Ето клас на адаптер за WizardListener:

публичен клас WizardAdapter изпълнява WizardListener {публична пустота nextSelected (WizardEvent e) {} публична пустота backSelected (WizardEvent e) {} публична пустота cancelSelected (WizardEvent e) {} публична пустота finishSelected (WizardEvent e) {}} 

Когато пишете клас, който трябва да бъде слушател на съветника, е възможно да се разшири WizardAdapterи осигури изпълнение за (или да се замени) само тези методи на слушател, които представляват интерес. Това е строго удобен клас.

Създайте клас на събитие

Следващата стъпка е да се създаде реалното Eventкласа тук: WizardEvent.

импортиране на java.awt.AWTEvent; публичен клас WizardEvent разширява AWTEvent {public static final int WIZARD_FIRST = AWTEvent.RESERVED_ID_MAX + 1; публичен статичен финал int NEXT_SELECTED = WIZARD_FIRST; публичен статичен финал int BACK_SELECTED = WIZARD_FIRST + 1; публичен статичен финал int CANCEL_SELECTED = WIZARD_FIRST + 2; публичен статичен финал int FINISH_SELECTED = WIZARD_FIRST + 3; публичен статичен финал int WIZARD_LAST = WIZARD_FIRST + 3; публичен WizardEvent (източник на съветника, int id) {super (source, id); }}

Две константи WIZARD_FIRSTи WIZARD_LASTмаркират обхвата на маските, използвани от този клас на събитието. Имайте предвид, че идентификаторите на събитията използват RESERVED_ID_MAXконстантата на класа, за AWTEventда определят обхвата на идентификаторите, които няма да противоречат на стойностите на идентификатора на събитието, дефинирани от AWT. С добавянето на повече компоненти на AWT, RESERVED_ID_MAXв бъдеще може да се увеличи.

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

ИД на събитие и източник на събитие са два аргумента за конструктора на събития на съветника. Източникът на събитието трябва да бъде от тип Wizard- това е типът компонент, за който е дефинирано събитието. Мотивите са, че само панел на съветника може да бъде източник на събития на съветника. Обърнете внимание, че WizardEventкласът се разширява AWTEvent.

Променете компонента

Следващата стъпка е да оборудваме нашия компонент с методи, които му позволяват да регистрира и премахва слушатели за новото събитие.

За да достави събитие на слушател, обикновено се извиква подходящият метод за слушане на събития (в зависимост от маската на събитието). Мога да регистрирам слушател на действия, за да получавам събития за действие от бутона NEXT и да ги предавам на регистрирани WizardListenerобекти. В actionPerformedметода на слушателя за действие за следващия (или други действия) бутон може да се осъществи, както следва:

public void actionPerformed (ActionEvent e) {// не прави нищо, ако не са регистрирани слушатели, ако (wizardListener == null) return; WizardEvent w; Източник на съветника = това; if (e.getSource () == nextButton) {w = new WizardEvent (source, WizardEvent.NEXT_SELECTED); wizardListener.nextSelected (w); } // обработваме останалите бутони на съветника по подобен начин}

Забележка: В горния пример самият Wizardпанел е слушателят на бутона NEXT .

При натискане на бутона NEXT се създава нов WizardEventсъс съответния източник и маска, който съответства на натискания бутон NEXT.

В примера линията

 wizardListener.nextSelected (w); 

се отнася до wizardListenerобекта, който е променлива на частен член за Wizardи е от тип WizardListener. Определихме този тип като първата стъпка в създаването на ново събитие на компонент.

На пръв поглед горният код изглежда ограничава броя на слушателите до един. Частната променлива wizardListenerне е масив и се прави само едно nextSelectedповикване. За да обясним защо горният код всъщност не представлява това ограничение, нека разгледаме как се добавят слушатели.

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

публична синхронизирана невалидна addWizardListener (WizardListener l) {wizardListener = WizardEventMulticaster.add (wizardListener, l); } публична синхронизирана void removeWizardListener (WizardListener l) {wizardListener = WizardEventMulticaster.remove (wizardListener, l); }

И двата метода правят извикване на статични членове на метода на класа WizardEventMulticaster.

Управление на множество слушатели

While it is possible to use a Vector to manage multiple listeners, JDK 1.1 defines a special class for maintaining a listener list: AWTEventMulticaster. A single multicaster instance maintains references to two listener objects. Because the multicaster is also a listener itself (it implements all listener interfaces), each of the two listeners it keeps track of can also be multicasters, thus creating a chain of event listeners or multicasters:

If a listener is also a multicaster, then it represents a link in the chain. Otherwise, it is merely a listener and is thus the last element in the chain.

Unfortunately, it is not possible simply to reuse the AWTEventMulticaster to handle event multicasting for new event types. The best that can be done is to extend the AWT multicaster, although this operation is rather questionable. AWTEventMulticaster contains 56 methods. Of these, 51 methods provide support for the 12 event types and their corresponding listeners that are part of AWT. If you subclass AWTEventMulticaster, you will never use them anyway. Out of the remaining five methods, addInternal(EventListener, EventListener), and remove(EventListener) need to be recoded. (I say recoded because in AWTEventMulticaster, addInternal is a static method and therefore cannot be overloaded. For reasons unknown to me at this time, remove makes a call to addInternal and it needs to be overloaded.)

Two methods, save and saveInternal, provide support for object streaming and can be reused in the new multicaster class. The last method that supports listener remove routines, removeInternal, can also be reused, provided that new versions of remove and addInternal have been implemented.

For the sake of simplicity, I am going to subclass AWTEventMulticaster, but with very little effort, it is possible to code remove, save, and saveInternal and have a fully functional, standalone event multicaster.

Here is the event multicaster as implemented to handle WizardEvent:

import java.awt.AWTEventMulticaster; import java.util.EventListener; public class WizardEventMulticaster extends AWTEventMulticaster implements WizardListener { protected WizardEventMulticaster(EventListener a, EventListener b) { super(a, b); } public static WizardListener add(WizardListener a, WizardListener b) { return (WizardListener) addInternal(a, b); } public static WizardListener remove(WizardListener l, WizardListener oldl) { return (WizardListener) removeInternal(l,oldl); } public void nextSelected(WizardEvent e) { //casting exception will never occur in this case //casting _is_ needed because this multicaster may //handle more than just one listener if (a != null) ((WizardListener) a).nextSelected(e); if (b != null) ((WizardListener) b).nextSelected(e); } public void backSelected(WizardEvent e) { if (a != null) ((WizardListener) a).backSelected(e); if (b != null) ((WizardListener) b).backSelected(e); } public void cancelSelected(WizardEvent e) { if (a != null) ((WizardListener) a).cancelSelected(e); if (b != null) ((WizardListener) b).cancelSelected(e); } public void finishSelected(WizardEvent e) { if (a != null) ((WizardListener) a).finishSelected(e); if (b != null) ((WizardListener) b).finishSelected(e); } protected static EventListener addInternal(EventListener a, EventListener b) { if (a == null) return b; if (b == null) return a; return new WizardEventMulticaster(a, b); } protected EventListener remove(EventListener oldl) { if (oldl == a) return b; if (oldl == b) return a; EventListener a2 = removeInternal(a, oldl); EventListener b2 = removeInternal(b, oldl); if (a2 == a && b2 == b) return this; return addInternal(a2, b2); } } 

Methods in the multicaster class: A review

Let's review the methods that are part of the multicaster class above. The constructor is protected, and in order to obtain a new WizardEventMulticaster, a static add(WizardListener, WizardListener) method must be called. It takes two listeners as arguments that represent two pieces of a listener chain to be linked:

  • To start a new chain, use null as the first argument.

  • To add a new listener, use the existing listener as the first argument and a new listener as the second argument.

This, in fact, is what has been done in the code for class Wizard that we have already examined.

Another static routine is remove(WizardListener, WizardListener). The first argument is a listener (or listener multicaster), and the second is a listener to be removed.

Four public, non-static methods were added to support event propagation through the event chain. For each WizardEvent case (that is, next, back, cancel, and finish selected) there is one method. These methods must be implemented since the WizardEventMulticaster implements WizardListener, which in turn requires the four methods to be present.

How it all works together

Let's now examine how the multicaster actually is used by the Wizard. Let's suppose a wizard object is constructed and three listeners are added, creating a listener chain.

Първоначално частната променлива wizardListenerна class Wizardе нула. Така че, когато е направено повикване WizardEventMulticaster.add(WizardListener, WizardListener), първият аргумент, wizardListenerе нула, а вторият не (няма смисъл да се добавя нулев слушател). В addметода, от своя страна, призовава addInternal. Тъй като един от аргументите е нула, връщането на addInternalе ненулевият слушател. Връщането се разпространява към addметода, който връща ненулевия слушател към addWizardListenerметода. Там wizardListenerпроменливата е настроена на новия слушател, който се добавя.