3. Interfaz Gráfica

 

3.1. AWT

 

3.1.1. Introducción a AWT

AWT (Abstract Windows Toolkit) es la parte de Java que se emplea para construir interfaces gráficas de usuario. Este paquete ha estado presente desde la primera versión (la 1.0), aunque con la 1.1 sufrió un cambio notable. En la versión 1.2 se incorporó también a Java una librería adicional, Swing, que enriquece a AWT en la construcción de aplicaciones gráficas.
 

Controles de AWT

Java proporciona una serie de controles que podremos colocar en las aplicaciones visuales que implementemos. Dichos controles son subclases de la clase Component, y forman parte del paquete java.awt . Las más comunes son:

Figura 1. Estructura de clases de AWT

Los controles sólo se verán si los añadimos sobre un contenedor. Para ello utilizamos el método add(...) del contenedor para añadir el control. Por ejemplo, si queremos añadir un botón a un Panel:

Button boton = new Button("Pulsame");
Panel panel = new Panel();
...
panel.add(boton);
Component La clase padre Component no se puede utilizar directamente. Es una clase abstracta, que proporciona algunos métodos útiles para sus subclases. 
Botones
Para emplear la clase Button, en el constructor simplemente indicamos el texto que queremos que tenga : 
Button boton = new Button("Pulsame");
Etiquetas
Para utilizar Label, el uso es muy similar al botón: se crea el objeto con el texto que queremos darle: 
Label etiq = new Label("Etiqueta");
Areas de dibujo La clase Canvas se emplea para heredar de ella y crear componentes personalizados. 
Accediendo al objeto Graphics de los elementos podremos darle la apariencia que queramos: dibujar líneas, pegar imágenes, etc:
Panel p = new Panel();
p.getGraphics().drawLine(0, 0, 100, 100);
p.getGraphics().drawImage(...);
Casillas de verificación
Checkbox se emplea para marcar o desmarcar opciones. Podremos tener controles aislados, o grupos de Checkboxes en un objeto CheckboxGroup, de forma que sólo una de las casillas del grupo pueda  marcarse cada vez. 
Checkbox cb = new Checkbox
              ("Mostrar subdirectorios", 
              false);
System.out.println ("Esta marcada: " + 
                    cb.getState());
Listas

Para utilizar una lista desplegable (objeto Choice ), se crea el objeto y se añaden, con el método addItem(...) , los elementos que queramos a la lista: 
Choice ch = new Choice();
ch.addItem("Opcion 1");
ch.addItem("Opcion 2");
...
int i = ch.getSelectedIndex();
Para utilizar listas fijas (objeto List), en el constructor indicamos cuántos elementos son visibles. También podemos indicar si se permite seleccionar varios elementos a la vez. Dispone de muchos de los métodos que tiene Choice para añadir y consultar elementos. 
List lst = new List(3, true);
lst.addItem("Opcion 1");
lst.addItem("Opcion 2");
Cuadros de texto
Al trabajar con TextField o TextArea, se indica opcionalmente en el constructor el número de columnas (y filas en el caso de TextArea) que se quieren en el cuadro de texto. 
TextField tf = new TextField(30);
TextArea ta = new TextArea(5, 40);
...
tf.setText("Hola");
ta.appendText("Texto 2");
String texto = ta.getText();
Menús
Para utilizar menús, se emplea la clase MenuBar (para definir la barra de menú), Menu (para definir cada menú), y MenuItem (para cada opción en un menú). Un menú podrá contener a su vez submenús (objetos de tipo Menu ). También está la clase CheckboxMenuItem para definir opciones de menú que son casillas que se marcan o desmarcan. 
MenuBar mb = new MenuBar();
Menu m1 = new Menu "Menu 1");
Menu m11 = new Menu ("Menu 1.1");
Menu m2 = new Menu ("Menu 2");
MenuItem mi1 = new MenuItem ("Item 1.1");
MenuItem mi11=new MenuItem ("Item 1.1.1");
CheckboxMenuItem mi2 = 
   new CheckboxMenuItem("Item 2.1");
mb.add(m1);
mb.add(m2);
m1.add(mi1);
m1.add(m11);
m11.add(mi11);
m2.add(mi2);
Mediante el método setMenuBar(...) de Frame podremos añadir un menú a una ventana:
Frame f = new Frame();
f.setMenuBar(mb);

 

3.1.2. Gestores de disposición

Para colocar los controles Java en los contenedores se hace uso de un determinado gestor de disposición. Dicho gestor indica cómo se colocarán los controles en el contenedor, siguiendo una determinada distribución. Para establecer qué gestor queremos, se emplea el método setLayout(...) del contenedor. Por ejemplo:

Panel panel = new Panel();
panel.setLayout(new BorderLayout());
Veremos ahora los gestores más importantes:
BorderLayout

(gestor por defecto para contenedores tipo Window)
Divide el área del contenedor en 5 zonas: Norte ( NORTH ), Sur (SOUTH), Este (EAST), Oeste (WEST) y Centro (CENTER), de forma que al colocar los componentes deberemos indicar en el método add(...) en qué zona colocarlo: 
panel.setLayout(new BorderLayout());
Button btn = new Button("Pulsame");
panel.add(btn, BorderLayout.SOUTH);
Al colocar un componente en una zona, se colocará sobre el que existiera anteriormente en dicha zona (lo tapa). 
FlowLayout

(gestor por defecto para contenedores de tipo Panel)
Con este gestor, se colocan los componentes en fila, uno detrás de otro, con el tamaño preferido (preferredSize ) que se les haya dado. Si no caben en una fila, se utilizan varias. 
panel.setLayout(new FlowLayout());
panel.add(new Button("Pulsame"));
GridLayout
Este gestor sitúa los componentes en forma de tabla, dividiendo el espacio del contenedor en celdas del mismo tamaño, de forma que el componente ocupa todo el tamaño de la celda. 
Se indica en el constructor el número de filas y de columnas. Luego, al colocarlo, va por orden (rellenando filas de izquierda a derecha). 
panel.setLayout(new GridLayout(2,2));
panel.add(new Button("Pulsame"));
panel.add(new Label("Etiqueta"));
Sin gestor Si especificamos un gestor null, podremos colocar a mano los componentes en el contenedor, con métodos como setBounds(...) , o setLocation(...)
panel.setLayout(null);
Button btn = new Button ("Pulsame");
btn.setBounds(0, 0, 100, 30);
panel.add(btn);

Ejemplo: Vemos el aspecto de algunos componentes de AWT, y el uso de gestores de disposición en este ejemplo:

Código
 

3.1.3. Modelo de Eventos en Java

Entendemos por evento una acción o cambio en una aplicación que permite que dicha aplicación produzca una respuesta.  El modelo de eventos de AWT se descompone en dos grupos de elementos: las fuentes y los oyentes de eventos. Las fuentes son los elementos que generan los eventos (un botón, un cuadro de texto, etc), mientras que los oyentes son elementos que están a la espera de que se produzca(n) determinado(s) tipo(s) de evento(s) para emitir determinada(s) respuesta(s).

Para poder gestionar eventos, necesitamos definir el manejador de eventos correspondiente, un elemento que actúe de oyente sobre las fuentes de eventos que necesitemos considerar. Cada tipo de evento tiene asignada una interfaz, de modo que para poder gestionar dicho evento, el manejador deberá implementar la interfaz asociada. Los oyentes más comunes son:

ActionListener
Para eventos de acción (pulsar un Button , por ejemplo)
ItemListener
Cuando un elemento (Checkbox, Choice , etc), cambia su estado
KeyListener
Indican una acción sobre el teclado: pulsar una tecla, soltarla, etc.
MouseListener
Indican una acción con el ratón que no implique movimiento del mismo: hacer click, presionar un botón, soltarlo, entrar / salir...
MouseMotionListener
Indican una acción con el ratón relacionada con su movimiento: moverlo por una zona determinada, o arrastrar el ratón.
WindowListener
Indican el estado de una ventana

Cada uno de estos tipos de evento puede ser producido por diferentes fuentes. 

 

Modos de definir un oyente

Supongamos que queremos realizar una acción determinada al pulsar un botón. En este caso, tenemos que asociar un ActionListener a un objeto Button. Veremos cómo podemos hacerlo:

1. Que la propia clase que usa el control implemente el oyente

class MiClase implements ActionListener
{
    public MiClase()
    {
           ...
        Button btn = new Button("Boton");
        btn.addActionListener(this);       
           ...
    }
   
    public void actionPerformed(ActionEvent e)
    {
        // Aqui va el codigo de la accion
    }
}
2. Definir otra clase aparte que implemente el oyente
class MiClase
{
     public MiClase()
     {
        ...
        Button btn = new Button("Boton");
        btn.addActionListener(new MiOyente());
        ...
     }
}

class MiOyente implements ActionListener
{
     public void actionPerformed(ActionEvent e)
     {
         // Aqui va el codigo de la accion
     }
}
3. Definir una instancia interna del oyente
class MiClase
{
     public MiClase()
     {
         ...
         Button btn = new Button("Boton");
         btn.addActionListener(new ActionListener()
         {
            public void actionPerformed(ActionEvent e)
            {
                // Aqui va el codigo de la accion
            }
         });
         ...
    }
}
 

Uso de los "adapters"

Algunos de los oyentes disponibles (como por ejemplo MouseListener ) tienen varios métodos que hay que implementar si queremos definir el oyente. Este trabajo puede ser bastante pesado e innecesario si sólo queremos usar algunos métodos.

Una solución a esto es el uso de los adapters. Asociado a cada oyente con más de un método hay una clase ...Adapter (para MouseListener está MouseAdapter , para WindowListener está WindowAdapter, etc). Estas clases implementan las interfaces con las que se asocian, de forma que se tienen los métodos implementados por defecto, y sólo tendremos que sobreescribir los que queramos modificar.

Veamos la diferencia con el caso de MouseListener, suponiendo que queremos asociar un evento de ratón a un Panel para que haga algo al hacer click sobre él.

1. Mediante Listener:

class MiClase 
{
     public MiClase()
     {
        ...
        Panel panel = new Panel();
        panel.addMouseListener(new MouseListener()
        {
           public void mouseClicked(MouseEvent e)
           {
               // Aqui va el codigo de la accion
           }
    
           public void mouseEntered(MouseEvent e)
           {
               // ... No se necesita
           }
   
           public void mouseExited(MouseEvent e)
           {
               // ... No se necesita
           }
    
           public void mousePressed(MouseEvent e)
           {
               // ... No se necesita
           }

           public void mouseReleased(MouseEvent e)
           {
               // ... No se necesita
           }       
        });        
        ...
    }
}
Vemos que hay que definir todos los métodos, aunque muchos queden vacíos porque no se necesitan.

2. Mediante Adapter:

class MiClase 
{
     public MiClase()
     {
         ...
         Panel panel = new Panel();
         panel.addMouseListener(new MouseAdapter()
         {
            public void mouseClicked(MouseEvent e)
            {
                // Aqui va el codigo de la accion
            }       
         });        
         ...
     }
}
Vemos que aquí sólo se añaden los métodos necesarios, el resto ya están implementados en MouseAdapter (o en el adapter que corresponda), y no hace falta ponerlos.

Ejemplo: Vemos el uso de oyentes en este ejemplo: Código