• No se han encontrado resultados

El AWT (Abstract Window Toolkit)

N/A
N/A
Protected

Academic year: 2019

Share "El AWT (Abstract Window Toolkit)"

Copied!
124
0
0

Texto completo

(1)

Interfaces de usuario en Java:

componentes AWT

Introducción

A lo largo de este capítulo y los siguientes, vamos a abordar la construcción de interfaces de usuario en el lenguaje Java, se ha querido crear varios capítulos para tratar este tema debido a su relevancia.

El interfaz de usuario tiene una gran importancia ya que es lo que realmente va a ver y utilizar el usuario. Un interfaz de usuario debe ser coherente, fácil de utilizar, intuitivo, atractivo, amigable, rápido, etc., pero sobretodo lo más importante es que el usuario pueda utilizarlo correctamente y se sienta dentro de un entorno amigable.

No vamos a teorizar sobre la construcción de interfaces de usuario, sino que vamos a explicar simplemente como crear sencillos interfaces dentro del lenguaje Java. Trataremos las bases de la creación de interfaces de usuario comentando las clases más relevantes que Java pone a nuestra disposición.

Este capítulo está dedicado a la utilización de componentes AWT.

El AWT (Abstract Window Toolkit)

(2)

encuentran en el paquete JAVA.AWT. El otro conjunto de clases que ofrece Java para la construcción de interfaces de usuario se denomina Swing y lo veremos en próximos capítulos.

El AWT está presente desde la primera versión del lenguaje Java, y todavía se sigue utilizando, tanto en la versión 2 del lenguaje como en la 1.1, debido a esto lo hemos incluido en nuestro curso. Los componentes Swing no se pueden utilizar dentro de Visual J++ pero sí con JBuilder.

Pero el AWT además de proporcionar clases que permiten la construcción de interfaces de usuario, también ofrece un subpaquete llamado java.awt.event que contiene las clases especializadas en el tratamiento de eventos, como puede ser la pulsación de un botón, la selección de un elemento de una lista, cerrar una ventana etc.

En este capítulo sólo vamos a tratar la utilización de componentes AWT, en el siguiente veremos con detenimiento el tratamiento de eventos que realiza Java, y también los gestores de diseño que nos permiten tener un mayor control sobre la posición de los distintos componentes y elementos del interfaz.

Mediante los programas Java se pueden construir interfaces de usuario muy completos, similares a los que podríamos construir con otros lenguajes de programación como Visual Basic, aunque ya avisamos, no de forma tan sencilla.

Para poder realizar interfaces que dispongan de botones, menús, etiquetas, campos de texto, listas y otros componentes se diseñó el Abstract Window Toolkit (AWT). El AWT ofrece lo siguiente:

• Un conjunto completo de accesorios de interfaz de usuario y otros componentes como

ventanas, menús, botones, casillas de verificación, campos de texto, barras de desplazamiento y listas desplegables.

• Soporte para contenedores de interfaces de usuario para que tengan a otros contenedores

dentro de él o accesorios como pueden ser botones, listas, cajas de texto, etc.

• Un sistema de eventos para administrar los eventos del sistema y los del usuario cuando

interacciona con componentes del AWT. Lo que vamos a ver de eventos y gestores de diseño en el próximo capítulo se aplica tanto a componentes AWT como a componentes Swing.

• Mecanismos para distribuir los componentes de forma que permitan un diseño de interfaz de

usuario independiente de la plataforma.

La idea fundamental que sostiene el AWT es que una ventana Java es un conjunto de componentes anidados que empiezan desde la ventana exterior hasta el más pequeño de los componentes que forman parte del interfaz. Esta anidación de componentes dentro de contenedores crea una jerarquía de componentes desde la casilla de verificación más pequeña hasta la ventana general de la pantalla.

Esta jerarquía determina la distribución de elementos en la pantalla y dentro de otros elementos además del orden en el que se muestran. Los componentes principales con los que puede trabajar el AWT son los siguientes:

• Contenedores (Container): componentes AWT genéricos que incluyen a otros componentes.

(3)

• Lienzos (Canvas): es una superficie de dibujo que permite dibujar imágenes y realizar otras

operaciones gráficas.

• Componentes de interfaz de usuario: incluyen botones, listas, menús de aparición súbita,

casillas de verificación, campos de texto y otros elementos comunes de un interfaz de usuario.

• Componentes de construcción de ventana: incluyen ventanas, cuadros, barras de menú y

diálogos.

Como ya habíamos comentado, las clases del AWT se encuentran organizadas en el paquete JAVA.AWT, para recordar un poco el término diremos que los paquetes Java son una manera de agrupar clases e interfaces relacionadas, permiten que grupos modulares de clases estén disponibles sólo cuando se necesiten.

Volviendo al AWT, sus diferentes clases conforman una jerarquía y la raíz para la mayoría de los componentes AWT es la clase Component, que ofrece características comunes para todos los componentes. La muestra una jerarquía parcial de las clases AWT.

Uno de los principales problemas con los que nos podemos encontrar es la portabilidad del interfaz de usuario, es decir, conseguir que la misma información (ventanas, controles, imágenes y otros objetos) guarden sus proporciones en todas las plataformas en las que el sistema tiene que ejecutarse. Dicho de otro modo, que la apariencia de lo que se ve sea la misma en distintos entornos.

La solución, sin ser nada fácil, es muy similar a la que se utiliza para escribir código portable. Para hacer que las aplicaciones funcionen bajo distintas plataformas, en lugar de compilar el código fuente de las mismas al código máquina de un microprocesador concreto, se compila a un pseudo-código, y se ejecuta en una máquina virtual, es necesario escribir una máquina virtual para cada entorno, pero una vez hecho esto, todos los programas escritos para ese compilador podrán ser ejecutados en todas aquellas plataformas para las que exista máquina virtual. Del mismo modo, es necesario crear una interfaz independiente del medio para poder portar las aplicaciones sin perder el aspecto que les imprimió su creador.

(4)

Java resuelve este problema utilizando lo sus creadores han dado en llamar "Gestores de diseño" (Layout Managers), que trataremos con detenimiento en el capítulo correspondiente.

Utilizando los componentes del AWT

En este apartado vamos a comentar algunos de los componentes básicos que podemos encontrar dentro del AWT. Vamos a realizar una sencilla aplicación a la que vamos a ir añadiendo diferentes componentes.

Frame

Lo primero que vamos a añadir a nuestra aplicación es una ventana, una ventana se representa mediante la clase Frame.

La clase Frame es un tipo de contenedor, por lo que es un descendiente de la clase Container, y además es una ventana, por lo que hereda directamente de la clase Window.

Para que nuestra aplicación posea una ventana tenemos dos opciones, creamos un atributo que sea un objeto de la clase Frame o bien nuestra aplicación hereda de la clase Frame. Vamos a elegir la segunda opción, ahora nuestra aplicación va a heredar todo el comportamiento de la clase Frame. El código con el que vamos a empezar es el que muestra el Código fuente 80.

import java.awt.*;

public class Ventana extends Frame{

public static void main (String[] args){ }

}

Código fuente 80

El Código fuente 80 merece el siguiente comentario. Para utilizar las clases de creación de interfaces de usuario con los componentes del AWT debemos importar el paquete JAVA.AWT, como hacemos en este caso.

Vamos a añadir un constructor a nuestra clase para que al crear un objeto se instancie de una forma determinada, es decir, con un título y dimensiones determinadas. El constructor podría ser como indica el Código fuente 81.

public Ventana(String titulo,int x, int y){ super(titulo);

setSize(x,y); show(); }

(5)

En el constructor de nuestra clase Ventana, llamamos con super() al constructor de la clase padre, en este caso la clase Frame. El constructor de la clase Frame se encuentra sobrecargado, y en una de sus versiones acepta como parámetro el título de la ventana.

Una vez lanzado el constructor se utiliza el método setSize() de la clase Frame para establecer las dimensiones de la ventana, y por último se muestra dicha ventana con el método show() de la clase Frame.

Una vez que ya tenemos constructor, podemos utilizar nuestra clase dentro del método main() de nuestra aplicación como muestra el Código fuente 82.

public static void main (String[] args){

Ventana miVentana=new Ventana("Una ventana",300,150); }

Código fuente 82

Si ejecutamos la aplicación podremos ver un objeto de nuestra clase Ventana, más o menos debe ser como el que aparece en la Figura 42.

Figura 42

Como el lector habrá comprobado, la ventana no se puede cerrar, más tarde en el siguiente capítulo veremos como implementar este comportamiento ala hora de tratar los eventos en el lenguaje Java.

Cursor

Si queremos modificar el aspecto del cursor del ratón en nuestra ventana utilizaremos el método setCursor(). Este método establece el aspecto que va a tener el cursor del ratón al moverse en el área de la ventana. Como parámetro recibe un objeto de la clase Cursor.

Esta clase representa al cursor del ratón. En el Código fuente 83 se muestra como utilizar este método, el constructor de la clase Cursor recibe como parámetro el tipo de cursor que se quiere crear. Los tipos de cursores se encuentran definidos como constantes de la clase Cursor.

miVentana.setCursor(new Cursor(Cursor.HAND_CURSOR));

(6)

MenuBar

Para añadir una barra de menú a nuestra ventana utilizaremos el método setMenuBar() de la clase Frame. Este método recibe como parámetro un objeto de la clase MenuBar, que representa una barra de menú.

Menu

Una vez creado el objeto MenuBar vamos a ir añadiéndole los menús que sean necesarios, para ello utilizaremos el método add() de la clase MenuBar. Este método recibe como parámetro objetos de la clase Menu.

MenuItem

A su vez cada menú (objeto de la clase Menu) va a tener elementos de menú, que se van añadiendo con el método add() de la clase Menu. A este método se le debe pasar como parámetro objetos de la clase MenuItem, que representarán cada uno de los elementos de menú.

CheckboxMenuItem

En nuestro ejemplo vamos a crear un nuevo método llamado añadeMenu(), que va a añadir a nuestra clase Ventana una barra de menú con dos menús, y uno de los elementos de menús va a ser una casilla de verificación. Este tipo de elemento de menú especial se representa mediante la clase CheckboxMenuItem.

Vamos a modificar el constructor de nuestra clase Ventana para que llame al método añadeMenu(). Finalmente el código quedaría como aparece en el Código fuente 84.

import java.awt.*;

public class Ventana extends Frame{

public Ventana(String titulo,int x, int y){ super(titulo);

setSize(x,y); añadeMenu(); show(); }

public void añadeMenu(){ //barra de menú MenuBar mb; //menús Menu m1, m2;

//elementos de menú MenuItem mi1_1, mi1_2; CheckboxMenuItem mi2_1;

//se crea y establece la barra de menú mb = new MenuBar();

setMenuBar(mb);

//se crea y añade el primer menú m1 = new Menu("Menú 1", true); mb.add(m1);

//se añade los elementos de menú al primer menú mi1_1 = new MenuItem("Menú item 1_1");

(7)

mi1_2 = new MenuItem("Menú item 1_2"); m1.add(mi1_2);

//se crea y añade el segundo menú m2 = new Menu("Menú 2");

mb.add(m2);

//se crea y añade el elemento de menú del segundo menú mi2_1 = new CheckboxMenuItem("Menú item 2_1");

m2.add(mi2_1); }

public static void main (String[] args){

Ventana miVentana=new Ventana("Una ventana",300,150); }

}

Código fuente 84

Y el resultado de la ejecución de la aplicación es el que aparece en la Figura 43.

Figura 43

Todas las clases adicionales utilizadas en este ejemplo: MenuBar, Menu, MenuItem y CheckboxMenuItem pertenecen todas ellas al paquete JAVA.AWT. Estas clases presentan una jerarquía separada del resto de las clases del AWT, todas ellas heredan de MenuComponent, su jerarquía se muestra en la Figura 44.

(8)

Otro tipo de ventanas que podemos crear con el AWT son los diálogos, este tipo de ventanas se distinguen del resto en que son dependientes de otra ventana, al destruir la ventana principal se destruyen todos los diálogos que dependan de ella y cuando se minimiza la ventana principal, todos los diálogos dependientes desaparecen de la pantalla.

Dialog

Los diálogos se representan mediante la clase Dialog, que ofrece además una subclase llamada FileDialog que permite al usuario abrir y guardar ficheros.

Un diálogo puede ser modal, es decir, hasta que no se cierre no se permite interactuar al usuario con la ventana de la que depende.

Vamos a añadir a nuestra aplicación un método que muestre un diálogo modal que dependa de la ventana creada anteriormente en este mismo apartado. Este método se llama muestraDialogo() y posee el Código fuente 85.

public void muestraDialogo(String titulo,int x,int y){ Dialog dialogo=new Dialog(this,titulo,true); dialogo.setSize(x,y);

dialogo.show(); }

Código fuente 85

Como se puede observar el método muestraDialogo() recibe tres parámetros, el título de la ventana de diálogo y las dimensiones de la misma. En la primera línea creamos un objeto de la clase Dialog, el constructor de esta clase, en una de sus versiones, recibe tres parámetros, la referencia a la ventana a la que va a estar relacionada, el título y un valor booleano que indica si el diálogo es de tipo modal o no. La ventana del que depende el diálogo es nuestra propia clase, debido a esto utilizamos la referencia this.

La llamada al método muestraDialogo() la podemos situar dentro del método main(), una vez creado un objeto de la clase Ventana. El resultado de la ejecución de la aplicación se puede observar en la Figura 45.

Figura 45

(9)

FileDialog

Si queremos mostrar un diálogo para la elección de un fichero utilizaremos la clase FileDialog. Esta clase hereda de la clase vista anteriormente, Dialog.

Para mostrar un diálogo de este nuevo tipo se va a utilizar el método muestraDialogoFichero() cuyo código es el Código fuente 86.

public void muestraDialogoFichero(int x, int y){ FileDialog dialogo=new FileDialog(this); dialogo.setSize(x,y);

dialogo.show(); }

Código fuente 86

El constructor de la clase FileDialog que se ha utilizado, recibe como parámetro la referencia a la ventana a la que se encuentra asociado este diálogo, que sigue siendo una instancia de nuestra clase Ventana. El aspecto de la ejecución de nuestra aplicación es el que aparece en la Figura 46.

Figura 46

Como se puede comprobar, este tipo de diálogo va a ser siempre modal, y al pulsar cualquiera de los botones se cierra el diálogo.

Una vez comentadas algunas clases del AWT vamos a definir el concepto de contenedor de una forma un poco más extensa y relacionándolo con lo que hemos visto hasta ahora.

Container

(10)

Pero además, los gestores de diseño permiten que esta distribución, o mejor dicho, la apariencia de la distribución de los componentes, se mantenga similar a través de los distintos entornos gráficos en los que se va a ejecutar la aplicación Java. En el siguiente capítulo, dentro del apartado dedicado a los gestores de diseño veremos los diferentes tipos y como utilizarlos en cada caso.

De la clase Container derivan las siguientes:

• Panel: No es más que una particularización de la clase Container. Tampoco tiene

representación gráfica y generalmente se utiliza para gestionar una agrupación lógica de elementos. El caso más típico es el del teclado de una calculadora.

• Window: Esta clase permite la definición de "ventanas" como áreas independientes capaces de

contener otros elementos, no tienen título, ni barra de menú, ni botones de tamaño, cierre, etc. De este modo esta clase puede utilizarse para construir ventanas que se adapten a las peculiaridades de cada uno de entornos para los que se va a construir una máquina Java (JVM). De esta clase, heredan las siguientes.

• Frame: Esta es clase que utilizamos para construir lo que normalmente entendemos por

ventanas o "forms" en algunos lenguajes.

• Dialog: Esta clase se utiliza para crear cajas de diálogo.

• FileDialog: Esta es un caso especial de la clase Dialog, de la cual hereda. Puesto que las tareas

de manejo de ficheros (guardar, guardar como y abrir) son muy comunes en todos los entornos, esta clase proporciona cajas de diálogo para realizar estas y otras tareas relacionadas.

Button

Ya hemos visto algunos contenedores y también la creación de menús, ahora vamos a añadir un botón a nuestra ventana. Los botones se encuentran representados por la clase Button. Para añadir el botón vamos a definir un atributo llamado boton dentro de nuestra clase Ventana, es decir, en la declaración de atributos escribiremos algo como lo que aparece en el Código fuente 87.

public Button boton;

Código fuente 87

El botón lo vamos a instanciar y añadir en el constructor de la clase Ventana, añadiendo para ello las el Código fuente 88.

boton=new Button("Un botón"); this.add(boton);

Código fuente 88

(11)

Se debe recordar que la clase Frame es un tipo de contenedor, y para añadir elementos a un componente contenedor se debe utilizar el método add() de la clase Container. Se utiliza la referencia this para indicar que el botón se va a añadir a nuestra ventana, aunque en realidad no es necesario, se ha puesto para una mayor claridad. El método add() en esta versión (está sobrecargado) recibe como parámetro la instancia del componente que se quiere añadir al contenedor.

Si probamos nuestra aplicación, vemos que se ha añadido el botón, pero es un botón enorme que ocupa toda la superficie de nuestra ventana. Para solucionar este problema vamos a utilizar un gestor de diseño. Los gestores de diseño (LayoutManagers) los trataremos más adelante en el siguiente capítulo, de momento simplemente vamos a añadir una línea de código para evitar el problema anterior. El nuevo código del constructor es el Código fuente 89.

public Ventana(String titulo,int x, int y){ super(titulo);

setSize(x,y); añadeMenu();

setLayout(new FlowLayout()); boton=new Button("Un botón"); this.add(boton);

show(); }

Código fuente 89

La nueva línea la vamos a comentar de forma somera, como ya hemos dicho más adelante explicaremos los gestores de diseño. La línea de código que hemos añadido permite establecer un tipo de gestor de diseño para evitar el efecto de la extensión del botón en toda la superficie del contenedor. El aspecto final de nuestra ventana es el de la Figura 47.

Figura 47

Un objeto botón genera un evento cada vez que lo pulsamos, generalmente lo vamos a utilizar para que el usuario con su pulsación nos indique que desea que se realice una acción determinada. Los eventos los veremos más en detalle en el próximo capítulo. Ahora si pulsamos el botón no pasará nada, el evento se lanza pero no hay nadie que lo trate.

La clase Button ofrece el método setLabel() para asignar la cadena que se le pasa como argumento a la etiqueta del botón.

(12)

Label

Las etiquetas son texto estático que se suele utilizar para mostrar información y dar indicaciones al usuario. En Java las etiquetas se representan con la clase Label. En nuestro ejemplo vamos a añadir tres etiquetas, pero en lugar de añadirlas directamente a nuestra ventana las vamos a añadir a un objeto de la clase Panel, para agrupar todas ellas bajo un gestor de diseño diferente al establecido para la ventana. Para añadir las etiquetas vamos a crear un nuevo método llamado añadeEtiquetas(), cuyo código se puede ver en el Código fuente 90.

public void añadeEtiquetas(){ Panel panel=new Panel();

panel.setLayout(new GridLayout(0,1,1,1)); Label label1=new Label();

label1.setText("Etiqueta 1");

Label label2=new Label("Etiqueta 2"); label2.setAlignment(Label.CENTER); Label label3=new Label("Etiqueta 3"); panel.add(label1);

panel.add(label2); panel.add(label3); add(panel);

validate(); }

Código fuente 90

En la primera línea instanciamos el objeto de la clase Panel que va a contener las etiquetas, y a continuación le asignamos un gestor de diseño. Luego se van creando las etiquetas, como se puede ver en el constructor utilizado, se le pasa como parámetro el texto que van a mostrar las diferentes etiquetas. Utilizamos el método setAligment() de la clase Label para alinear el texto de la etiqueta, utilizando como parámetro una constante definida por la clase Label, por defecto el texto de las etiquetas se encuentra alineado a la izquierda.

Después añadimos las etiquetas al objeto de clase Panel creado, ya que es un tipo de contenedor, utilizamos el método add(). Una vez añadidas las etiquetas al panel, debemos añadir el panel a nuestra ventana, al igual que hacíamos con el botón.

El método validate() que aparece al final de nuestro código es utilizado para que la ventana muestre los cambios que se han realizado sobre ella, ya que el panel que contiene las etiquetas se ha añadido después de mostrar la ventana. El método validate() pertenece a la clase Container, y su función es la de actualizar el aspecto de los contenedores. El aspecto que debe tener nuestra aplicación es el de la Figura 48.

(13)

Los métodos más relevantes de la clase Label son setText() y getText() que asignan y devuelven el texto de la etiqueta, respectivamente.

List, Choice

Otro elemento del AWT son las listas y las listas desplegables, en ambos casos se trata de un conjunto de elementos que podemos seleccionar de entre varias opciones. Las listas se encuentran representadas por la clase List y las listas desplegables se encuentran representadas por la clase Choice.

Como viene siendo costumbre vamos a crear un método que añada los dos tipos de lista a nuestra aplicación, este método se denomina añadeListas(). Y lo implementamos como indica el Código fuente 91.

public void añadeListas(){

List lista1=new List(3, true); Choice lista2=new Choice(); lista1.add("Elemento 1"); lista1.add("Elemento 2"); lista1.add("Elemento 3"); lista1.add("Elemento 4"); lista1.add("Elemento 5"); add(lista1);

lista2.addItem("Elemento 1"); lista2.addItem("Elemento 2"); lista2.addItem("Elemento 3"); lista2.addItem("Elemento 4"); lista2.addItem("Elemento 5"); add(lista2);

validate(); }

Código fuente 91

Lo que procede a continuación es comentar el código anterior. El constructor de la clase List recibe dos parámetros en la versión del método utilizada, por un lado le indicamos el número de elementos que queremos que sean visibles, y por otro lado le indicamos si se permite la selección múltiple, es decir, si se tiene la posibilidad de seleccionar varios elementos a la vez. El constructor de la clase Choice, no está sobrecargado y no recibe ningún parámetro.

Una vez instanciado un objeto de la clase List vamos a ir añadiendo cada uno de los elementos que va a contener con el método add() de la clase List, y a continuación añadimos la lista a la ventana con el método add().

Con el objeto Choice realizamos la misma tarea. Al igual que en el ejemplo de las etiquetas, es necesario lanzar el método validate() de la clase Container. El método añadeListas() lo llamaremos, al igual que ocurría en los ejemplos anteriores, en el método main() de nuestra clase.

Para simplificar el aspecto de nuestra aplicación, vamos a eliminar de nuestro constructor la creación del botón. En la Figura 49 se puede ver el nuevo aspecto de la aplicación.

(14)

Figura 49

De los métodos que ofrece la clase List se destacan los siguientes:

• int getItemCount(): devuelve el número de elementos de una lista.

• replaceItem(String,int): reemplaza el elemento cuyo índice se especifica en el segundo

parámetro por el texto indicado en el primer parámetro. El primer elemento de la lista posee el índice cero.

• removeAll(): elimina todos los elementos de la lista.

• remove(int): elimina el elemento de la lista cuyo índice coincida con el especificado.

• String getSelectedItem(): devuelve el elemento que se encuentra seleccionado.

• String[] getSelectedItems(): devuelve los elementos que se encuentran seleccionados.

• boolean isIndexSelected(int): indica si el elemento cuyo índice se pasa por parámetro se

encuentra seleccionado o no.

Y de la clase Choice podemos destacar los siguientes:

• int getItemCount(): devuelve el número de elementos de una lista.

• String getItem(int): devuelve el elemento cuyo índice coincide con el se pasa por parámetro.

• select(int): selecciona el elemento cuyo índice coincide con el que se pasa por parámetro.

TextField, TextArea

Ahora vamos a pasar a ver las cajas de texto y las áreas de texto. Normalmente estos dos tipos de controles se utilizan para obtener una entrada del usuario. La diferencia principal entre ambos es que las cajas de texto poseen una única línea, y las áreas de texto permiten tener más de una, reconociendo los saltos de línea, además muestra barras de scroll si el texto no es visible completamente en el área de texto.

(15)

Al igual que en el caso de las listas, al ser dos componentes muy parecidos vamos a implementar un método llamado añadeTexto() que va a añadir a nuestra ya conocida aplicación una caja de texto y un área de texto. El método añadeTexto() es como se muestra en el Código fuente 92.

public void añadeTexto(){

TextField caja=new TextField("Texto",10); TextArea area=new TextArea(5,10);

area.setText("Primera línea.\n"); area.append("Segunda línea.\n");

area.append("Tercera línea que es más larga que las anteriores.\n"); add(caja);

add(area); validate(); }

Código fuente 92

El constructor de la clase TextField recibe como argumentos en una de sus versiones, ya que se encuentra sobrecargado, el texto que va a parecer en la caja de texto y el tamaño de la caja de texto. El constructor utilizado para instanciar el objeto TextArea recibe como parámetros dos enteros que indican el número de filas y de columnas del área de texto.

Para asignarle texto al objeto TextArea y al objeto TextField se utiliza el método setText() pasando como parámetro un objeto de la clase String que va a representar el texto que va a contener el área o campo de texto.

La clase TextArea ofrece el método append() para ir añadiendo al final del texto existente en el área de texto el nuevo texto que se le pasa por parámetro. Se puede observar que para indicar un salto de línea utilizamos el carácter de escape especial \n. Por último añadimos la caja de texto y el área de texto a nuestra ventana y llamamos al método validate(), al igual que en los casos anteriores.

Si llamamos al método añadeTexto() de nuestra clase Ventana, se obtendrá el resultado mostrado en la Figura 50.

Figura 50

A continuación vamos a comentar los métodos más interesantes de la clase TextComponent, y que por lo tanto heredan las clases TextArea y TextField:

(16)

• selectAll(): selecciona todo el texto.

• select(int,int): selecciona el texto comprendido entre los índices indicados como parámetros.

Cero es el primer carácter.

• setCaretPosition(int): indica cual va a ser la posición del cursor (el caret) dentro del texto. • int getCaretPosition(): devuelve la posición en la que se encuentra el cursor dentro del texto.

• setEditable(boolean): indicamos si el texto se va a poder modificar o no, dependiendo del

argumento booleano que le facilitemos.

• String getText(): devuelve el texto que posea el componente.

La clase TextField añade una serie de métodos a los que hereda de la clase TextComponent, los más interesantes son:

• setEchoChar(char): establece en la caja de texto un carácter de eco, que ocultará el contenido

real del texto de la caja, se suele utilizar el carácter ‘*’.

• char getEchoChar(): devuelve el carácter de eco que tiene asignada la caja de texto.

Por su parte la clase TextArea añade los siguientes métodos:

• insertText(String, int): inserta el texto especificado, en la posición indicada

• replaceText(String, int, int): reemplaza el texto comprendido entre las posiciones indicadas

por el texto especificado.

Checkbox, CheckboxGroup

Los siguientes componentes del AWT que vamos a tratar son las opciones o casillas de verificación. Estos componentes poseen dos estados: activado o desactivado (verdadero o falso) y se utilizan para que el usuario elija entre una serie de opciones disponibles. La clase que representa a las opciones es Checkbox.

Hay dos tipos de opciones, las que son independientes y se pueden activar y desactivar sin tener en cuenta al resto, y las que forman parte de un grupo de opciones y sólo puede estar activada una de las opciones agrupadas. Para agrupar las opciones vamos a hacer uso de la clase CheckboxGroup. Esta clase nos asegura que de todos los objetos Checkbox que contenga, sólo uno de ellos puede estar activado en un mismo momento.

Vamos a añadir seis de opciones, tres de ellas independientes y otras tres agrupadas y por lo tanto dependientes y excluyentes entre sí. Los dos conjuntos de opciones las vamos a añadir en dos objetos de la clase Panel, y a su vez añadiremos estos dos objetos a nuestra ventana.

Al igual que en todos los casos anteriores vamos a mostrar el código del método (Código fuente 93) que añade a nuestra aplicación los nuevos componentes y acto seguido comentaremos dicho código.

public void añadeOpciones(){ Panel p1, p2;

(17)

Checkbox cb1, cb2, cb3; //opciones excluyentes Checkbox cb4, cb5, cb6;

//objeto que va a contener al grupo de opciones CheckboxGroup cbg;

//Primer panel con las casillas independientes //se crean las opciones

cb1 = new Checkbox(); cb1.setLabel("Opción 1");

cb2 = new Checkbox("Opción 2",true); cb3 = new Checkbox("Opción 3"); cb3.setState(true);

p1 = new Panel();

p1.setLayout(new FlowLayout()); p1.add(cb1);

p1.add(cb2);

p1.add(cb3); //Segundo panel con las opciones agrupadas cbg = new CheckboxGroup();

cb4 = new Checkbox("Opción 4", cbg, true); cb5 = new Checkbox("Opción 5", cbg, false); cb6 = new Checkbox("Opción 6", cbg, false); p2 = new Panel();

p2.setLayout(new FlowLayout()); p2.add(cb4);

p2.add(cb5); p2.add(cb6);

//utilizamos otro gestor de diseño para la ventana setLayout(new GridLayout(0, 2));

//se añaden los paneles add(p1); add(p2);

validate(); }

Código fuente 93

En este código se han utilizado varias versiones del constructor de la clase Checkbox para instanciar los objetos correspondientes, vamos a ver cada una de ellas.

El primer constructor es el más sencillo y no posee ningún tipo de argumentos, el segundo constructor ofrece un parámetro para indicar mediante un objeto de la clase String la etiqueta que va a acompañar a la opción y otro parámetro para indicar el estado de la opción activada o desactivada (true o false), y el tercer constructor permite asignar la etiqueta de la opción correspondiente.

Para activar una opción utilizamos el método setState() de la clase Checkbox, a este método le pasamos un valor booleano para indicar si va a estar activada o no la opción. Una vez creadas las opciones y configuradas de forma correcta, creamos el panel que las va a contener instanciando un objeto de la clase Panel al que aplicaremos un gestor de diseño con el método setLayout(), y a continuación añadimos los objetos Checkbox mediante el método add() de la clase Container.

Para crear el segundo grupo de opciones y para que permanezcan agrupadas de forma excluyente es necesario instanciar un objeto de la clase CheckboxGroup. Este objeto se utilizará como parámetro en otra nueva versión del constructor de la clase Checkbox. En este nuevo constructor vamos a indicar la etiqueta que acompaña a la opción, el grupo al que pertenece y su estado. Sólo uno de estos objetos Checkbox puede crearse como activado, ya que pertenecen todos al mismo objeto CheckboxGroup.

(18)

Figura 51

ScrollPane

El último de los componentes que vamos a tratar del AWT es la clase ScrollPane. Esta clase hereda de Container, y por lo tanto es un tipo de contenedor, y como todo contenedor se va a utilizar para albergar otros componentes. La particularidad que ofrece este tipo de contenedor es que muestra barras de desplazamiento horizontal y vertical que nos permiten desplazar el contenido de este contenedor.

Vamos a crear un objeto de la clase ScrollPane en nuestra ventana, para que contenga a su vez un objeto de la clase Panel que contiene cuatro botones en una fila, de esta forma podremos ver las barras de desplazamiento horizontal. El código que nos permite hacer esto es el Código fuente 94.

public void añadePanelScroll(){

ScrollPane panel=new ScrollPane(ScrollPane.SCROLLBARS_AS_NEEDED); Panel otroPanel=new Panel();

otroPanel.setLayout(new GridLayout(1,4)); otroPanel.add(new Button("botón 1")); otroPanel.add(new Button("botón 2")); otroPanel.add(new Button("botón 3")); otroPanel.add(new Button("botón 4")); panel.add(otroPanel);

add(panel); validate(); }

Código fuente 94

El constructor de la clase ScrollPane recibe como argumento una constante de la clase ScrollPane que indica cuando se van a mostrar las barras de scroll o de desplazamiento, en este caso indicamos que se muestren sólo cuando sea necesario. El resto del código es bastante claro, creamos un panel que va a contener los botones y vanos añadiendo los distintos botones.

Como se puede observar, al crear los botones no los guardamos en ninguna variable, esto lo hacemos así porque no vamos a utilizarlos más, podemos crear así los componentes del AWT a los que después no es necesario hacer referencia.

(19)
(20)
(21)

Interfaces de usuario en Java: gestores

de diseño y eventos

Introducción

A lo largo de este capítulo, vamos a continuar con la creación de interfaces de usuario en Java, pero en este caso no vamos a hablar de componentes como en el capítulo anterior, sino que vamos a tratar dos características del interfaz de usuario en los programas Java, los gestores de diseño y los eventos.

Lo que vamos a ver en este capítulo es aplicable tanto a componentes AWT como a componentes Swing (que los veremos en el siguiente capítulo), aunque muchas de las clases que implementan los gestores de diseño y el tratamiento de eventos se encuentran dentro del paquete JAVA.AWT y del subpaquete java.awt.events.

Los eventos y gestores de diseño específicos de los componentes Swing los veremos en los próximos capítulos en los que se abordará Swing, en éste veremos lo común a los dos tipos de componentes.

Gestores de diseño

Los gestores de diseño (Layout Managers) tienen una gran importancia en el leguaje Java, para poder incorporar controles en una ventana (ya sea de una aplicación o de un applet), es necesario que estos estén contenidos en un gestor de diseño, de otro modo no funcionarán correctamente.

(22)

• Desde el punto de vista del lenguaje, como ya hemos comentado, un gestor de diseño es un

"estilo de visualización", es decir, es el mecanismo que el lenguaje incorpora para conseguir crear aplicaciones que tengan la misma apariencia a través de las diferentes plataformas en las que éstas pueden ejecutarse.

• Desde el punto de vista de la jerarquía de clases de Java, un gestor de diseño es un interfaz, y

deriva directamente de la clase Object, que es la que se encuentra en la raíz de la jerarquía de clases. Sin embargo, es a través de la clase Container como podemos establecer el o los gestores de diseño que la ventana va a utilizar para implementar los componentes que ésta manejará. El gestor de diseño es parte del Container, de hecho, siempre que se crea un Container, se puede especificar con cual o cuales de los gestores de diseño disponibles en Java se quiere trabajar, o dicho de otro modo, qué gestor de diseño se asignará al contenedor.

• Desde el punto de vista operativo, un Gestor de diseño es una propiedad del contenedor, que

puede almacenar cualquier tipo de objeto que derive de la clase Container (normalmente bien controles o bien otros gestores de diseño). El Container actúa de intermediario entre la ventana y los componentes que él almacena pasándoles a estos los mensajes que a él le llegan desde la ventana.

El modo de interactuar con un gestor de diseño es muy simple: basta con especificar con cual de los disponibles queremos trabajar y añadirle componentes. Veamos esto de una forma más detenida.

Puesto que existen diferentes gestores de diseño incluidos en el lenguaje Java y puesto que además podemos construir nuestros propios gestores de diseño, se hace necesario indicar con cual de los disponibles deseamos trabajar. Lo que se hace pasándole al método setLayout() de la clase Container un objeto del tipo deseado de gestor de diseño con el que se desea trabajar. Veamos, por ejemplo, la línea de código que muestra el Código fuente 95.

this.setLayout(new FlowLayout());

Código fuente 95

El operador new devuelve un gestor de diseño del tipo FlowLayout, éste objeto es recogido por el método setLayout() de la clase Container, quien a partir de ahora ya sabe con qué gestor deseamos trabajar.

La clase Container dispone del método add(), que permite añadir componentes. Como ya hemos comentado, a un objeto de la clase Container le podemos añadir bien controles o bien otros gestores de diseño. Para ello algo tan simple como lo que aparece en el Código fuente 96.

this.add( new Button( "Uno" ) );

Código fuente 96

A continuación vamos a comentar los distintos gestores de diseño que podemos encontrar dentro del paquete JAVA.AWT. En los distintos ejemplos se han utilizado botones para poner de manifiesto las diferentes distribuciones que los distintos gestores de diseño hacen del espacio ya que el gestor es invisible.

(23)

FlowLayout

El gestor de diseño FlowLayout, además de ser el gestor de diseño por defecto de los applets (que los trataremos en el capítulo correspondiente), es también el más simple de todos.

Este gestor, distribuye los controles de izquierda a derecha llenando el ancho del contenedor de arriba a abajo. Es decir, cuando acaba con una fila de controles, sigue con la siguiente, actuando con los controles como lo hace un editor de textos con las palabras. Es decir, los controles fluyen (Flow).

Para ver cada uno de los ejemplos de los distintos tipos de gestores de diseño vamos a volver a utilizar la aplicación de ejemplo del capítulo anterior, es decir, nuestra clase Ventana. Implementaremos un método para cada tipo de gestor de diseño. En la Figura 53 se puede ver el ejemplo del gestor Flow-Layout.

Figura 53

Y el método, que será llamado desde el método main() y que permite mostrar la ventana de la Figura 53 es el que se muestra en el Código fuente 97.

public void gestorFlowLayout(){ setLayout( new FlowLayout() ); for( int n = 0; n <= 15; n++ )

add( new Button( Integer.toString(n) ) ); validate();

}

Código fuente 97

Como se puede observar, se utiliza primero el método setLayout() pasándole por parámetro una instancia del gestor de diseño que se va a utilizar en nuestra ventana. En el bucle se van añadiendo botones a la ventana, en este caso se utiliza el método add() de la clase Container, en la versión que ya conocemos, es decir pasándole por parámetro la instancia del componente AWT que queremos añadir. Como se ha puede comprobar, los componentes se van incorporando al contenedor atendiendo al orden en el que los vamos añadiendo.

BorderLayout

(24)

Al igual que con el gestor anterior, vamos a ver en la Figura 54 el aspecto que muestra y a continuación veremos y comentaremos el código de nuestra clase Ventana que nos permite crear este gestor de diseño.

Figura 54

Y el método correspondiente de la clase Ventana es el que muestra el Código fuente 98.

public void gestorBorderLayout(){ setLayout( new BorderLayout() ); add("North",new Button("Norte")); add("South", new Button("Sur"));

add(BorderLayout.EAST,new Button("Este")); add(BorderLayout.WEST,new Button("Oeste")); add("Center",new Button("Centro"));

validate(); }

Código fuente 98

Aquí se puede observar que el método setLayout() se sigue utilizando de la misma forma que para el gestor anterior, pero vemos que el método add() antepone un parámetro al componente que vamos añadir, este parámetro es para indicar la posición del componente dentro del contenedor. También se puede observar que hay dos formas de indicar la posición del componente, o con una cadena indicando la situación (North, Center, South...) o con una constante definida por la clase BorderLayout.

CardLayout

Este tercer gestor permite manejar varias "fichas intercambiables", de forma tal que sólo una esté visible a la vez y ocupando todo el área del contenedor. Es similar a los controles de pestaña o "tabs" de Windows.

El problema que este gestor de diseño tiene es que la implementación que en Java se ha hecho de él, no es completa, y las fichas (o tarjetas si se prefiere) no tienen la correspondiente pestaña asociada, por lo que no existe una forma preestablecida de interactuar con los controles para cambiar de unas fichas a otras; siendo necesario crear este mecanismo programáticamente con los inconvenientes de pérdida de tiempo y esfuerzo que esto supone. En el capítulo siguiente veremos que los componentes Swing ofrecen una clase llamada JTabbedPane que representa un componente en el que se pueden elegir las distintas fichas (paneles) a través de los paneles que presenta.

(25)

Figura 55

Y el código correspondiente se puede ver en el Código fuente 99.

public void gestorCardLayout(){

CardLayout gestor=new CardLayout(); setLayout(gestor);

add("1",new Button("Card 1")); add("2",new Button("Card 2")); add("3",new Button("Card 3")); add("4",new Button("Card 4")); add("5",new Button("Card 5")); validate();

}

Código fuente 99

El lector puede que esté un poco confundido, debido a que en el código se añaden cinco botones, pero sin embargo al ejecutar la aplicación sólo podemos ver el primero, esto es así porque las demás fichas con los botones correspondientes se encuentran debajo de la primera. La clase CardLayout ofrece una serie de métodos para mostrar las distintas fichas que conforman el gestor de diseño. Estos métodos son:

• first(Container): nos movemos a la primera ficha. A este método le pasamos por parámetro el

contenedor sobre el que se ha aplicado el gestor de diseño. En el ejemplo anterior deberíamos añadir la siguiente línea: gestor.first(this).

• next(Container): nos movemos a la siguiente ficha, si estamos en la última, volveremos a la

primera, es decir, la disposición del orden de las fichas es circular. Al igual que en el método anterior, debemos pasar por parámetro el contenedor que al que se le ha aplicado el gestor de diseño.

• previous(Container): nos movemos a la ficha anterior, si estamos en la primera ficha, nos

moveremos a la última.

• last(Container): nos movemos a la última ficha.

• show(Container, String): mostramos una ficha específica, la ficha se indica mediante un

(26)

Si observamos el método gestorCardLayout(), comprobamos que el gestor de diseño se ha asignado de una forma un poco diferente, en este caso se ha creado un variable llamada gestor que va a contener la referencia al gestor de diseño de la clase CardLayout. Esto se ha hecho así para poder tener una referencia del gestor de diseño sobre la que podemos lanzar los métodos de la clase CardLayout vistos anteriormente.

Los componentes se añaden a las fichas utilizando una versión del método add() que tiene como primer parámetro el objeto String que identifica la ficha a la que se va añadir el elemento, según se van añadiendo los diferentes componentes se van añadiendo fichas al gestor de diseño, no es obligatorio especificar el nombre que va a identificar a la ficha, es decir podemos utilizar al método add() con un único parámetro que va a ser el componente a añadir.

GridLayout

Este gestor permite distribuir componentes basándose en una rejilla (grid) bidimensional, haciendo que cada componente utilice una celda de la rejilla.

Para este gestor de diseño debemos indicar el número de filas y columnas que va a presentar. Sin embargo, los componentes que se añaden al gestor no pueden especificarse bidimensionalmente, sino que es obligatorio añadirlos ordinalmente, es decir, primero el primero, después el segundo en la rejilla, y así sucesivamente.

Veamos la figura que muestra este gestor de diseño (Figura 56).

Figura 56

Como se puede apreciar en el ejemplo aquí expuesto, el flujo de los componentes ha ido de izquierda a derecha y de arriba a abajo hasta completarse la rejilla (grid). Y el código del método correspondiente se puede ver en el Código fuente 100.

(27)

validate(); }

Código fuente 100

El constructor de la clase GridLayout es diferente del resto de gestores, se debe especificar dos parámetros que indican el número de filas y columnas, respectivamente, que va a presentar el gestor de diseño.

GridBagLayout

El quinto gestor de diseño es el más potente, y es también el más complicado de manejar.

Esta clase, trabaja en conjunción con otra llamada GridBagConstraints, que se encarga de almacenar todos los parámetros de visualización necesarios para que los componentes puedan presentarse de una forma versátil y flexible.

Quizás una de las mayores ventajas que incorpora este gestor es la posibilidad de que un componente abarque más de una celda, lo que le da una gran potencia. Pero esta es sólo una de las muchas ventajas que este gestor incorpora.

En la Figura 57 se puede ver el aspecto de un gestor de diseño GridBagLayout.

Figura 57

Y como viene siendo ya costumbre, se muestra el código del método correspondiente, en el Código fuente 101.

public void gestorGridBagLayout(){

GridBagLayout gestor=new GridBagLayout();

GridBagConstraints constantes=new GridBagConstraints(); Button boton;

setLayout(gestor);

//primer botón en la posición (0,0) (columna,fila) //se extiende hasta la última fila

boton=new Button("Primero"); constantes.gridwidth=1;

constantes.gridheight=GridBagConstraints.REMAINDER; constantes.fill=GridBagConstraints.BOTH;

constantes.gridx=0; constantes.gridy=0;

(28)

//segundo botón en la posición (1,0) boton=new Button("Segundo"); constantes.gridx= GridBagConstraints.RELATIVE; constantes.gridheight=1; gestor.setConstraints(boton,constantes); add(boton);

//tercer botón en la posición (2,0), se extiende dos columnas boton = new Button("Tercero");

constantes.gridx=2; constantes.gridwidth=2;

gestor.setConstraints(boton,constantes); add(boton);

//cuarto botón en la posición (4,0) boton=new Button("Cuarto");

constantes.gridx=4; constantes.gridwidth=1

gestor.setConstraints(boton,constantes); add(boton);

//quinto botón en la posición (5,0), se extiende 3 filas boton = new Button("Quinto");

constantes.gridx=5; constantes.gridheight=3;

gestor.setConstraints(boton,constantes); add(boton);

//sexto botón, en la posición (1,1), se extiende 4 columnas boton=new Button("Sexto"); constantes.gridx=1; constantes.gridy++; constantes.gridheight=1; constantes.gridwidth=4; gestor.setConstraints(boton,constantes); add(boton);

//séptimo botón en la posición (1,2) boton=new Button("Séptimo"); constantes.gridy++; constantes.gridx=1; constantes.gridwidth=1; gestor.setConstraints(boton,constantes); add(boton);

//octavo botón situado en la posición (2,2) se extiende 3 columnas boton = new Button("Octavo");

constantes.gridx=2; constantes.gridwidth=3;

gestor.setConstraints(boton, constantes ); add(boton);

//noveno botón en la posición (1,3) se extiende hasta la última //columna

boton = new Button("Noveno"); constantes.gridx=1; constantes.gridy++; constantes.gridwidth=GridBagConstraints.REMAINDER; gestor.setConstraints(boton,constantes); add(boton); validate(); }

Código fuente 101

Creamos el gestor de diseño GridBagLayout y también un objeto GridBagConstraints, el objeto GirdBagConstraints, que en el código hemos llamado constantes, indica al gestor de diseño GridBagLayout cómo se debe comportar.

(29)

que se va añadir y se le va a aplicar lo definido por el objeto GridBagConstraints, y el propio objeto GridBagConstraints.

El objeto GridBagConstraints lo podemos utilizar para ir añadiendo distintos componentes al contenedor al que se le ha asignado el gestor de diseño, en nuestro ejemplo hemos ido reutilizando y modificando los aspectos que nos convenían en cada caso.

Para definir el comportamiento que debe tener cada control al añadirse al contenedor, la clase GridBagConstraints tiene una serie de atributos que permiten un gran control sobre cómo se sitúa cada componente en el contenedor al que se le ha asignado el gestor de diseño GridBagLayout.

Los atributos más relevantes de la clase GridBagConstraints son los siguientes:

• gridx, gridy: especifica las coordenadas en las que se va a situar el componente, de esta forma

la primera columna sería gridx=0 y la primera fila gridy=0. Para indicar que el componente se va a situar a la derecha (para gridx) o debajo (para gridy) del último componente que se ha añadido al contenedor utilizaremos la constante RELATIVE de la clase GridBagConstraints, además, este es el valor por defecto.

• gridwidth, gridheight: a través de estos dos atributos indicamos cuantas columnas y cuantas

filas, respectivamente, va a ocupar el componente. El valor por defecto en ambos casos es uno. Para especificar que el componente se extienda hasta la última columna (para gridwidth) o hasta la última fila (para gridheight) se utiliza la constante REMAINDER de la clase GridBagConstraints.

• fill: cuando el área en la que se va a mostrar el componente es mayor que el tamaño

especificado del componente, podemos determinar mediante este atributo si se va a redimensionar el componente. Este atributo acepta como valores válidos varias constantes de la clase GridBagConstraints. Por defecto el valor de esta propiedad es NONE, es decir, no se redimiensionan los componentes, si queremos que se redimensione de forma horizontal o de forma vertical utilizaremos las constantes HORIZONTAL y VERTICAL, respectivamente. Y si queremos que se redimensione el componente de forma completa utilizaremos la constante BOTH.

En el Código fuente 101 se puede observar la utilización de todos estos atributos, para seguir el código se deben tener en cuenta los comentarios y fijarnos en el aspecto que ofrece al final nuestro contenedor.

Con este gestor de diseño finalizamos el apartado dedicado a los mismos, ahora vamos a pasar a un apartado que tiene una gran importancia dentro del tema de la construcción de interfaces de usuario, es el apartado dedicado al tratamiento de eventos en Java.

Veremos como reaccionar ante eventos generados por la interacción del usuario con el interfaz de usuario, como pueden ser pulsaciones de botón, selección de elementos de menú, selección de listas, cambios en cajas de texto, cierre de una ventana, movimientos del ratón, etc.

Tratamiento de eventos en Java

(30)

Como veremos a lo largo de este apartado, el lenguaje Java dentro de su paquete java.awt.event nos ofrece una completa jerarquía de clases cuya función es la de representar los distintos tipos de eventos que podemos tratar desde un interfaz de usuario.

El modelo de eventos que ofrece Java es denominado modelo de delegación de eventos (Delegation Event Model). Los diferentes tipos de eventos se encuentran encapsulados dentro de una jerarquía de clases que tienen como raíz a la clase java.util.EventObject.

La idea fundamental del modelo de eventos de Java es que un evento se propaga desde un objeto "fuente" (source) hacia un objeto "oyente" (listener) invocando un método en el oyente al que se le pasa como parámetro una instancia de la clase del evento que representa el tipo de evento generado. Este método será el que trate al evento y actúe en consecuencia.

Los eventos son representados por toda una jerarquía de clases de eventos. Esta jerarquía de eventos se muestra en la Figura 58, cada una de las líneas significa que la clase que se encuentra en la parte inferior de la línea hereda de la clase que se encuentra en la parte superior. Se debe aclarar que las clases en las que no aparece el nombre de su paquete pertenecen todas al paquete dedicado a los eventos, java.awt.event.

Figura 58

Como ya hemos adelantado, en el tratamiento de eventos dentro del lenguaje Java hay dos entidades bien diferenciadas y que son las necesarias para que se produzca el tratamiento de eventos en Java: la fuente del evento y el oyente del mismo. La fuente va a ser el componente que genere el evento y el oyente el componente o clase que lo trate.

(31)

Las características que debe tener un oyente es que debe implementar el interfaz que se corresponda con el evento que desea tratar y debe registrarse como oyente de la fuente de eventos que va a disparar los eventos a tratar. Cualquier tipo de objeto se puede registrar como un "oyente" de eventos. Estos oyentes sólo reciben notificaciones de los eventos en los que están interesados, es decir, los eventos que provienen de la fuente para cual se han registrado.

Una fuente de eventos es un objeto que origina o "dispara" eventos. La fuente define los eventos que van a se emitidos ofreciendo una serie de métodos de la forma add<Tipo de Evento>(), que son usados para registrar oyentes específicos para esos eventos. A los métodos add<Tipo de Evento>() se les pasa por parámetro una instancia del oyente que va a tratar los eventos que genere la fuente.

Todos estos conceptos que estamos introduciendo acerca del tratamiento de eventos en Java, se verá mucho más claro cuando se muestren algunos ejemplos más adelante.

Así por ejemplo, si tenemos el siguiente GUI: un botón y una caja de texto dentro de una ventana, y queremos que al pulsar el botón aparezca un mensaje en la caja de texto, el oyente será la ventana que contiene al botón y a la caja de texto, y la fuente será el botón que lanzará el evento correspondiente.

Este modelo de eventos ofrece las siguientes características:

• Ofrece un estructura robusta para soportar programas Java complejos.

• Es simple y fácil de aprender.

• Ofrece una clara separación entre el código de la aplicación y del interfaz de usuario, en lo que

al tratamiento de eventos se refiere.

• Facilita la creación de un código de tratamiento de eventos robusto y menos propenso a

errores.

Un oyente en lugar de implementar un interfaz, puede utilizar una clase que herede de una clase adaptadora de eventos del paquete java.awt.event. Las clases adaptadoras permiten sobrescribir solamente los métodos del interfaz en los que se esté interesado. Así por ejemplo, si queremos atrapar un click del ratón, en lugar de implementar el interfaz MouseListener, heredamos de la clase adaptadora de los eventos de ratón, es decir de la clase MouseAdapter, solamente deberemos sobrescribir el método mouseClicked().

Esto es posible debido a que las clases adaptadoras implementan el interfaz correspondiente y simplemente tienen implementaciones vacías de todos los métodos del interfaz EventListener. De esta forma se consigue un código más claro y limpio. Estas clases se suelen utilizar cuando se quiere hacer uso de un interfaz muy complejo del que sólo interesan un par de métodos.

Dentro de la jerarquía de clases de Java hay una serie de clases para representar todos los eventos y otra serie de interfaces que definen una serie de métodos que deben implementar las clases que van a tratar los eventos, es decir, lo que hemos llamado oyentes. Otras clases que también hemos comentado y que se utilizan dentro del tratamiento de eventos en Java son las clases adaptadoras. Las clases adaptadoras las utilizaremos para simplificar nuestro código, ya que implementan de forma vacía todos los métodos de un interfaz de tratamiento de eventos determinado.

(32)

Cada componente del AWT genera un tipo de eventos distinto, que está representado mediante una clase en el paquete java.awt.event.

En la Tabla 21 y en la Tabla 22 se indica que eventos lanza cada clase perteneciente a los componentes del AWT. En las filas aparecen las clases de los componentes y en las columnas las clases de los eventos que pueden lanzar.

ActionEvent Adjustment Event

Component Event

Container-Event

FocusEvent

Button X X X

Canvas X X

Checkbox X X

CheckBoxMenu-Item

Choice X X

Component X X

Container X X X

Dialog X X X

Frame X X X

Label X X

List X X X

MenuItem X

Panel X X X

Scrollbar X X X

Scrollpane X X X

TextArea X X

TextComponent X X

TextField X X X

Window X X X

(33)

ItemEvent KeyEvent MouseEvent TextEvent WindowsEvent

Button X X

Canvas X X

Checkbox X X X

CheckBoxMenu-Item

X

Choice X X X

Component X X

Container X X

Dialog X X X

Frame X X X

Label X X

List X X X

MenuItem

Panel X X

Scrollbar X X

Scrollpane X X

TextArea X X X

TextComponent X X X

TextField X X X

Window X X X

Tabla 22

Los interfaces que definen los métodos de cada evento van a ser utilizados por los oyentes, o bien implementándolos directamente o bien a través de las clases adaptadoras. Y las clases de los eventos se van a utilizar para obtener información del evento que se ha producido, el cual se pasará como parámetro a los métodos del interfaz correspondiente.

(34)

Interfaz Métodos Clase Adaptadora

ActionListener actionPerformed(ActionEvent) --- AdjustmentListener adjustmentValueChanged(AdjustmentEvent) --- ComponentListener componentHidden(ComponentEvent)

componentMoved(ComponentEvent) componentResized(ComponentEvent) componentShown(ComponentEvent)

ComponentAdapter

ContainerListener componentAdded(ContainerEvent) componentRemoved(ContainerEvent)

ContainerAdapter

FocusListener focusGained(FocusEvent) focusLost(FocusEvent) FocusAdapter

ItemListener ItemStateChanged(ItemEvent) --- KeyListener KeyPressed(KeyEvent) keyReleased(KeyEvent)

keyTyped(KeyEvent)

KeyAdapter

MouseListener mouseClicked(MouseEvent) mouseEntered(MouseEvent) mouseExited(MouseEvent) mousePressed(MouseEvent) mouseReleased(MouseEvent)

MouseAdapter

MouseMotionListener mouseDragged(MouseEvent) mouseMoved(MouseEvent)

MouseMotionAdapter

TextListener textValueChanged(TextEvent) --- WindowListener windowActivated(WindowEvent)

windowClosed(WindowEvent) windowClosing(WindowEvent) windowDeactivated(WindowEvent) windowDeiconified(WindowEvent) windowIconified(WindowEvent) windowOpened(WindowEvent)

WindowAdapter

Tabla 23

Como se puede apreciar en la tabla anterior, los interfaces que únicamente poseen un método no tienen clase adaptadora correspondiente, ya que no tiene ningún sentido, siempre que utilizamos un interfaz con un único método vamos a implementarlo.

A continuación vamos a comentar los pasos genéricos que se deben seguir a la hora de realizar el tratamiento de eventos en Java. Aunque los pasos son genéricos, para poder hacer referencia a un interfaz concreto vamos a suponer que queremos realizar el tratamiento de eventos que se corresponde con la pulsación de un botón. Lo primero es importar el paquete java.awt.event:

import java.awt.event.*;

(35)

A continuación escribiremos la declaración de la clase para que implemente el interfaz adecuado (listener interface).

Por ejemplo si se está tratando de atrapar un evento ActionEvent (es decir, una pulsación de un botón) generado por un botón, será necesario implementar el interfaz ActionListener.

public class MiClase extends ClasePadre implements ActionListener{

Código fuente 103

Debemos determinar que componentes van a generar los eventos. Se registra cada uno de ellos con el tipo adecuado de oyente, si tenemos en cuenta el ejemplo anterior del botón, se debería escribir lo que indica el Código fuente 104.

objetoBoton.addActionListener(this);

Código fuente 104

Una vez hecho esto debemos crear las implementaciones de todos los métodos del interfaz que la clase debe implementar.

public void actionPerformed(ActionEvent evento){

//cuerpo del método

}

Código fuente 105

Una vez comentado el tratamiento de eventos en Java de forma más o menos teórica vamos a comentar una serie de ejemplos para aplicar la teoría a la práctica. Estos ejemplos además nos van a servir para repasar distintos puntos del lenguaje Java comentados hasta ahora.

Vamos a realizar una aplicación que vamos a ir modificando y complicando para mostrar las diferentes facetas del tratamiento de eventos en Java.

No vamos a tratar todos los interfaces, sólo vamos a tratar tres de los más representativos, el que se encarga de las pulsaciones de botones, ActionListener, el que se encarga del ratón, MouseListener, y el que se encarga de las ventanas, WindowListener, estos tres nos ofrecen ejemplos bastante prácticos. También veremos los adaptadores MouseAdapter y WindowAdapter.

Primero vamos a comenzar creando una aplicación sencilla que va a consistir simplemente en una ventana que nos va a indicar si se encuentra minimizada o no, y cuando pulsemos sobre el aspa de cerrar la ventana esta se cierre y finalice la ejecución de la aplicación.

(36)

Figura 59

El código de esta aplicación de ejemplo es el que aparece en el Código fuente 106.

import java.awt.*;

//paquete necesario para el tratamiento de eventos import java.awt.event.*;

public class Ventana extends Frame implements WindowListener{ //constructor de nuestra clase

public Ventana(String titulo){ //constructor de la clase padre super(titulo);

//tamaño de la ventana setSize(150,150); //se muestra la ventana show();

//se registra nuestra clase como oyente addWindowListener(this);

}

public void windowClosed(WindowEvent evento){ System.out.println("La ventana se ha cerrado"); //finaliza la aplicación

System.exit(0); }

public void windowIconified(WindowEvent evento){ System.out.println("Estoy minimizada"); }

public void windowDeiconified(WindowEvent evento){ System.out.println("No estoy minimizada"); }

public void windowClosing(WindowEvent evento){ //se cierra la ventana

dispose(); }

public void windowDeactivated(WindowEvent evento){} public void windowActivated(WindowEvent evento){} public void windowOpened(WindowEvent evento){} public static void main (String[] args){

Ventana miVentana=new Ventana("Eventos"); }

}

Código fuente 106

(37)

main() de arranque, y como hemos dicho que es una ventana, hereda de la clase Frame. Como se puede observar hemos creado también un método constructor para nuestra clase Ventana.

En lo concerniente al tratamiento de eventos, como se puede ver importamos el paquete correspondiente e indicamos que nuestra clase implementa el interfaz WindowListener. En el constructor de nuestra clase indicamos quien va a ser el oyente de nuestra clase, es decir, quien va a tratar los eventos, en este caso el oyente es nuestra misma clase. Para ello utilizamos la línea de código que aparece en el Código fuente 107.

addWindowListener(this);

Código fuente 107

Es decir, indicamos que el oyente de los eventos de la ventana va a ser nuestra propia clase, es decir, estamos registrando nuestra clase como oyente mediante la referencia this. Al ser nuestra clase fuente y oyente de los eventos, también debe implementar el interfaz que define los métodos que se van a ejecutar atendiendo al evento que se produzca. En nuestro caso hay tres métodos que no nos interesan, pero como implementamos el interfaz WindowListener debemos implementarlos aunque no tengan contenido.

Los métodos windowIconified() y windowDeiconified() se ejecutarán cuando se minimice la ventana y cuando se restaure, respectivamente, el método windowClosing() se ejecuta en el momento de cerrar la ventana y el método windowClosed() cuando ya se ha cerrado.

El resto del código es bastante sencillo y considero que no necesita una mayor explicación.

Como ya hemos comentado hay tres métodos del interfaz WindowListener que no nos interesan y que por lo tanto nos gustaría suprimir, en este momento entran en juego las clases adaptadoras. Como ya debe saber el alumno, las clases adaptadoras realizan una implementación vacía de todos los métodos del interfaz con el que se corresponden, y al heredar de ellas utilizaremos únicamente los métodos del interfaz correspondiente que nos interese.

En nuestro caso no podemos heredar de una clase adaptadora, ya que heredamos de la clase Frame y la herencia múltiple no se permite en Java. Por lo tanto deberemos crear y definir una nueva clase que herede de la clase adaptadora del interfaz WindowListener, es decir, que herede de la clase WindowAdapter.

Por lo tanto si queremos hacer uso de la clase adaptadora WindowAdapter, el código de nuestra aplicación se debe modificar como indica el Código fuente 108.

import java.awt.*; import java.awt.event.*;

//nuestra clase ya no implementa el interfaz WindowListener public class Ventana extends Frame{

public Ventana(String titulo){ super(titulo);

setSize(150,150); show();

//se registra como oyente la clase que hereda la clase adaptadora addWindowListener(new AdaptadorVentana(this));

(38)

public static void main (String[] args){ Ventana miVentana=new Ventana("Eventos"); }

}

//clase que hereda de la clase adaptadora class AdaptadorVentana extends WindowAdapter{

//atributo que se utiliza como referencia a la clase //que es la fuente del evento que queremos tratar private Ventana fuente;

//constructor de nuestra clase adaptadora

//recibe como parámetro la clase fuente del evento public AdaptadorVentana(Ventana fuente){

this.fuente=fuente; }

//contiene la implementación de los métodos que nos intersan //del interfaz WindowListener

public void windowClosed(WindowEvent evento){

System.out.println("La ventana se ha cerrado"); System.exit(0);

}

public void windowIconified(WindowEvent evento){ System.out.println("Estoy minimizada"); }

public void windowDeiconified(WindowEvent evento){ System.out.println("No estoy minimizada"); }

public void windowClosing(WindowEvent evento){ fuente.dispose();

} }

Código fuente 108

Los mayores cambios se aprecian en que existe una nueva clase que va a ser la encargada de tratar los eventos, en este caso si que existe una separación clara entre clase fuente y clase oyente. El oyente sería la clase AdaptadorVentana y la clase fuente sería la clase principal Ventana. Nuestra clase Ventana, al ser sólo fuente de eventos no va a implementar el interfaz WindowListener y por lo tanto únicamente va a tener en su cuerpo el método constructor y el método main().

Otra cosa que cambia también en el código de nuestra aplicación es la forma en la que se registra el oyente. Se sigue utilizando el método addWindowListener() pero en este caso se pasa por parámetro una instancia de la clase adaptadora que hemos creado nosotros.

addWindowListener(new AdaptadorVentana(this));

Código fuente 109

Ahora vamos a detenernos en la clase AdaptadorVentana. Esta clase hereda de la clase WindowAdapter y posee un atributo denominado fuente que es de la clase Ventana.

Referencias

Documento similar

4.- Másteres del ámbito de la Biología Molecular y Biotecnología (9% de los títulos. Destaca el de Biotecnología Molecular de la UB con un 4% y se incluyen otros

Esta corriente dentro de la arquitectura, registra al Diseño como herramienta fundamental para mejorar la sustentabilidad en el hábitat.. Es más abarcativa que la corriente

Pero cuando vio a Mar sacar el fuego de bajo su ala, voló de vuelta a su tribu a contarles lo que había visto.... Justo antes de que el sol saliera, Tatkanna se despertó y comenzó

o esperar la resolución expresa&#34; (artículo 94 de la Ley de procedimiento administrativo). Luego si opta por esperar la resolución expresa, todo queda supeditado a que se

¿Cómo se traduce la incorporación de ésta en la idea de museo?; ¿Es útil un museo si no puede concebirse como un proyecto cultural colectivo?; ¿Cómo puede ayudar el procomún

Volviendo a la jurisprudencia del Tribunal de Justicia, conviene recor- dar que, con el tiempo, este órgano se vio en la necesidad de determinar si los actos de los Estados

Una nueva decisión del Tribunal Europeo de Dere- chos Humanos va a permitir a ese Tribunal con- tinuar por el camino iniciado en el Caso Pretty c. Suiza en dar contenido al derecho

También hemos visto como la principal característica de este proceso de racialización es que se hace presente en los libros de texto de una forma dialéctica, al pretender