Suggerimento Java 35: crea nuovi tipi di eventi in Java

Sebbene JDK 1.1 abbia certamente semplificato la gestione degli eventi con l'introduzione del modello di eventi di delega, non rende facile per gli sviluppatori creare i propri tipi di eventi. La procedura di base qui descritta è in realtà piuttosto semplice. Per motivi di semplicità, non tratterò i concetti di attivazione di eventi e maschere di eventi. Inoltre, dovresti sapere che gli eventi creati utilizzando questa procedura non verranno pubblicati nella coda degli eventi e funzioneranno solo con i listener registrati.

Attualmente, il core Java è costituito da 12 tipi di eventi definiti in java.awt.events:

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

Poiché la creazione di nuovi tipi di eventi è un'attività non banale, è necessario esaminare gli eventi che fanno parte del nucleo Java. Se possibile, prova a utilizzare questi tipi piuttosto che crearne di nuovi.

Ci saranno momenti, tuttavia, in cui sarà necessario sviluppare un nuovo tipo di evento per un nuovo componente. Ai fini di questa discussione, userò l'esempio di un semplice componente, un pannello della procedura guidata, come mezzo per dimostrare come creare un nuovo tipo di evento.

Un pannello della procedura guidata implementa una semplice interfaccia della procedura guidata . Il componente è costituito da un pannello della scheda che può essere avanzato utilizzando il pulsante NEXT. Il pulsante INDIETRO consente di passare al pannello precedente. Sono inoltre forniti i pulsanti FINISH e CANCEL.

Per rendere il componente flessibile, ho voluto fornire il pieno controllo sulle azioni intraprese da tutti i pulsanti allo sviluppatore che lo utilizza. Ad esempio, quando si preme il pulsante NEXT, dovrebbe essere possibile per lo sviluppatore verificare prima se i dati richiesti sono stati inseriti nel componente attualmente visibile prima di passare al componente successivo.

Ci sono cinque attività principali nella creazione del tuo tipo di evento:

  • Crea un listener di eventi

  • Crea un adattatore listener

  • Crea una classe evento

  • Modifica il componente

  • Gestione di più ascoltatori

Esamineremo ciascuna di queste attività a turno e poi le metteremo insieme.

Crea un listener di eventi

Un modo (e ce ne sono molti) per informare gli oggetti che si è verificata una determinata azione è creare un nuovo tipo di evento che potrebbe essere consegnato ai listener registrati. Nel caso del pannello della procedura guidata, un listener dovrebbe supportare quattro diversi casi di evento, uno per ogni pulsante.

Inizio creando un'interfaccia di ascolto. Per ogni pulsante, definisco un metodo di ascolto nel modo seguente:

import java.util.EventListener; l'interfaccia pubblica WizardListener estende EventListener {public abstract void nextSelected (WizardEvent e); public abstract void backSelected (WizardEvent e); public abstract void cancelSelected (WizardEvent e); public abstract void finishSelected (WizardEvent e); }

Ogni metodo accetta un argomento:, WizardEventche viene definito successivamente. Si noti che l'interfaccia si estende EventListener, utilizzata per identificare questa interfaccia come un listener AWT.

Crea un adattatore listener

La creazione di un adattatore listener è un passaggio facoltativo. In AWT, un adattatore listener è una classe che fornisce un'implementazione predefinita per tutti i metodi di un determinato tipo di listener. Tutte le classi dell'adattatore nel java.awt.eventpacchetto forniscono metodi vuoti che non fanno nulla. Ecco una classe adattatore per WizardListener:

public class WizardAdapter implementa WizardListener {public void nextSelected (WizardEvent e) {} public void backSelected (WizardEvent e) {} public void cancelSelected (WizardEvent e) {} public void finishSelected (WizardEvent e) {}} 

Quando si scrive una classe che deve essere un ascoltatore di procedure guidate, è possibile estendere WizardAdaptere fornire l'implementazione per (o sovrascrivere) solo quei metodi di ascolto che sono di interesse. Questa è strettamente una classe di convenienza.

Crea una classe evento

Il passo successivo è quello di creare l'attuale Eventclasse di qui: WizardEvent.

import java.awt.AWTEvent; la classe pubblica WizardEvent estende AWTEvent {public static final int WIZARD_FIRST = AWTEvent.RESERVED_ID_MAX + 1; public static final int NEXT_SELECTED = WIZARD_FIRST; int finale statico pubblico BACK_SELECTED = WIZARD_FIRST + 1; public static final int CANCEL_SELECTED = WIZARD_FIRST + 2; public static final int FINISH_SELECTED = WIZARD_FIRST + 3; pubblico statico finale int WIZARD_LAST = WIZARD_FIRST + 3; public WizardEvent (origine guidata, int id) {super (origine, id); }}

Due costanti, WIZARD_FIRSTe WIZARD_LAST, contrassegnano l'intervallo inclusivo di maschere usate da questa classe Event. Si noti che gli ID evento utilizzano la RESERVED_ID_MAXcostante di classe AWTEventper determinare l'intervallo di ID che non saranno in conflitto con i valori dell'ID evento definiti dall'AWT. Man mano che vengono aggiunti più componenti AWT, RESERVED_ID_MAXpotrebbero aumentare in futuro.

Le restanti quattro costanti rappresentano quattro ID evento, ciascuno corrispondente a un diverso tipo di azione, come definito dalla funzionalità della procedura guidata.

L'ID evento e l'origine dell'evento sono due argomenti per il costruttore di eventi della procedura guidata. L'origine dell'evento deve essere del tipo Wizard, ovvero il tipo di componente per cui è definito l'evento. Il ragionamento è che solo un pannello della procedura guidata può essere una fonte di eventi della procedura guidata. Nota che la WizardEventclasse si estende AWTEvent.

Modifica il componente

Il passo successivo è dotare il nostro componente di metodi che gli permettano di registrare e rimuovere ascoltatori per il nuovo evento.

Per inviare un evento a un ascoltatore, normalmente si chiama il metodo del listener di eventi appropriato (a seconda della maschera dell'evento). Posso registrare un ascoltatore di azioni per ricevere eventi di azione dal pulsante NEXT e trasmetterli agli WizardListeneroggetti registrati . Il actionPerformedmetodo del listener di azioni per il pulsante NEXT (o altre azioni) potrebbe essere implementato come segue:

public void actionPerformed (ActionEvent e) {// non fare nulla se non sono registrati listener if (wizardListener == null) return; WizardEvent w; Fonte guidata = questo; if (e.getSource () == nextButton) {w = new WizardEvent (source, WizardEvent.NEXT_SELECTED); wizardListener.nextSelected (w); } // gestisce il resto dei pulsanti della procedura guidata in modo simile}

Nota: nell'esempio precedente, il Wizardpannello stesso è l'ascoltatore per il pulsante AVANTI .

Quando si preme il pulsante NEXT, ne WizardEventviene creata una nuova con la sorgente e la maschera appropriate che corrisponde al pulsante NEXT premuto.

Nell'esempio, la linea

 wizardListener.nextSelected (w); 

fa riferimento wizardListenerall'oggetto che è una variabile membro privata Wizarded è di tipo WizardListener. Abbiamo definito questo tipo come il primo passo nella creazione di un nuovo evento componente.

A prima vista, il codice sopra sembra limitare il numero di ascoltatori a uno. La variabile privata wizardListenernon è un array e nextSelectedviene effettuata solo una chiamata. Per spiegare perché il codice sopra in realtà non pone tale restrizione, esaminiamo come vengono aggiunti i listener.

Ogni nuovo componente che genera eventi (predefiniti o nuovi) deve fornire due metodi: uno per supportare l'aggiunta di listener e uno per supportare la rimozione di listener. Nel caso della Wizardclasse, questi metodi sono:

pubblico sincronizzato void addWizardListener (WizardListener l) {wizardListener = WizardEventMulticaster.add (wizardListener, l); } public synchronized void removeWizardListener (WizardListener l) {wizardListener = WizardEventMulticaster.remove (wizardListener, l); }

Entrambi i metodi effettuano una chiamata ai membri del metodo statico della classe WizardEventMulticaster.

Gestione di più ascoltatori

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.

Inizialmente, la variabile privata wizardListenerdi classe Wizardè nulla. Quindi, quando viene effettuata una chiamata a WizardEventMulticaster.add(WizardListener, WizardListener), il primo argomento wizardListener,, è nullo e il secondo no (non ha senso aggiungere un ascoltatore nullo). Il addmetodo, a sua volta, chiama addInternal. Poiché uno degli argomenti è nullo, il ritorno di addInternalè l'ascoltatore non nullo. Il ritorno si propaga al addmetodo che restituisce il listener non nullo al addWizardListenermetodo. Lì la wizardListenervariabile è impostata sul nuovo listener che viene aggiunto.