A continuación se verá un ejemplo más, pero
ya como indicación solamente, para la creación de eventos, para que el lector pueda
investigar en un ejemplo útil sobre el que se proporciona suficiente información para
que concluya el ejemplo y pueda comprobar que la lectura de las secciones anteriores ha
sido del todo fructífera, y el concepto de funcionamiento de los eventos que se generan
por programa está completamente comprendido. Para ello lo que se va a intentar es implementar un interfaz de un Asistente,
semejante al que se está popularizando en los entornos Windows. El Asistente estará
constituido por un panel con un CardLayout como gestor de posicionamiento de los
componentes y cuatro botones etiquetados como Siguiente, Anterior, Cancelar
y Finalizar, que realizan las tareas que indican sus respectivos nombres. Para que
este Asistente sea flexible, se proporciona un control total sobre las acciones de los
botones, por ejemplo, cuando se pulsa el botón Siguiente, debería ser posible
comprobar si los datos que son imprescindibles del panel que se está visualizando
actualmente se han rellenado antes de pasar de verdad al siguiente.
En la creación del nuevo tipo de evento, se
involucran cinco tareas principales, que a continuación se van a ver en detalle.
Crear
un Receptor de Eventos
Una forma, entre otras, de informar a los
objetos de que se ha producido una cierta acción, es crear un nuevo tipo de eventos que
pueda ser enviado a los receptores que se encuentren registrados para recibirlo. En el
caso del Asistente, el receptor deberá proporcionar soporte a cuatro tipos de eventos,
uno para cada botón.
Se crea un interfaz para el receptor,
definiendo un método para cada botón, de la forma siguiente:
import java.util.EventListener;
public interface AsistenteListener exetends EventListener {
public abstract void sigSeleccionado( AsistenteEvent evt );
public abstract void antSeleccionado( AsistenteEvent evt );
public abstract void cancelaSeleccionado( AsistenteEvent evt );
public abstract void finalizaSeleccionado( AsistenteEvent evt );
}
Cada método tiene un argumento de tipo AsistenteEvent,
que se definirá posteriormente. El interfaz extiende a EventListener, utilizado
para identificar este interfaz como un receptor de eventos del AWT.
Crear
un Adaptador del Receptor de Eventos
Este es un paso opcional. En el AWT, un
adaptador del receptor es una clase que proporciona una implementación por defecto para
todos los métodos de un tipo determinado de receptor. Todos los adaptadores en el paquete
java.awt.event proporcionan métodos vacíos que no hacen nada, así que un adaptador para el AsistenteListener
sería:
public class AsistenteAdapter implements AsistenteListener {
public abstract void sigSeleccionado( AsistenteEvent evt ) {}
public abstract void antSeleccionado( AsistenteEvent evt ) {}
public abstract void cancelaSeleccionado( AsistenteEvent evt ) {}
public abstract void finalizaSeleccionado( AsistenteEvent evt ) {}
}
Cuando se escriba una clase que sea un receptor
para un Asistente, es posible extender el AsistenteAdapter y proporcionar la
implementación solamente para aquellos métodos que intervengan. Es simplemente una clase
de conveniencia.
Crear
la Clase del Evento
Aquí es donde se crea la clase del evento, en
este caso: AsistenteEvent.
import java.awt.AWTEvent;
public class AsistenteEvent extends AWTEvent {
public static final int ASISTENTE_FIRST = AWTEvent.RESERVED_ID_MAX+1;
public static final int SIG_SELECCIONADO = ASISTENTE_FIRST;
public static final int ANT_SELECCIONADO = ASISTENTE_FIRST+1;
public static final int CANCELA_SELECCIONADO = ASISTENTE_FIRST+2;
public static final int FINALIZA_SELECCIONADO = ASISTENTE_FIRST+3;
public static final int ASISTENTE_LAST = ASISTENTE_FIRST+3;
public AsistenteEvent( Asistente source,int id ) {
super( source,id );
}
}
Las dos constantes ASISTENTE_FIRST y ASISTENTE_LAST
marcan el rango de máscaras que se utilizan en esta clase Event. Los
identificadores de eventos usan la constante RESERVED_ID_MAX de la clase AWTEvent para determinar el rango de
identificadores que no presentan conflicto con los valores ya definidos por el AWT. Cuando
se añadan más componentes RESERVED_ID_MAX puede estar incrementado.
Las cuatro constantes representan los
identificadores de los cuatro tipos de eventos que definen la funcionalidad del Asistente.
El identificador del evento y su origen son los dos argumentos que se pasan al constructor
del evento del Asistente. Cada fuente de eventos debe ser de tipo Asistente, que es el
tipo de componente para el que se define el evento. La razón es que solamente un panel Asistente
puede ser origen de eventos de tipo Asistente. Observar que la clase AsistenteEvent
extiende a AWTEvent.
Modificar
el Componente
El siguiente paso es equipar al componente con
métodos que permitan registrar y eliminar receptores para el nuevo evento.
Para enviar un evento a un receptor,
normalmente se hace una llamada al método adecuado del receptor (dependiendo de la
máscara del evento). Se puede registrar un receptor de eventos de acción desde el botón
Siguiente y transmitirlo a los objetos AsistenteListener registrados. El
método actionPerformed() del receptor para el botón Siguiente (u otras
acciones) puede ser implementado como se muestra a continuación.
public void actionPerformed( ActionEvent evt ) {
// No se hace nada si no hay receptores registrados
if( asistenteListener == null )
return;
AsistenteEvent asistente;
Asistente origen = this;
// Control del botón "Siguiente"
if( evt.getSource() == botonSiguiente ) {
Asistente = new AsistenteEvent( origen,
AsistenteEvent.SIG_SELECCIONADO );
}
// Los demás botones se controlan de forma similar
}
Cuando se pulsa el botón Siguiente, se
crea un nuevo evento AsistenteEvent con la máscara adecuada y el origen fijado en
el botón Siguiente, que es el que se ha pulsado. En el ejemplo, la línea de
código:
asistenteListener.sigSeleccionado( asistente );
se refiere al objeto asistenteListener,
que es una variable miembro privada de Asistente y es de tipo AsistenteListener,
tipo que se había definido en el primer paso de creación del evento.
En una primera impresión, el código parece
que restringe el número de receptores a uno. La variable privada asistenteListener
no es un array y solamente se puede hacer una llamada al método sigSeleccionado().
Para explicar el porqué de que no haya esta restricción, obliga a que haya que revisar
la forma en que se añaden los receptores de eventos.
Cada nuevo componente que genera eventos
predefinidos, o creados nuevos, necesita proporcionar dos métodos, uno para soportar la
adición de receptores y otro para soportar la eliminación de receptores. En la clase Asistente,
estos métodos son:
public synchronized void addAsistenteListener( AsistenteListener al ) {
asistenteListener = AsistenteEventMulticaster.add(
asistenteListener,al );
}
public synchronized void removeAsistenteListener( AsistenteListener al ) {
asistenteListener = AsistenteEventMulticaster.remove(
asistenteListener,al );
}
Ambos métodos hacen una llamada a miembros
estáticos de la clase AsistenteEventMulticaster, lo que introduce el siguiente
paso en que se permite el control de más de un receptor de eventos para el mismo tipo de
evento que se genere.
Manejar
Múltiples Receptores
Aunque es posible utilizar un Vector
para manejar múltiples receptores, el JDK ahora define una clase especial para mantener
esta lista de receptores, la clase AWTEventMulticaster. Una sola instancia de esta
clase mantiene referencias a los objetos receptores. Como esta clase es un receptor en sí
misma (implementa todos los interfaces de los receptores), cada uno de estos receptores
pueden ser también multicasters, creándose así una cadena de receptores de
eventos o multicasters.
Si un receptor de eventos es también un multicaster,
entonces representa un eslabón de la cadena; si solamente es un receptor, representa un
elemento final de la cadena. Desafortunadamente, no es posible reusar el AWTEventMulticaster
en el manejo de eventos Multicaster para los nuevos tipos. La mejor forma de
hacerlo es extender la clase AWTEventMulticaster, aunque esta operación sea en
cierta forma cuestionable.
La clase AWTEventMulticaster contiene
cincuenta y tantos métodos. De ellos, unos cincuenta proporcionan soporte a la docena de
tipos de eventos y sus correspondientes receptores que son parte del AWT. De los restantes
métodos, hay dos que necesitan recodificarse de nuevo, addInternal() y remove();
y se dice recodificar porque en AWTEventMulticaster, addInternal() es
un método estático y no puede ser sobrecargado y, por razones que no se entienden muy
bien, remove() hace una llamada a addInternal(), así que también necesita
ser recodificado, de ahí que se pueda cuestionar esta técnica por parte de algunos
puristas de los objetos.
Hay dos métodos, save() y saveInternal()
que proporcionan soporte para ficheros o canales de comunicación que podrán ser reusados
en la nueva clase Multicaster.
Por simplificar y en aras de la sencillez se
hace una subclase de AWTEventMulticaster, pero muy sencilla, ya que podrían
codificarse los métodos remove(), save() y saveInternal() para tener
una funcionalidad completa. El código siguiente es el que implementa el multicaster
para manejar el evento AsistenteEvent.
import java.awt.AWTEventMulticaster;
import java.util.EventListener;
public class AsistenteEventMulticaster extends AWTEventMulticaster
implements AsistenteListener {
protected AsistenteEventMulticaster( EventListener a,EventListener b ) {
super( a,b );
}
public static AsistenteListener add( AsistenteListener a,
AsistenteListener b ) {
return( (AsistenteListener)addInternal( a,b ) );
}
public static AsistenteListener remove( AsistenteListener al,
AsistenteListener antal ) {
return( (AsistenteListener)removeInternal( al,antal ) );
}
public void sigSeleccionado( AsistenteEvent evt ) {
// Aunque nunca se producirá una excepción por "casting"
// hay que ponerlo, porque un multicaster puede manejar
// más de un receptor
if( a != null )
( (AsistenteListener)a).sigSeleccionado( evt );
if( b != null )
( (AsistenteListener)b).sigSeleccionado( evt );
}
public void antSeleccionado( AsistenteEvent evt ) {
if( a != null )
( (AsistenteListener)a).antSeleccionado( evt );
if( b != null )
( (AsistenteListener)b).antSeleccionado( evt );
}
public void cancelaSeleccionado( AsistenteEvent evt ) {
if( a != null )
( (AsistenteListener)a).cancelaSeleccionado( evt );
if( b != null )
( (AsistenteListener)b).cancelaSeleccionado( evt );
}
public void finalizaSeleccionado( AsistenteEvent evt ) {
if( a != null )
( (AsistenteListener)a).finalizaSeleccionado( evt );
if( b != null )
( (AsistenteListener)b).finalizaSeleccionado( evt );
}
protected static EventListener addInternal( EventListener a,
EventListener b ) {
if( a == null )
return( a );
if( b == null )
return( b );
return( new AsistenteEventMulticaster( a,b );
}
protected EventListener remove( EventListener antal ) {
if( antal == a )
return( a );
if( antal == b )
return( b );
EventListener a2 = removeInternal( a,antal );
EventListener b2 = removeInternal( b,antal );
if( a2 == a && b2 == b )
return( this );
return( addInternal( a2,b2 ) );
}
}
Funcionamiento
del Asistente
Antes de ver el funcionamiento, se hace
necesaria una revisión de los métodos que se han utilizado. El constructor de la clase Multicaster
es protected, así que para obtener un nuevo AsistenteEventMulticaster hay que llamar
al método add(), al que se pasan los receptores como argumentos, representando las
dos piezas de una cadena de receptores que han de ser enlazados.
Para iniciar una nueva cadena, se pasará null en el primer
argumento y para añadir un nuevo receptor, se pasará uno ya existente como primer
argumento y el que se va a añadir como segundo argumento.
Otro método es remove(), al que se pasa
un receptor (o un receptor multicaster) como primer argumento y como segundo
argumento, el receptor que se va a eliminar.
Se han añadido cuatro métodos públicos para
soportar la propagación de eventos a través de la cadena. Para cada tipo de AsistenteEvent,
es decir, siguiente, anterior, cancelar y finalizar, hay un
método. Estos métodos deben ser implementados desde el AsistenteEventMulticaster
extendiendo el AsistenteListener, que es quien requiere que los cuatro métodos
estén presentes.
Ahora sí es el momento de ver cómo funciona
todo junto en el Asistente. Se supone que se ha construido un objeto asistente y se
han añadido tres receptores de eventos, creando una cadena de receptores.
Inicialmente, la variable privada asistenteListener
de la clase Asistente es null. Cuando se llama al método add(), el primer
argumento es nulo y el segundo no. El método add() hace una llamada al método addInternal().
Aunque uno de los argumentos es nulo, el retorno de addInternal() es un receptor
no-nulo. Esto se propaga al método add() que devuelve el receptor no-nulo al
método addAsistenteListener(). Aquí, la variable asistenteListener se fija al nuevo
receptor que se ha añadido.
Esto es exactamente lo que se pretendía. Si no
hay receptores y se añade uno nuevo, se asignará a la variable asistenteListener.
En este momento, asistenteListener es una referencia a un objeto AsistenteListener
que no es un multicaster (no es necesario utilizar un multicaster si
solamente hay un receptor registrado).
Cuando se hace una segunda llamada al método addAsistenteListener(),
los dos argumentos que se le pasan no son nulos, así que es necesario un multicaster,
por lo tanto, addInternal() devuelve una instancia a AsistenteEventListener
que será asignado a la variable asistenteListener, que ahora contiene ya una cadena de dos receptores. Al
añadir un tercer receptor el procedimiento es el mismo.
Si ahora se pulsa el botón Siguiente
sobre el panel del Asistente, es suficiente con invocar al método sigSeleccionado()
del objeto AsistenteListener representado por la variable asistenteListener y
enviar un evento de tipo AsistenteEvent a todos los receptores de la cadena.
Para eliminar un receptor hay que buscarlo en
la cadena de receptores de forma recursiva.
Como se puede observar por todo lo descrito,
tanto en este ejemplo no desarrollado del todo, como en el anterior, el desarrollo de
eventos propios o sintéticos en el nuevo modelo de eventos del JDK no es algo que se haga
con simplicidad, sino que requiere código adicional. La interacción entre eventos
diferentes y las clases que los soportan también es complicada de seguir.
Sin embargo, lo interesante es que no es
necesario crear un nuevo multicaster para cada tipo de evento que se cree. Como un multicaster
puede extender varios interfaces de receptores, es suficiente con añadir un receptor y
los métodos específicos del evento a un multicaster ya existente para conseguir
que maneje más tipos de eventos.
Una cuestión que se ha dejado de lado en esta
segunda explicación, pero que resulta interesante en la creación de eventos nuevos es el
manejo de la cola de eventos y la habilitación de eventos, ya comentada en otra sección. |