Wechseln zu JDK 1.1: Verwenden des Delegierungsereignismodells zum Erstellen benutzerdefinierter AWT-Komponenten

Obwohl für viele Anwendungen derzeit JDK 1.0.2 erforderlich ist, ist ein Übergang zu 1.1 für ernsthafte Entwicklungsanstrengungen unvermeidlich. Dieser Schritt führt zu wesentlichen Änderungen am Abstract Windowing Toolkit (AWT), von denen einige in diesem Artikel behandelt werden. Auf den nächsten virtuellen Seiten erfahren Sie, wie Sie wiederverwendbare AWT-Komponenten erstellen, die im neuen Ereignisdelegierungsmodell ausgeführt werden. Die von uns behandelten Konzepte sind auch hilfreich, wenn Sie vorhandene benutzerdefinierte Komponenten aktualisieren müssen, um innerhalb des neuen Ereignismodells zu arbeiten. Wer die Zukunft im Auge hat, sollte sich bemühen, sich der zusätzlichen Änderungen bewusst zu werden, die JDK 1.2 mit sich bringen wird, aber das ist eine andere Geschichte ...

Um die Dinge einfach zu halten, schauen wir uns ein ziemlich einfaches Beispiel an. Mein Ziel ist es, Ihnen zu zeigen, wie Sie Ereignisse erfassen, verarbeiten und versenden können, ohne sich durch die komplizierten Details der Farbauswahl zu verzetteln.

Die ColorPicker-Oberfläche

Wie Sie in der folgenden Abbildung sehen können, besteht die ColorPicker-Komponente aus drei Bereichen: Der linke Bereich zeigt ein Farbfeld an, wobei der Rotpegel von links nach rechts über das Farbfeld und der Grünpegel von oben nach unten variiert. Der Benutzer wählt rote und grüne Ebenen aus, indem er auf dieses Farbfeld klickt. Der mittlere Bereich zeigt einen vertikalen blauen Balken an. Der Benutzer gibt die Menge blau an, indem er auf die richtige Stelle in der Leiste klickt. Der rechte Bereich zeigt die aktuelle Farbe an, bei der es sich um eine Kombination der vom Benutzer ausgewählten Rot-, Grün- und Blaustufen handelt. Durch Klicken in diesen Bereich wird die aktuelle Farbe ausgewählt, wodurch ein geeignetes AWT-Ereignis auftritt.

Die ColorPicker-Oberfläche

Neue AWT-Grundlagen

Die folgende Liste enthält die wesentlichen Elemente der AWT, die von JDK 1.1 betroffen sind, da sie für benutzerdefinierte Komponenten gelten:

  • Ereignismodell - Ereignisse versickern nicht wie zuvor in der Containerhierarchie. Stattdessen registrieren sich interessierte Listener bei AWT-Komponenten, und Ereignisse werden über listenerSchnittstellen per Multicast übertragen .

  • Ereignistypen - Eine einzelne monolothische EventKlasse wird nicht mehr für die Übermittlung aller Ereignisse verwendet. Stattdessen werden verschiedene Ereignisklassen von java.util.EventObject(oder in Bezug auf die AWT java.awt.AWTEvent) abgeleitet und bieten eine geeignete Schnittstelle zum relevanten Ereignis.

  • Ereignismethoden - Ereignisse werden nach der herkömmlichen handleEvent()Methode nicht mehr an Komponenten übermittelt. Stattdessen wird eine processEvent()Methode zusammen mit verschiedenen zugehörigen Helfern verwendet.

  • Ereignismasken - Ereignismasken der Ereignisse, die eine bestimmte Komponente generieren soll, werden jetzt verwaltet. Dieser neue Ansatz ist effizienter, da ein bestimmtes Ereignis nicht generiert und verarbeitet wird, wenn kein Ziel darauf wartet. Wenn Sie eine Komponente in Unterklassen unterteilen, werden daher standardmäßig nicht die üblichen AWT-Ereignisse generiert. Wenn Sie bestimmte Ereignisse empfangen möchten, müssen Sie die Ereignistypen, die Sie generieren möchten, explizit kennzeichnen.

  • Methodennamen - Viele Methoden wurden umbenannt, um eine konsistentere und Beans-ähnliche Schnittstelle zu erzielen. Diese Änderung war für einen Übergang zum neuen Ereignismodell erforderlich.

Die ColorPicker-Implementierung

Hier sind die vier Klassen, die unsere Komponente implementieren:

  • ColorEvent ist eine benutzerdefinierte Ereignisklasse, die das ColorErgebnis des ColorPicker transportiert .

  • ColorListener ist die Schnittstelle, über die Interessenten auf ColorEvents hören .

  • ColorPicker ist die eigentliche grafische Farbauswahlkomponente.

  • ColorEventMulticaster wird von der ColorPickerKlasse verwendet, um eine Liste der registrierten ColorListeners zu verwalten.

Schauen wir uns jede dieser Klassen genauer an und zeigen Ihnen dann, wie die ColorPicker-Komponente funktioniert.

Klasse ColorEvent

Ereignisse vom Typ ColorEventwerden von der ColorPickerKomponente veröffentlicht, wenn der Benutzer eine Farbe durch Klicken in den rechten GUI-Bereich auswählt. Das Ereignis enthält ein ColorFeld, das die vom Benutzer ausgewählte Farbe ist und über die getColor()Methode extrahiert werden kann.

Jedes Ereignis, das von einer AWT-Komponente verwendet wird, muss eine Unterklasse der AWTEventKlasse sein. In diesem Fall deklarieren wir eine ColorEventUnterklasse zum Transportieren von Farbereignissen:

import java.awt.AWTEvent; import java.awt.Color; öffentliche Klasse ColorEvent erweitert AWTEvent {

Allen AWT-Ereignissen müssen ganzzahlige Kennungen zugewiesen werden. Eine gültige Benutzer-ID ist ein beliebiger Wert darüber AWTEvent.RESERVED_ID_MAX.

public static final int COLOR_PICKED = AWTEvent.RESERVED_ID_MAX + 1; 

Da die Klasse eines Ereignisses jetzt als Unterscheidungsmerkmal und nicht nur als ID verwendet wird, müssen Benutzerkomponenten keine global eindeutigen Werte mehr auswählen. Stattdessen kann dieser Bezeichner verwendet werden, um zwischen verschiedenen Typen einer bestimmten Ereignisklasse zu unterscheiden. Beispielsweise könnten wir auch einen COLOR_CHANGEDBezeichner definieren , der angibt, wann der Benutzer die Auswahl geändert, das Ergebnis jedoch noch nicht akzeptiert hat. Wir könnten dann zwischen den beiden Ereignissen mit einer ColorEventKlasse und zwei Bezeichnern anstelle von zwei getrennten Ereignisklassen unterscheiden.

Die diesem Ereignis zugeordnete Farbe wird in der colorVariablen gespeichert :

geschützte Farbe Farbe; 

Der Konstruktor für dieses Ereignis akzeptiert die Quelle des Ereignisses, sourcedie in diesem Beispiel a ist ColorPicker, und die Colorcolorausgewählte:

public ColorEvent (Objektquelle, Farbfarbe) {super (Quelle, COLOR_PICKED); this.color = color; }}

Mit dieser getColorMethode kann der Empfänger des Ereignisses die zugehörige Farbe extrahieren:

public Color getColor () {Farbe zurückgeben; }}

Die paramStringMethode ist einfach eine bequeme Methode, die verwendet wird, wenn eine AWTEventauf der Konsole gedruckt wird:

public String paramString () {return "COLOR_PICKED, color =" + color; }}

Interface ColorListener

Klassen, die ColorEvents empfangen möchten, müssen die ColorListenerSchnittstelle implementieren, die eine colorPicked()Methode deklariert , über die solche Ereignisse übermittelt werden.

Alle Event-Listener-Schnittstellen müssen die EventListenerDummy-Schnittstelle erweitern:

import java.util.EventListener; öffentliche Schnittstelle ColorListener erweitert EventListener {

The colorPicked() method will be called on all interested parties when a color has been picked. The parameter e contains the relevant ColorEvent:

 public void colorPicked (ColorEvent e); 

Class ColorPicker

The ColorPicker class is a simple color-picking component that uses the new event delegation model of JDK 1.1. You add it to a container as you would any normal AWT component, and it delivers a new-paradigm event when the user selects a color.

I've intentionally made the color selection user interface very primitive because we are concerned with the internals of 1.1 eventing, not usability. Lines of code that bear particular relevance to JDK 1.1 are highlighted in red. I recommend you experiment with creating a more friendly interface.

We extend Canvas because ColorPicker is an entirely custom-drawn component. If we wanted to build our color picker from other components, we would extend Panel instead:

import java.awt.*; import java.awt.event.MouseEvent; public class ColorPicker extends Canvas { 

The color picker quantizes the 0-255^3 RGB space into six levels of each component: 0, 51, 102, 153, 204, 255. This corresponds to the typical 256-color browser color cube. For finer granularity of color selection, use more levels:

protected static final int LEVELS = 6; 

The current levels of red, green, and blue are stored in the variables r, g, and b, respectively:

protected int r, g, b; 

In the constructor we extract the various color levels from the initial color color, and then enable mouse events using the enableEvents() method and a mask of AWTEvent.MOUSE_EVENT_MASK. If we did not enable events in this manner, the mouse events would not be generated by this component. We'll get into this in greater detail later when we discuss the processMouseEvent() method.

public ColorPicker (Color color) { r = color.getRed (); g = color.getGreen (); b = color.getBlue (); enableEvents (AWTEvent.MOUSE_EVENT_MASK); } 

The following table shows the event masks in the AWTEvent class that correspond to the different AWT event listeners. Multiple event types can be enabled by either ORing together the masks or by calling enableEvents() repeatedly. Registering a listener for an event type automatically enables the relevant event type.

Event mask Listener interface
ACTION_EVENT_MASK ActionListener
ADJUSTMENT_EVENT_MASK AdjustmentListener
COMPONENT_EVENT_MASK ComponentListener
CONTAINER_EVENT_MASK ContainerListener
FOCUS_EVENT_MASK FocusListener
ITEM_EVENT_MASK ItemListener
KEY_EVENT_MASK KeyListener
MOUSE_EVENT_MASK MouseListener
MOUSE_MOTION_EVENT_MASK MouseMotionListener
TEXT_EVENT_MASK TextListener
WINDOW_EVENT_MASK WindowListener
Event masks and their corresponding listeners

An alternative means for this component to receive its own mouse events would be to implement the MouseListener interface and register as a listener.

This constructor calls the other constructor with an initial color value of black:

public ColorPicker () { this (Color.black); } 

The getPreferredSize() method, shown next, chooses an appropriate size for the component. Under JDK 1.0.2 this method was called preferredSize(). For sake of completeness, we should also implement the getMinimumSize() and getMaximumSize() methods; however, for clarity (not to mention brevity) I've omitted these from this example:

public Dimension getPreferredSize () { return new Dimension (150, 60); } 

Moving right along, the paint() method draws a color swatch on the left, a blue bar with a small blue level marker in the middle, and the current color on the right. The details are not particularly interesting; We choose a block size based on the number of levels desired and the component size and then fill in the blanks:

public void paint (Graphics g) { int h = getSize ().width / (LEVELS + 3 + LEVELS); int v = getSize ().height / (LEVELS); for (int red = 0; red < LEVELS; ++ red) { for (int green = 0; green < LEVELS; ++ green) { g.setColor (new Color (red * 255 / (LEVELS - 1), green * 255 / (LEVELS - 1), b)); g.fillRect (red * h, green * v, h, v); } } int x = LEVELS * h + h / 2; int y = v / 2 + v * (b * (LEVELS - 1) / 255); g.setColor (getForeground ()); g.drawLine (x, y, x + 2 * h - 1, y); for (int blue = 0; blue < LEVELS; ++ blue) { g.setColor (new Color (0, 0, blue * 255 / (LEVELS - 1))); g.fillRect ((LEVELS + 1) * h, blue * v, h, v); } g.setColor (new Color (r, this.g, b)); g.fillRect ((LEVELS + 3) * h, 0, h * LEVELS, v * LEVELS); } 

The processMouseEvent() method is called automatically by Component's processEvent() method when a mouse event is generated. We override this method to call our own mousePressed() method for mouse-press events, and then we call the superclass processMouseEvent() to perform further appropriate processing. If there are other registered listeners for our own mouse events, the superclass method will appropriately inform them through their MouseListener interface:

/* * This code allows us to catch mouse events without registering listeners. */ protected void processMouseEvent (MouseEvent e) { if (e.getID () == MouseEvent.MOUSE_PRESSED) { mousePressed (e); } super.processMouseEvent (e); } 

We call mousePressed when the user clicks on the color picker. If the user clicks in the swatch of colors, we assign new red and green color levels, quantized to the chosen number of color levels. If the user clicks in the blue bar, we assign a new blue level. If the user clicks in the right-hand region, we call the postColorEvent() method to post an appropriate event. Let's see how this works:

public void mousePressed (MouseEvent e) { int h = getSize ().width / (LEVELS + 3 + LEVELS); int v = getSize ().height / (LEVELS); if (e.getX () < LEVELS * h) { // in swatch area r = (e.getX () / h) * 255 / (LEVELS - 1); r = (r  255) ? 255 : r; g = (e.getY () / v) * 255 / (LEVELS - 1); g = (g  255) ? 255 : g; repaint (); } else if (e.getX () < (LEVELS + 3) * h) { // in blue bar b = (e.getY () / v) * 255 / (LEVELS - 1); b = (b  255) ? 255 : b; repaint (); } else { // in select square postColorEvent (); } } 

Now take a look at the following snippet:

/* * This code posts a new ColorEvent to the system event queue. */ protected void postColorEvent () { ColorEvent e = new ColorEvent (this, new Color (r, g, b)); Toolkit toolkit = getToolkit (); EventQueue queue = toolkit.getSystemEventQueue (); queue.postEvent (e); } // alternatively: // dispatchEvent (new ColorEvent (this, new Color (r, g, b))); 

Das postColorEventVerfahren erzeugt eine neue ColorEventmit thisals Ursprung und der aktuell ausgewählten Farbe als seine Nutzlast und trägt ihn in die Systemereigniswarteschlange. Unter JDK 1.0.2 haben wir die postEvent()Methode aufgerufen , um zu bewirken, dass ein Ereignis die Containerhierarchie durchdringt. Unter JDK 1.1 können wir entweder anrufen dispatchEvent(), um ein Ereignis sofort auszulösen, oder wir können ein Ereignis in die Systemereigniswarteschlange stellen. Diese Ereigniswarteschlange wird von einem AWT-Thread ( EventDispatchThread) überwacht , der einfach AWTEvents extrahiert und dispatchEvent()die Komponente aufruft, die die Quelle des Ereignisses ist.