Tutorial de Java

Asistente Creado por el Usuario

Anterior | Siguiente
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.

Navegador

Home | Anterior | Siguiente | Indice | Correo