Passaggio a JDK 1.1: utilizzo del modello di eventi di delega per creare componenti AWT personalizzati

Sebbene molte applicazioni attualmente richiedano JDK 1.0.2, una transizione alla 1.1 è inevitabile per qualsiasi serio sforzo di sviluppo. Questa mossa introduce modifiche significative all'Abstract Windowing Toolkit (AWT), alcune delle quali tratteremo in questo articolo. Nelle prossime pagine virtuali, illustreremo le procedure per la creazione di componenti AWT riutilizzabili che operano all'interno del nuovo modello di delega degli eventi. I concetti trattati saranno utili anche quando sarà necessario aggiornare i componenti personalizzati esistenti per lavorare con il nuovo modello di eventi. Coloro che hanno un occhio al futuro dovrebbero fare uno sforzo per essere consapevoli dei cambiamenti aggiuntivi che JDK 1.2 porterà, ma questa è un'altra storia ...

Per mantenere le cose semplici, esamineremo un esempio abbastanza semplice. Il mio obiettivo è mostrarti come acquisire, elaborare e inviare eventi, senza lasciarti impantanare dai complicati dettagli sulla selezione del colore.

L'interfaccia di ColorPicker

Come puoi vedere nella figura sotto, il componente ColorPicker è composto da tre regioni: L'area di sinistra mostra un campione di colori, con il livello di rosso che varia da sinistra a destra attraverso il campione e il livello di verde che varia dall'alto verso il basso. L'utente seleziona i livelli di rosso e verde facendo clic su questo campione. La regione centrale mostra una barra verticale di colore blu. L'utente specifica l'importo blu facendo clic sulla posizione corretta nella barra. La regione di destra mostra il colore corrente, che è una combinazione dei livelli rosso, verde e blu selezionati dall'utente. Facendo clic in questa regione si seleziona il colore corrente, provocando il verificarsi di un evento AWT appropriato.

L'interfaccia di ColorPicker

Nuovi elementi essenziali di AWT

Il seguente elenco include gli elementi essenziali dell'AWT interessati da JDK 1.1, poiché si applicano ai componenti personalizzati:

  • Modello di eventi: gli eventi non filtrano nella gerarchia del contenitore come prima; invece, gli ascoltatori interessati si registrano con i componenti AWT e gli eventi sono multicast tramite listenerinterfacce.

  • Tipi di eventi - Una singola Eventclasse monolitica non viene più utilizzata per la consegna di tutti gli eventi; invece, classi di eventi differenti derivano da java.util.EventObject(o, rispetto all'AWT, java.awt.AWTEvent) e forniscono un'interfaccia appropriata all'occorrenza rilevante.

  • Metodi degli eventi: gli eventi non vengono più consegnati ai componenti tramite il handleEvent()metodo tradizionale ; invece, processEvent()viene utilizzato un metodo insieme a vari helper associati.

  • Maschere di eventi - Le maschere di eventi degli eventi che un particolare componente dovrebbe generare vengono ora mantenute. Questo nuovo approccio è più efficiente perché un particolare evento non verrà generato ed elaborato se nessun target lo sta ascoltando. Di conseguenza, se si sottoclasse un componente, per impostazione predefinita non genererà i soliti eventi AWT. Se desideri ricevere determinati eventi, devi contrassegnare esplicitamente quei tipi di eventi che desideri vengano generati.

  • Nomi dei metodi: molti metodi sono stati rinominati per ottenere un'interfaccia più coerente e simile a Beans. Questa modifica era necessaria per una transizione al nuovo modello di eventi.

L'implementazione di ColorPicker

Ecco le quattro classi che implementano il nostro componente:

  • ColorEvent è una classe evento personalizzata che trasporta il Colorrisultato di ColorPicker .

  • ColorListener è l'interfaccia attraverso la quale le parti interessate ascoltano ColorEvents.

  • ColorPicker è l'effettivo componente grafico di selezione dei colori.

  • ColorEventMulticaster viene utilizzato dalla ColorPickerclasse per mantenere un elenco di messaggi registrati ColorListener.

Diamo uno sguardo dettagliato a ciascuna di queste classi, quindi mostrerò come funziona il componente ColorPicker.

Classe ColorEvent

Gli eventi di tipo ColorEventvengono pubblicati dal ColorPickercomponente quando l'utente seleziona un colore facendo clic nella regione della GUI a destra. L'evento contiene un Colorcampo, che è il colore selezionato dall'utente ed è estraibile tramite il getColor()metodo.

Qualsiasi evento che verrà utilizzato da un componente AWT deve essere una sottoclasse della AWTEventclasse. In questo caso, dichiariamo una ColorEventsottoclasse per il trasporto di eventi di colore:

import java.awt.AWTEvent; import java.awt.Color; la classe pubblica ColorEvent estende AWTEvent {

A tutti gli eventi AWT devono essere assegnati identificatori interi; un identificatore utente valido è qualsiasi valore sopra AWTEvent.RESERVED_ID_MAX.

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

Poiché la classe di un evento viene ora utilizzata come caratteristica distintiva e non solo il suo ID, i componenti utente non devono più scegliere valori univoci a livello globale. Invece, questo identificatore può essere utilizzato per distinguere tra diversi tipi di una particolare classe di eventi. Ad esempio, potremmo anche definire un COLOR_CHANGEDidentificatore che identifica quando l'utente ha modificato la selezione ma non ha ancora accettato il risultato. Potremmo quindi distinguere tra i due eventi con una ColorEventclasse e due identificatori invece di due classi di eventi separate.

Il colore associato a questo evento è memorizzato nella colorvariabile:

colore colore protetto; 

Il costruttore di questo evento accetta l'origine dell'evento source, che per questo esempio sarà a ColorPicker, e Colorcolorquello scelto:

public ColorEvent (origine oggetto, colore colore) {super (origine, COLOR_PICKED); this.color = color; }

Il getColormetodo consente al destinatario dell'evento di estrarre il colore associato:

public Color getColor () {return color; }

Il paramStringmetodo è semplicemente un metodo comodo che viene utilizzato quando AWTEventviene stampato sulla console:

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

Interfaccia ColorListener

Le classi interessate a ricevere messaggi di posta ColorEventelettronica devono implementare l' ColorListenerinterfaccia che dichiara un colorPicked()metodo attraverso il quale verranno consegnati tali eventi.

Tutte le interfacce del listener di eventi devono estendere l' EventListenerinterfaccia fittizia:

import java.util.EventListener; l'interfaccia pubblica ColorListener estende EventListener {

Il colorPicked()metodo verrà richiesto a tutte le parti interessate quando un colore è stato scelto. Il parametro econtiene i relativi ColorEvent:

 public void colorPicked (ColorEvent e); 

Class ColorPicker

La ColorPickerclasse è un semplice componente per la selezione del colore che utilizza il nuovo modello di delega degli eventi di JDK 1.1. Lo aggiungi a un contenitore come faresti con un normale componente AWT e fornisce un evento nuovo paradigma quando l'utente seleziona un colore.

Ho intenzionalmente reso l'interfaccia utente per la selezione del colore molto primitiva perché ci occupiamo degli interni dell'eventing 1.1, non dell'usabilità. Le righe di codice che hanno particolare rilevanza per JDK 1.1 sono evidenziate in rosso. Ti consiglio di sperimentare con la creazione di un'interfaccia più amichevole.

Estendiamo Canvasperché ColorPicker è un componente interamente disegnato su misura. Se volessimo costruire il nostro selettore di colori da altri componenti, estenderemmo Panelinvece:

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

Il selettore di colori quantizza lo spazio RGB 0-255 ^ 3 in sei livelli di ciascun componente: 0, 51, 102, 153, 204, 255. Ciò corrisponde al tipico cubo di colore del browser a 256 colori. Per una maggiore granularità della selezione del colore, utilizzare più livelli:

int. finale statico protetto LEVELS = 6; 

Gli attuali livelli di rosso, verde e blu sono memorizzati nelle variabili r, ge brispettivamente:

protetto 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))); 

Il postColorEventmetodo crea un nuovo ColorEventcon thiscome origine e il colore attualmente selezionato come carico utile e lo inserisce nella coda degli eventi di sistema. In JDK 1.0.2 abbiamo chiamato il postEvent()metodo per far filtrare un evento nella gerarchia del contenitore. In JDK 1.1 possiamo chiamare dispatchEvent()per inviare immediatamente un evento oppure possiamo inviare un evento alla coda degli eventi di sistema. Questa coda di eventi è monitorata da un thread AWT ( EventDispatchThread), che estrae semplicemente se AWTEventchiama dispatchEvent()il componente che è l'origine dell'evento.