|
![]() |
AWT - Creación de Componentes Propios |
Anterior | Siguiente |
Para cubrir todos los conceptos vertidos sobre el AWT, tanto sobre los Componentes como sobre el nuevo modelo de eventos instaurado desde el JDK 1.1; y como fin de fiesta (por el momento), se va a crear un nuevo Componente de cosecha propia. Para mantener las cosas claras y no entrar en demasiadas profundidades, el ejemplo java1329.java creará un componente simple, un selector de color, y el fin que se persigue con el ejemplo es mostrar cómo se controlan los eventos y se incorporan Componentes al AWT, mas que entrar en detalles de la selección de color, que complicaría demasiado la explicación. Interfaz Gráfica del Selector de Color El Componente SelectorColor está formado por tres regiones, tal como muestra la figura al pie del párrafo. La zona izquierda presenta una paleta de colores, variando el tono de rojo de izquierda a derecha y el tono de verde de arriba a abajo. El usuario selecciona los niveles de rojo y verde pulsando con el ratón en esta paleta. En la zona media aparece una barra de desplazamiento, sobre la cual el usuario puede uindicar la cantidad de color azul colocando el selector de la barra en la posición deseada. La zona derecha es la que presenta el color seleccionado, que estará generado a partir de la combinación de rojo, verde y azul que el usuario haya elegido en los dos selectores. Pulsando con el ratón en esta zona se selecciona el color actual, generándose el evento AWT adecuado. Implementación del Selector de Color El Componente que se desea crear se implementa a través de cuatro clases: ColorEvent,
es la clase del evento propio que va a transportar el Color resultante
de la selección final A continuación se describe cada una de las cuatro clases, para luego entrar en el funcionamiento en sí del selector de color. Los eventos de tipo ColorEvent son eviados por el componente SelectorColor cuando el usuario pica en la zona derecha del interfaz gráfico del selector de color. El evento contiene un campo Color, que corresponde al color seleccionado y que se puede obtener a través del método getColor(). Cualquier evento que sea utilizado por un componente del AWT, debe sr subclase de AWTEvent, así que la declaración de ColorEvent será: public class ColorEvent extends AWTEvent { Todos los eventos AWT deben tener asignados identificadores enteros; un identificador válido y disponible para el programador es cualquiera por encima de RESERVED_ID_MAX, así que la línea de código siguiente es la que define al identificador para el evento que generará el selector de color. public static final int COLOR_SELECCIONADO = AWTEvent.RESERVED_ID_MAX + 1; Como la clase de un evento ahora se utiliza como una característica distintiva y no solamente como un identificativo, los componentes creados por los programadores no tienen que seleccionar valores globales únicos; en lugar de eso, el identificador puede utilizarse para distinguir entre diferentes tipos de una clase de un evento determinado. Por ejemplo, se puede definir también un COLOR_ACTIVADO que indique cuando el usuario ha cambiado el color seleccionado pero todavía no ha confirmado la selección. Así, con los dos identificadores, se podría distinguir entre los dos eventos con una sola clase ColorEvent, en lugar de utilizar dos clases separadas. El color asociado al evento se almacena en la variable color. protected Color color; El constructor del evento acepta como parámetro el origen del evento, origen, que en este caso será un SelectorColor y el color que se ha picado. También se declaran un método que permite al receptor del evento saber qué color se ha seleccionado, getColor(). El código correspondiente es el que se reproduce a continuación. public ColorEvent( Object obj,Color color ) { super( obj,COLOR_SELECCIONADO ); this.color = color; } public Color getColor() { return( color ); } Las clases interesadas en recibir eventos de tipo ColorEvent, deben implementar el interfaz ColorListener, que declara el método colorSeleccionado() a través del cual se despachan los eventos. Todos los interfaces deben extender el interfaz EventListener, así que hay que seguir las normas: import java.util.EventListener; public interface ColorListener extends EventListener { El método colorSeleccionado() deberá ser llamado por todos los interesados cuando se seleccione un color. El parámetro evt contiene el ColorEvent que les interesa. public void colorSeleccionado( ColorEvent evt ); La clase SelectorColor es un componente simple que se puede añadir al interfaz gráfico como cualquier otro componente del AWT proporcionado con el JDK y, además, utiliza el nuevo modelo de delegación de eventos. El interfaz gráfico no está muy trabajado a propósito, porque la intención es mostrar los entresijos de los eventos del modelo de delegación, que es lo más complicado del diseño de componentes, y no el diseño de interfaces, porque esto último, al fin y a la postre, va en función del gusto del programador, así que se plantea como reto al lector el conseguir un interfaz más amigable para este Selector de Color. Aquí se extiende la clase Canvas porque el selector de color es un Componente completamente orientado a dibujo, tal como se ha diseñado. Una orientación al lector, por si ha aceptado el reto anterior, es que utilice la clase Panel, para usar otros Componentes del AWT en la creación de un mejor interfaz gráfico para el selector de color. public class SelectorColor extends Canvas { El selector de color cuantifica el espacio RGB en seis niveles de cada componente del color: 0, 51, 102, 153, 204 y 255, que corresponden a la paleta de colores que utiliza Netscape. Si se desea una degradación más fina, se pueden utilizar más niveles. protected static final int NIVELES = 6; Los actuales niveles de rojo, verde y azul , se almacenan en las variables correspondientes. En el constructor se extraen los componentes del color inicial y luego se habilitan los eventos del ratón a través del método enableEvents(). Si no se habilitan los eventos de este modo, el Componentes sería incapaz de generar eventos de ratón. public SelectorColor( Color color ) { r = color.getRed(); g = color.getGreen(); b = color.getBlue(); enableEvents( AWTEvent.MOUSE_EVENT_MASK ); } Una alternativa para este Componente sería la de recibir sus propios evetnos de ratón, para lo cual debería implementar el interfaz MouseListener y registrarse como un receptor de eventos de ratón sobre el Componente. Así, el constructor llamaría al otro constructor con un color por defecto: public SelectorColor() { this( Color.black ); } Para determinar el tamaño correcto del Componente, se implementan los métodos getMinimumSize(), getMaximumSize() y getPreferredSize(). Las líneas de código siguientes, reproducen este último. public Dimension getPreferredSize() { return( new Dimension( 150,60 ) ); } Siguiendo con la apariencia en pantalla, el método paint() es el que va a rellenar las tres partes en que se ha dividido el Componente. El trozo de código siguiente muestra cómo se hace, no siendo muy importante el entrar en detalles, porque el código resulta bastante autoexplicativo. public void paint( Graphics g ) { int h = getSize().width / (NIVELES+3+NIVELES); int v = getSize().height / (NIVELES); for( int rojo=0; rojo < NIVELES; ++rojo) { for( int verde=0; verde < NIVELES; ++verde ) { g.setColor( new Color( rojo * 255 / (NIVELES-1), verde * 255 / (NIVELES-1),b ) ); g.fillRect( rojo*h,verde*v,h,v ); } } int x = NIVELES*h + h/2; int y = v / 2+v * (b*(NIVELES-1) / 255); g.setColor( getForeground() ); g.drawLine( x,y,x+2*h-1,y ); for( int azul=0; azul < NIVELES; ++azul ) { g.setColor( new Color( 0,0,azul*255 / (NIVELES-1) ) ); g.fillRect( (NIVELES+1)*h,azul*v,h,v ); } g.setColor( new Color( r,this.g,b) ); g.fillRect( (NIVELES+3)*h,0,h*NIVELES,v*NIVELES ); } El método processMouseEvent() es invocado automáticamente por el método processEvent() del Componente cuando se genera un evento de ratón. Habrá que sobreescribir este método para que se llame al método mousePressed() que interesa, en respuesta a las pulsaciones del ratón, y luego ya se llamará al método de la superclase processMouseEvent() para realizar el tratamiento adecuado. Si hay otros receptores registrados para los eventos, el método de la superclase los mantendrá informados a través del interfaz MouseListener. protected void processMouseEvent( MouseEvent evt ) { if( evt.getID() == MouseEvent.MOUSE_PRESSED ) { mousePressed( evt ); } super.processMouseEvent( evt ); } Se llama a mousePressed() cuando el usuario pulsa sobre el Componente. Si el usuario pulsa en la zona de selección de la izquierda, se asignarán menos niveles de rojo y verde al color final, cuantificados al número de tonos seleccionado. Si se pulsa sobre la barra central, se asignará un nuevo tono de azul. Y, si se pulsa sobre la zona de la derecha, se confirmará la selección del color que esté presente, llamando al método postColorEvent(), para lanzar el evento correspondiente. El código siguiente muestra cómo se hace. public void mousePressed( MouseEvent evt ) { int h = getSize().width / (NIVELES+3+NIVELES); int v = getSize().height / (NIVELES); // En la zona izquierda de selección de color directo if( evt.getX () < NIVELES*h ) { r = (evt.getX() / h) * 255 / (NIVELES-1); r = (r < 0) ? 0 : (r > 255) ? 255 : r; g = (evt.getY() / v) * 255 / (NIVELES-1); g = (g < 0) ? 0 : (g > 255) ? 255 : g; repaint(); // en la barra azul de enmedio } else if( evt.getX() < (NIVELES+3) * h ) { b = (evt.getY() / v) * 255 / (NIVELES-1); b = (b < 0) ? 0 : (b > 255) ? 255 : b; repaint(); // en la zona derecha de resultado de la combinación } else { postColorEvent(); } } El método postColorEvent() crea un nuevo ColorEvent, con this como origen y el color actualmente seleccionado como parámetro adicional, y lo envía a la cola de eventos del sistema. También se pueden enviar eventos a la cola de eventos del sistema a través de llamadas al método dispatchEvent(). La cola de eventos del sistema es monitorizada por un hilo de ejecución del AWT, EventDispatchThread, que lo que hace es extraer los objetos AWTEvent y llamar a dispatchEvent() sobre el Componente que sea el origen del evento. En la práctica, los applets deberán utilizar siempre el método dispatchEvent() para lanzar eventos, porque el gestor de seguridad les retringe el acceso a la cola de eventos del sistema. Para una aplicación, cualquiera de los dos métodos es posible, la diferencia entre el uso de dispatchEvent() y el acceso directo a la cola de eventos es claramente sutil: Un evento insertado en la cola será despachado posteriormente por el hilo de ejecución del AWT y, en el otro caso, el evento será despachado inmediatamente. No obstante, debe utilizarse siempre que se pueda la cola de eventos del sistema, porque esto protege al llamador contra posibles excepciones de tipo RuntimeException, que pueda lanzar el receptor. Aquí, tal como muestra el código siguiente, se indican los dos métodos. En cualquierea de ellos se llamará a dispatchEvent(), que invocará a su vez a processEvent(). Por lo tanto, habrá que sobreescribir este último para que pueda tratar adecuadamente el nuevo tipo de evento. protected void postColorEvent() { ColorEvent evt = new ColorEvent( this,new Color( r,g,b ) ); Toolkit toolkit = getToolkit(); EventQueue cola = toolkit.getSystemEventQueue(); cola.postEvent( evt ); } // Otra forma de hacerlo sería invocando al método dispatchEvent() // dispatchEvent( new ColorEvent( this,new Color( r,g,b) ) ); Para seguir la folosofía implantada por el nuevo modelo de delegación de eventos, hay que mantener una lista de los receptores registrados para recibir eventos que generará el Componente. Se puede utilizar un Vector para este propósito, aunque así se va a autilizar la clase ColorEventMulticaster para mantener esa lista. Por lo tanto, hay que incorporar el método addColorListener(), que añadirá un ColorListener a la lista utilizando el método add() de la clase ColorEventMulticaster. Este receptor añadido será notificado cuando el usuario seleccione un color a través del componente SelectorColor. public synchronized void addColorListener( ColorListener l ) { receptorColor = ColorEventMulticaster.add( receptorColor,l ); } Del mismo modo, hay que proporcionar un método para poder eliminar de la lista un objeto que quiera darse de baja en la recepción de eventos procedentes del SelectorColor. public synchronized void removeColorListener( ColorListener l ) { receptorColor = ColorEventMulticaster.remove( receptorColor,l ); } El método processEvent() es llamado por dispatchEvent() para distribuir eventos AWT generados por este Componente a cualquiera de los receptores registrados. Hay que sobreescribir el método para procesar los eventos de color: si el evento es de la clase ColorEvent, se llama al método processColorEvent(); y, en cualquier otro caso, se invocará al método processEvent() de la superclase, para tener controlados los eventos normales del AWT. protected void processEvent( AWTEvent evt ) { if( evt instanceof ColorEvent ) processColorEvent( (ColorEvent)evt ); else super.processEvent( evt ); } Ahora, el ColorEvent será distribuido a la lista de receptores registrados a través del método colorSeleccionado() del interfaz ColorListener. Como se ha indicado antes, también se puede utilizar un Vector de receptores que recorrerá la lista llamando a colorSeleccionado() sobre cada uno de los elemenos, y el mantenimiento de esta lista no es nada complicado. La clase ColorEventMulticaster también mantiene una lista de objetos receptores de eventos del SelectorColor, ColorListener. Esta clase implementa el interfaz ColorListener y mantiene referencias a otros dos objetos ColorListener. Es la típica lista, tal como se muestra en la figura. Cuando se invoca al método colorSeleccionado(), el evento se propaga a los dos ColorListener asociados. Si se encadenan más objetos de este tipo, se consigue una lista de receptores, tal como indica la representación gráfica de la figura. Se implementa el interfaz ColorListener y se mantienen referencias a otros dos objetos ColorListener, a y b: class ColorEventMulticaster implements ColorListener { protected ColorListener a,b; El constructor acepta referencias a dos objetos ColorListener. Y el método colorSeleccionado(), lo único que hará será pasar el evento a los objetos ColorListener asociados. public void colorSeleccionado( ColorEvent evt ) { a.colorSeleccionado( evt ); b.colorSeleccionado( evt ); } Si se quiere añadir un receptor, se usa el método estático add(). Este método devuelve un objeto ColorListener, que genera una lista con los objetos proporcionados. En el caso de que alguno de los dos objetos sea nulo, se puede devolver el otro; o se puede devolver un ColorEventMulticaster que pasará cualquier llamada al método colorSeleccionado() a los dos objetos, tal como refleja la figura siguiente. En el caso de querer eliminar un receptor, se usará el método estático remove(). Este método devuelve un ColorListener formado por la lista de ColorListener a con el ColorListener b eliminado. Si a o b son nulos, se devuelve null. Sin embargo, si a es una lista ColorEventMulticaster, entonces se puede eliminar b de los árboles y combinar el resultado. En otro caso, se devuelve a, porque será un ColorListener. La figura siguiente intenta mostrar estas posibilidades gráficamente. Utilización del Selector de Color El selector de color se puede utilizar ahora como cualquier otro de los Componentes que se han visto en el AWT, es decir, se puede añadir a un Contenedor y luego registrar a los objetos interesados en recibir sus eventos a través del método addColorListener(). Cuando el usuario seleccione un color, los receptores serán notificados a través de dus métodos colorSeleccionado(). El ejemplo java1329.java, muestra el uso que se puede hacer del Componente, en este caso, muy trivial. import java.awt.*; public class java1329 implements ColorListener { public void colorSeleccionado( ColorEvent evt ) { System.out.println( "Color Seleccionado: "+evt.getColor() ); } public static void main( String args[] ) { Frame frame = new Frame( "Tutorial de Java, AWT" ); SelectorColor selector = new SelectorColor(); java1329 ejemplo = new java1329(); selector.addColorListener( ejemplo ); frame.add( "Center",selector ); frame.pack(); frame.setVisible( true ); } } El ejemplo simplemente consiste en imprimir el color seleccionado, pero muestra claramente que el uso del Componente que se ha diseñado no difiere en absoluto de la forma de empleo de cualquier otro de los Componentes estándar del AWT. |
|