Consejo 35 de Java: cree nuevos tipos de eventos en Java

Si bien JDK 1.1 ciertamente ha simplificado el manejo de eventos con la introducción del modelo de eventos de delegación, no facilita a los desarrolladores la creación de sus propios tipos de eventos. El procedimiento básico que se describe aquí es bastante sencillo. En aras de la simplicidad, no discutiré conceptos de habilitación de eventos y máscaras de eventos. Además, debe saber que los eventos creados mediante este procedimiento no se publicarán en la cola de eventos y funcionarán solo con oyentes registrados.

Actualmente, el núcleo de Java consta de 12 tipos de eventos definidos en java.awt.events:

  • ActionEvent
  • AjusteEvento
  • ComponentEvent
  • ContainerEvent
  • FocusEvent
  • InputEvent
  • ItemEvent
  • Evento clave
  • MouseEvent
  • PaintEvent
  • TextEvent
  • WindowEvent

Dado que la creación de nuevos tipos de eventos no es una tarea trivial, debe examinar los eventos que forman parte del núcleo de Java. Si es posible, intente utilizar esos tipos en lugar de crear otros nuevos.

Sin embargo, habrá ocasiones en las que será necesario desarrollar un nuevo tipo de evento para un nuevo componente. Para los propósitos de esta discusión, usaré el ejemplo de un componente simple, un panel de asistente, como un medio para demostrar cómo crear un nuevo tipo de evento.

Un panel de asistente implementa una interfaz de asistente simple . El componente consta de un panel de tarjetas que se puede avanzar con el botón SIGUIENTE. El botón ATRÁS le permite volver al panel anterior. También se proporcionan los botones FINALIZAR y CANCELAR.

Para hacer que el componente sea flexible, quería brindar un control total sobre las acciones realizadas por todos los botones al desarrollador que lo usa. Por ejemplo, cuando se presiona el botón SIGUIENTE, el desarrollador debería poder verificar primero si los datos requeridos se ingresaron en el componente actualmente visible antes de avanzar al siguiente componente.

Hay cinco tareas principales para crear su propio tipo de evento:

  • Crea un detector de eventos

  • Crea un adaptador de escucha

  • Crea una clase de evento

  • Modificar el componente

  • Gestionar varios oyentes

Examinaremos cada una de estas tareas por turno y luego las juntaremos todas.

Crea un detector de eventos

Una forma (y hay muchas) de informar a los objetos que se ha producido una determinada acción es crear un nuevo tipo de evento que se pueda entregar a los oyentes registrados. En el caso del panel del asistente, un oyente debe admitir cuatro casos de eventos diferentes, uno para cada botón.

Empiezo por crear una interfaz de escucha. Para cada botón, defino un método de escucha de la siguiente manera:

import java.util.EventListener; La interfaz pública WizardListener extiende EventListener {vacío abstracto público nextSelected (WizardEvent e); público abstracto void backSelected (WizardEvent e); public abstract void cancelSelected (WizardEvent e); public abstract void finishSelected (WizardEvent e); }

Cada método toma un argumento:, WizardEventque se define a continuación. Tenga en cuenta que la interfaz se extiende EventListener, que se utiliza para identificar esta interfaz como un oyente de AWT.

Crea un adaptador de escucha

La creación de un adaptador de escucha es un paso opcional. En AWT, un adaptador de escucha es una clase que proporciona una implementación predeterminada para todos los métodos de un determinado tipo de escucha. Todas las clases de adaptadores del java.awt.eventpaquete proporcionan métodos vacíos que no hacen nada. Aquí hay una clase de adaptador para 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) {}} 

Al escribir una clase que va a ser un oyente asistente, es posible extender WizardAdaptery proporcionar implementación para (o anular) solo los métodos de oyente que sean de interés. Esta es estrictamente una clase de conveniencia.

Crea una clase de evento

El siguiente paso es crear el actual Eventclase aquí: WizardEvent.

import java.awt.AWTEvent; La clase pública WizardEvent extiende 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 (fuente del asistente, int id) {super (fuente, id); }}

Dos constantes, WIZARD_FIRSTy WIZARD_LAST, marcan el rango inclusivo de máscaras que usa esta clase de evento. Tenga en cuenta que los ID de eventos utilizan la RESERVED_ID_MAXconstante de clase AWTEventpara determinar el rango de ID que no entrarán en conflicto con los valores de ID de eventos definidos por el AWT. A medida que se agregan más componentes de AWT, es RESERVED_ID_MAXposible que aumente en el futuro.

Las cuatro constantes restantes representan cuatro ID de eventos, cada uno correspondiente a un tipo de acción diferente, según lo definido por la funcionalidad del asistente.

El ID de evento y el origen del evento son dos argumentos para el constructor de eventos del asistente. El origen del evento debe ser de tipo Wizard, es decir, el tipo de componente para el que está definido el evento. El razonamiento es que solo un panel de asistente puede ser una fuente de eventos de asistente. Tenga en cuenta que la WizardEventclase se extiende AWTEvent.

Modificar el componente

El siguiente paso es equipar nuestro componente con métodos que le permitan registrar y eliminar oyentes para el nuevo evento.

Para entregar un evento a un oyente, normalmente se llamaría al método de oyente de eventos apropiado (dependiendo de la máscara del evento). Puedo registrar un oyente de acciones para recibir eventos de acción desde el botón SIGUIENTE y transmitirlos a los WizardListenerobjetos registrados . El actionPerformedmétodo del oyente de acciones para el botón SIGUIENTE (u otras acciones) podría implementarse de la siguiente manera:

public void actionPerformed (ActionEvent e) {// no hacer nada si no hay oyentes registrados if (wizardListener == null) return; WizardEvent w; Fuente del asistente = esto; if (e.getSource () == nextButton) {w = new WizardEvent (fuente, WizardEvent.NEXT_SELECTED); wizardListener.nextSelected (w); } // maneja el resto de los botones del asistente de manera similar}

Nota: En el ejemplo anterior, el Wizardpanel en sí es el oyente del botón SIGUIENTE .

Cuando se presiona el botón SIGUIENTE, WizardEventse crea una nueva con la fuente y la máscara apropiadas que corresponde al botón SIGUIENTE que se está presionando.

En el ejemplo, la línea

 wizardListener.nextSelected (w); 

hace referencia al wizardListenerobjeto que es una variable miembro privada Wizardy es de tipo WizardListener. Hemos definido este tipo como el primer paso para crear un nuevo evento componente.

A primera vista, el código anterior parece restringir el número de oyentes a uno. La variable privada wizardListenerno es una matriz y solo se realiza una nextSelectedllamada. Para explicar por qué el código anterior en realidad no plantea esa restricción, examinemos cómo se agregan los oyentes.

Cada componente nuevo que genera eventos (predefinidos o nuevos) debe proporcionar dos métodos: uno para admitir la adición de oyentes y otro para admitir la eliminación de oyentes. En el caso de la Wizardclase, estos métodos son:

addWizardListener vacío sincronizado público (WizardListener l) {wizardListener = WizardEventMulticaster.add (wizardListener, l); } removeWizardListener vacío sincronizado público (WizardListener l) {wizardListener = WizardEventMulticaster.remove (wizardListener, l); }

Ambos métodos realizan una llamada a los miembros del método estático de la clase WizardEventMulticaster.

Gestionar varios oyentes

Si bien es posible utilizar una Vectorde gestionar múltiples oyentes, JDK 1.1 define una clase especial para el mantenimiento de una lista de oyentes: AWTEventMulticaster. Una única instancia de multidifusión mantiene referencias a dos objetos de escucha. Debido a que el multicaster también es un oyente en sí mismo (implementa todas las interfaces de oyentes), cada uno de los dos oyentes de los que realiza un seguimiento también puede ser multicaster, creando así una cadena de oyentes de eventos o multicasters:

Si un oyente también es un multicaster, entonces representa un eslabón en la cadena. De lo contrario, es simplemente un oyente y, por lo tanto, es el último elemento de la cadena.

Desafortunadamente, no es posible simplemente reutilizar AWTEventMulticasterpara manejar la multidifusión de eventos para nuevos tipos de eventos. Lo mejor que se puede hacer es ampliar el multicaster AWT, aunque esta operación es bastante cuestionable. AWTEventMulticastercontiene 56 métodos. De estos, 51 métodos brindan soporte para los 12 tipos de eventos y sus correspondientes oyentes que forman parte de AWT. Si eres una subclase AWTEventMulticaster, nunca los usarás de todos modos. De los cinco métodos restantes addInternal(EventListener, EventListener), y remove(EventListener)deben recodificarse. (Digo recodificado porque in AWTEventMulticaster, addInternales un método estático y, por lo tanto, no se puede sobrecargar. Por razones que desconozco en este momento, removehace una llamada a addInternaly debe sobrecargarse).

Dos métodos, savey saveInternal, brindan soporte para la transmisión de objetos y se pueden reutilizar en la nueva clase multicaster. El último método que admite rutinas de eliminación de oyentes removeInternal, también se puede reutilizar, siempre que se hayan implementado nuevas versiones de removey addInternal.

En aras de la simplicidad, voy a subclase AWTEventMulticaster, pero con muy poco esfuerzo, es posible codificar remove, savey saveInternaly tienen una, multicaster completamente funcional evento independiente.

Aquí está el evento multicaster implementado para manejar WizardEvent:

importar java.awt.AWTEventMulticaster; import java.util.EventListener; WizardEventMulticaster de clase pública extiende AWTEventMulticaster implementa WizardListener {protegido WizardEventMulticaster (EventListener a, EventListener b) {super (a, b); } public static WizardListener add (WizardListener a, WizardListener b) {return (WizardListener) addInternal (a, b); } público estático WizardListener remove (WizardListener l, WizardListener oldl) {return (WizardListener) removeInternal (l, oldl); } public void nextSelected (WizardEvent e) {// la excepción de conversión nunca ocurrirá en este caso // la conversión _ es_ necesaria porque este multicaster puede // manejar más de un oyente si (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); } EventListener estático protegido addInternal (EventListener a, EventListener b) {if (a == null) return b; if (b == null) return a; devolver nuevo WizardEventMulticaster (a, b); } protegido EventListener remove (EventListener oldl) {if (oldl == a) return b; if (oldl == b) return a; EventListener a2 = removeInternal (a, oldl); EventListener b2 = removeInternal (b, oldl); si (a2 == a && b2 == b) devuelve esto; return addInternal (a2, b2); }}= nulo) ((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); } EventListener estático protegido addInternal (EventListener a, EventListener b) {if (a == null) return b; if (b == null) return a; devolver nuevo WizardEventMulticaster (a, b); } protegido EventListener remove (EventListener oldl) {if (oldl == a) return b; if (oldl == b) return a; EventListener a2 = removeInternal (a, oldl); EventListener b2 = removeInternal (b, oldl); si (a2 == a && b2 == b) devuelve esto; return addInternal (a2, b2); }}= nulo) ((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); } EventListener estático protegido addInternal (EventListener a, EventListener b) {if (a == null) return b; if (b == null) return a; devolver nuevo WizardEventMulticaster (a, b); } protegido EventListener remove (EventListener oldl) {if (oldl == a) return b; if (oldl == b) return a; EventListener a2 = removeInternal (a, oldl); EventListener b2 = removeInternal (b, oldl); si (a2 == a && b2 == b) devuelve esto; return addInternal (a2, b2); }}} 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); } EventListener estático protegido addInternal (EventListener a, EventListener b) {if (a == null) return b; if (b == null) return a; devolver nuevo WizardEventMulticaster (a, b); } protegido EventListener remove (EventListener oldl) {if (oldl == a) return b; if (oldl == b) return a; EventListener a2 = removeInternal (a, oldl); EventListener b2 = removeInternal (b, oldl); si (a2 == a && b2 == b) devuelve esto; return addInternal (a2, b2); }}} 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); } EventListener estático protegido addInternal (EventListener a, EventListener b) {if (a == null) return b; if (b == null) return a; devolver nuevo WizardEventMulticaster (a, b); } protegido EventListener remove (EventListener oldl) {if (oldl == a) return b; if (oldl == b) return a; EventListener a2 = removeInternal (a, oldl); EventListener b2 = removeInternal (b, oldl); si (a2 == a && b2 == b) devuelve esto; return addInternal (a2, b2); }}cancelSelected (e); } public void finishSelected (WizardEvent e) {if (a! = null) ((WizardListener) a) .finishSelected (e); if (b! = null) ((WizardListener) b) .finishSelected (e); } EventListener estático protegido addInternal (EventListener a, EventListener b) {if (a == null) return b; if (b == null) return a; devolver nuevo WizardEventMulticaster (a, b); } protegido EventListener remove (EventListener oldl) {if (oldl == a) return b; if (oldl == b) return a; EventListener a2 = removeInternal (a, oldl); EventListener b2 = removeInternal (b, oldl); si (a2 == a && b2 == b) devuelve esto; return addInternal (a2, b2); }}cancelSelected (e); } public void finishSelected (WizardEvent e) {if (a! = null) ((WizardListener) a) .finishSelected (e); if (b! = null) ((WizardListener) b) .finishSelected (e); } EventListener estático protegido addInternal (EventListener a, EventListener b) {if (a == null) return b; if (b == null) return a; devolver nuevo WizardEventMulticaster (a, b); } protegido EventListener remove (EventListener oldl) {if (oldl == a) return b; if (oldl == b) return a; EventListener a2 = removeInternal (a, oldl); EventListener b2 = removeInternal (b, oldl); si (a2 == a && b2 == b) devuelve esto; return addInternal (a2, b2); }}EventListener b) {if (a == null) return b; if (b == null) return a; devolver nuevo WizardEventMulticaster (a, b); } protegido EventListener remove (EventListener oldl) {if (oldl == a) return b; if (oldl == b) return a; EventListener a2 = removeInternal (a, oldl); EventListener b2 = removeInternal (b, oldl); si (a2 == a && b2 == b) devuelve esto; return addInternal (a2, b2); }}EventListener b) {if (a == null) return b; if (b == null) return a; devolver nuevo WizardEventMulticaster (a, b); } protegido EventListener remove (EventListener oldl) {if (oldl == a) return b; if (oldl == b) return a; EventListener a2 = removeInternal (a, oldl); EventListener b2 = removeInternal (b, oldl); si (a2 == a && b2 == b) devuelve esto; return addInternal (a2, b2); }}

Métodos en la clase multicaster: una revisión

Repasemos los métodos que forman parte de la clase multicaster anterior. El constructor está protegido y para obtener uno nuevo WizardEventMulticaster, se add(WizardListener, WizardListener)debe llamar a un método estático . Se necesitan dos oyentes como argumentos que representan dos partes de una cadena de oyentes para vincular:

  • Para comenzar una nueva cadena, use null como primer argumento.

  • Para agregar un nuevo oyente, use el oyente existente como primer argumento y un nuevo oyente como segundo argumento.

Esto, de hecho, es lo que se ha hecho en el código de clase Wizardque ya hemos examinado.

Otra rutina estática es remove(WizardListener, WizardListener). El primer argumento es un oyente (o un oyente multicaster) y el segundo es un oyente que debe eliminarse.

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.

Inicialmente, la variable privada wizardListenerde clase Wizardes nula. Entonces, cuando se realiza una llamada a WizardEventMulticaster.add(WizardListener, WizardListener), el primer argumento wizardListener,, es nulo y el segundo no (no tiene sentido agregar un oyente nulo). El addmétodo, a su vez, llama addInternal. Dado que uno de los argumentos es nulo, el retorno de addInternales el oyente no nulo. El retorno se propaga al addmétodo que devuelve el oyente no nulo al addWizardListenermétodo. Allí, la wizardListenervariable se establece en el nuevo oyente que se agrega.