Java-Tipp 35: Erstellen Sie neue Ereignistypen in Java

Während JDK 1.1 mit der Einführung des Delegierungsereignismodells die Ereignisbehandlung sicherlich optimiert hat, ist es für Entwickler nicht einfach, eigene Ereignistypen zu erstellen. Das hier beschriebene grundlegende Verfahren ist eigentlich ziemlich einfach. Der Einfachheit halber werde ich nicht auf Konzepte der Ereignisaktivierung und Ereignismasken eingehen. Außerdem sollten Sie wissen, dass Ereignisse, die mit diesem Verfahren erstellt wurden, nicht in die Ereigniswarteschlange gestellt werden und nur mit registrierten Listenern funktionieren.

Derzeit besteht der Java-Kern aus 12 Ereignistypen, die definiert sind in java.awt.events:

  • ActionEvent
  • AdjustmentEvent
  • ComponentEvent
  • ContainerEvent
  • FocusEvent
  • InputEvent
  • ItemEvent
  • Schlüsselereignis
  • MouseEvent
  • PaintEvent
  • TextEvent
  • WindowEvent

Da das Erstellen neuer Ereignistypen keine triviale Aufgabe ist, sollten Sie Ereignisse untersuchen, die Teil von Java sind. Versuchen Sie nach Möglichkeit, diese Typen zu verwenden, anstatt neue zu erstellen.

Es wird jedoch Zeiten geben, in denen ein neuer Ereignistyp für eine neue Komponente entwickelt werden muss. Für die Zwecke dieser Diskussion werde ich das Beispiel einer einfachen Komponente, eines Assistentenfensters, verwenden, um zu demonstrieren, wie ein neuer Ereignistyp erstellt wird.

Ein Assistentenfenster implementiert eine einfache Assistentenoberfläche . Die Komponente besteht aus einem Kartenfeld, das mit der Schaltfläche WEITER erweitert werden kann. Mit der Schaltfläche ZURÜCK können Sie zum vorherigen Bereich wechseln. Die Tasten FINISH und CANCEL sind ebenfalls vorhanden.

Um die Komponente flexibel zu gestalten, wollte ich dem Entwickler, der sie verwendet, die volle Kontrolle über die Aktionen aller Schaltflächen geben. Wenn beispielsweise die Schaltfläche WEITER gedrückt wird, sollte es dem Entwickler möglich sein, zunächst zu überprüfen, ob die erforderlichen Daten für die aktuell sichtbare Komponente eingegeben wurden, bevor er zur nächsten Komponente übergeht.

Es gibt fünf Hauptaufgaben beim Erstellen Ihres eigenen Ereignistyps:

  • Erstellen Sie einen Ereignis-Listener

  • Erstellen Sie einen Listener-Adapter

  • Erstellen Sie eine Ereignisklasse

  • Ändern Sie die Komponente

  • Mehrere Listener verwalten

Wir werden jede dieser Aufgaben der Reihe nach untersuchen und sie dann alle zusammenfügen.

Erstellen Sie einen Ereignis-Listener

Eine Möglichkeit (und es gibt viele), Objekte darüber zu informieren, dass eine bestimmte Aktion ausgeführt wurde, besteht darin, einen neuen Ereignistyp zu erstellen, der an registrierte Listener gesendet werden kann. Im Fall des Assistentenbereichs sollte ein Listener vier verschiedene Ereignisfälle unterstützen, einen für jede Schaltfläche.

Ich beginne mit der Erstellung einer Listener-Oberfläche. Für jede Schaltfläche definiere ich eine Listener-Methode auf folgende Weise:

import java.util.EventListener; öffentliche Schnittstelle WizardListener erweitert 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); }}

Jede Methode benötigt ein Argument : WizardEvent, das als nächstes definiert wird. Beachten Sie, dass die Schnittstelle erweitert EventListenerwird, um diese Schnittstelle als AWT-Listener zu identifizieren.

Erstellen Sie einen Listener-Adapter

Das Erstellen eines Listener-Adapters ist ein optionaler Schritt. In AWT ist ein Listener-Adapter eine Klasse, die eine Standardimplementierung für alle Methoden eines bestimmten Listener-Typs bereitstellt. Alle Adapterklassen im java.awt.eventPaket bieten leere Methoden, die nichts bewirken. Hier ist eine Adapterklasse für WizardListener:

öffentliche Klasse WizardAdapter implementiert WizardListener {public void nextSelected (WizardEvent e) {} public void backSelected (WizardEvent e) {} public void cancelSelected (WizardEvent e) {} public void finishSelected (WizardEvent e) {}} 

Wenn Sie eine Klasse schreiben, die ein Assistenten-Listener sein soll, können Sie WizardAdapterdie Listener-Methoden erweitern und nur für die Listener-Methoden implementieren (oder überschreiben), die von Interesse sind. Dies ist ausschließlich eine Convenience-Klasse.

Erstellen Sie eine Ereignisklasse

Der nächste Schritt besteht darin, die eigentliche EventKlasse hier zu erstellen : WizardEvent.

import java.awt.AWTEvent; öffentliche Klasse WizardEvent erweitert AWTEvent {public static final int WIZARD_FIRST = AWTEvent.RESERVED_ID_MAX + 1; public static final int NEXT_SELECTED = WIZARD_FIRST; public static final int BACK_SELECTED = WIZARD_FIRST + 1; public static final int CANCEL_SELECTED = WIZARD_FIRST + 2; public static final int FINISH_SELECTED = WIZARD_FIRST + 3; public static final int WIZARD_LAST = WIZARD_FIRST + 3; public WizardEvent (Assistentenquelle, int id) {super (Quelle, id); }}

Zwei Konstanten WIZARD_FIRSTund WIZARD_LASTmarkieren den inklusiven Bereich von Masken, die von dieser Ereignisklasse verwendet werden. Beachten Sie, dass die Ereignis-IDs die RESERVED_ID_MAXKonstante der Klasse verwenden AWTEvent, um den ID-Bereich zu bestimmen, der nicht mit den vom AWT definierten Ereignis-ID-Werten in Konflikt steht. Wenn weitere AWT-Komponenten hinzugefügt werden, RESERVED_ID_MAXkann sich diese in Zukunft erhöhen.

Die verbleibenden vier Konstanten repräsentieren vier Ereignis-IDs, die jeweils einem anderen Aktionstyp entsprechen, wie durch die Funktionalität des Assistenten definiert.

Ereignis-ID und Ereignisquelle sind zwei Argumente für den Ereigniskonstruktor des Assistenten. Die Ereignisquelle muss vom Typ sein Wizard- das ist der Komponententyp, für den das Ereignis definiert ist. Der Grund dafür ist, dass nur ein Assistentenfenster eine Quelle für Assistentenereignisse sein kann. Beachten Sie, dass die WizardEventKlasse erweitert wird AWTEvent.

Ändern Sie die Komponente

Der nächste Schritt besteht darin, unsere Komponente mit Methoden auszustatten, mit denen sie Listener für das neue Ereignis registrieren und entfernen kann.

Um ein Ereignis an einen Listener zu liefern, würde man normalerweise die entsprechende Event-Listener-Methode aufrufen (abhängig von der Ereignismaske). Ich kann einen Aktionslistener registrieren, um Aktionsereignisse über die Schaltfläche WEITER zu empfangen und an registrierte WizardListenerObjekte weiterzuleiten. Die actionPerformedMethode des Aktionslisteners für die Schaltfläche NEXT (oder andere Aktionen) kann wie folgt implementiert werden:

public void actionPerformed (ActionEvent e) {// nichts tun, wenn keine Listener registriert sind if (wizardListener == null) return; WizardEvent w; Assistentenquelle = this; if (e.getSource () == nextButton) {w = neues WizardEvent (Quelle, WizardEvent.NEXT_SELECTED); wizardListener.nextSelected (w); } // den Rest der Assistentenschaltflächen auf ähnliche Weise behandeln}

Hinweis: Im obigen Beispiel ist das WizardBedienfeld selbst der Listener für die Schaltfläche WEITER .

Wenn die Taste NEXT gedrückt wird, wird eine neue WizardEventmit der entsprechenden Quelle und Maske erstellt, die der Taste NEXT entspricht, die gedrückt wird.

Im Beispiel die Zeile

 wizardListener.nextSelected (w); 

bezieht sich auf das wizardListenerObjekt, das eine private Mitgliedsvariable für Wizardund vom Typ ist WizardListener. Wir haben diesen Typ als ersten Schritt beim Erstellen eines neuen Komponentenereignisses definiert.

Auf den ersten Blick scheint der obige Code die Anzahl der Hörer auf eins zu beschränken. Die private Variable wizardListenerist kein Array und es wird nur ein nextSelectedAufruf ausgeführt. Um zu erklären, warum der obige Code diese Einschränkung nicht darstellt, untersuchen wir, wie Listener hinzugefügt werden.

Jede neue Komponente, die Ereignisse generiert (vordefiniert oder neu), muss zwei Methoden bereitstellen: eine zur Unterstützung des Hinzufügens von Listenern und eine zur Unterstützung des Entfernens von Listenern. Im Fall der WizardKlasse sind diese Methoden:

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

Beide Methoden rufen statische Methodenmitglieder der Klasse auf WizardEventMulticaster.

Mehrere Listener verwalten

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.

Anfangs ist die private Variable wizardListenerder Klasse Wizardnull. Wenn also ein Aufruf erfolgt, WizardEventMulticaster.add(WizardListener, WizardListener)ist das erste Argument wizardListenernull und das zweite nicht (es ist nicht sinnvoll, einen Null-Listener hinzuzufügen). Die addMethode ruft wiederum auf addInternal. Da eines der Argumente null ist, ist die Rückgabe von addInternalder Nicht-Null-Listener. Die Rückgabe wird an die addMethode weitergegeben, die den Nicht-Null-Listener an die addWizardListenerMethode zurückgibt . Dort wird die wizardListenerVariable auf den neuen Listener gesetzt, der hinzugefügt wird.