Interfaces Gráficas

Java fue concebido de modo que las aplicaciones posean interfaces gráficas con el usuario, y no interfaces de diálogo como las que se han visto en este curso. Para construir interfaces gráficas, Java posee una biblioteca estándar denominada Abstract Window Toolkit (AWT).

Definición: Un widget es un elemento gráfico con el que el usuario puede interactuar. Un widget ocupa una porción rectangular en una ventana. Ejemplos de widgets son: botones, áreas de texto, etiquetas, etc. El kit AWT implementa los widgets más usuales. Sin embargo, las aplicaciones avanzadas requieren de widgets más sofisticados. En estos casos, Java ofrece Swing que por razones de tiempo y de complejidad, no podemos ver en este curso.

Para usar los widget de la AWT es necesario importar las clases del paquete java.awt:

    import java.awt.*;

Algunos Widgets de la AWT

La siguiente figura muestra los widgets más importantes de la AWT.

En la ventana (clase Frame) se puede apreciar en el centro un canvas (clase Canvas). Este widget representa una tela de pintar y se usa cuando ninguno de los widgets ofrecidos por la AWT cumple los requerimientos de la aplicación. A la izquierda se muestra un área de texto (TextArea), a la derecha una lista de ítemes (List) y arriba una etiqueta (Label). Abajo se observa un panel que se usa para agrupar widgets y procesarlos como si se tratase de uno solo. El panel consiste en un campo de texto (TextField), un botón (Button) y un botón de encendido y apagado (Checkbox).

Para construir una interfaz gráfica, el programador debe armar una jerarquía de widgets cuya raíz es la ventana de la aplicación. Por ejemplo, la ventana de más arriba posee la siguiente jerarquía:

Observe que:

Jerarquía de clases de AWT

La siguiente figura muestra las relaciones de subclase entre los distintos widgets de la AWT.

En la raíz se encuentra la clase Component. Esto significa que todos los widgets son subclases de Component.

Construcción de una ventana de interacción

La construcción de una interfaz contempla las siguientes etapas: A continuación se detalla cada una de estas etapas.

Creación de widgets

La lista de ítemes:

    List lista = new List(3);
    for (int i = 1; i <= 10; i++) {
      lista.addItem("List item " + i);
    }
El panel de widgets:

    Panel panel= new Panel();

Establecimiento de organizadores

En Java no es conveniente asignar a los widgets posiciones absolutas dentro de la ventana. Para organizar un contenedor se le asigna un layout manager u organizador. Para ello, los contenedores (ventanas, paneles y otros) disponen del método setLayout. Para establecer los organizadores de la ventana y el panel que contempla la interfaz gráfica del ejemplo, se usa:

    frame.setLayout(new BorderLayout());
    panel.setLayout(new FlowLayout());
El layout manager elige la mejor posición y tamaño de cada widget de acuerdo al espacio disponible.

Un ejemplo de layout managers son BorderLayout que organiza un contenedor en 5 zonas:

Otro ejemplo es FlowLayout que coloca los widgets de izquierda a derecha y de arriba hacia abajo:

Colocar los widget en los contenedores

Los widgets se colocan dentro de cada contendedor por medio del método add:

El panel se trata como si fuese un solo widget:

    frame.add("South", panel);

Despliegue de la ventana

La ventana se despliega con:

    frame.pack();
    frame.show();
Estas instrucciones deben ser las últimas que se ejecuten a partir del método run. Todo el resto del proceso se realiza atrapando los eventos que produce el usuario al interactuar con los widgets.

Ambientes de desarrollo rápido de aplicaciones

Un ambiente de desarrollo rápido de aplicaciones (RAD o builders) evita que los programadores tengan que programar todo el código mostrado anteriormente. Con un RAD el programador diseña la interfaz gráfica de su aplicación arrastrando beans hacia la ventana de diseño que provee el RAD. El RAD se encarga de generar automáticamente el código que construye la interfaz.

Un bean es un objeto inspeccionable por el RAD. El RAD provee de una paleta de beans, entre los que se cuentan los widgets vistos anteriormente.

Ejemplos de RAD son JBuilder de Borland (disponible en los PCs de la Facultad), Cafe de Symantec, VisualAge for Java de IBM, Netbeans de Sun y muchos otros.

¡Ops! Faltó la superficie de dibujo

Cuando la paleta de beans no ofrece el widget que se necesita, entonces se fabrica con un Canvas. Para ello se debe definir una subclase de Canvas. En el ejemplo definiremos la clase MiCanvas. El nuevo widget se agrega a la ventana como si fuese un widget cualquiera:

    Canvas canvas= new MiCanvas();
    frame.add("Center", canvas);
Cumpliento algunos requisitos, el nuevo widget se puede agregar a la paleta de beans de cualquier RAD.

La subclase MiCanvas debe definir los métodos paint, getMinimumSize y getPreferredSize:

class MiCanvas extends Canvas {
    public void paint(Graphics g) {
        int w = getSize().width;
        int h = getSize().height;
        g.drawLine(10,10, 100,100);
    }

    public Dimension getMinimumSize() {
        return new Dimension(150,130);
    }

    public Dimension getPreferredSize() {
        return getMinimumSize();
    }
}
(Ver el programa completo en Gui.java.)

El sistema gráfico invoca el método paint de un canvas cuando se necesita redibujarlo parcial o completamente. Esto ocurre cuando el usuario saca una ventana que cubría una parte del canvas.

El layout manager invoca los métodos getMinimumSize y getPreferredSize para obtener hints acerca del mejor tamaño del widget. Se invoca al mostrar en pantalla la ventana que contiene el canvas o cuando el usuario redimensiona la ventana.

La mayoría de los widgets (como botones, etiquetas, texto, etc.) ya tienen redefinidos estos métodos en forma conveniente.


Ejercicio:

Escribir una interfaz gráfica que despliegue una ventana con los siguientes widgets: una etiqueta que diga ``Archivo:'', un campo de texto en donde el usuario pueda ingresar el nombre de un archivo y por último un botón etiquetado con la leyenda ``mostrar''. Solución:

    import tools.*;
    import java.awt.*;

    class Type extends Program {
      // Componentes o widgets
      Frame vent;      // La ventana
      Label etiq;      // una etiqueta que dice "Archivo: "
      TextField campo; // El campo de texto
      Button boton;    // El boton

      void run() {
        // Crear vantana
        vent= new Frame("Type");
        // Establecer una politica de organizacion (layout) de la ventana
        vent.setLayout(new FlowLayout());
        // Crear los widgets
        etiq= new Label("Archivo: ");
        campo= new TextField(15);
        boton= new Button("mostrar");
        // Colocarlos en la ventana
        vent.add(etiq);
        vent.add(campo);
        vent.add(boton);
        // Mostrar la ventana
        vent.pack();
        vent.show();
      }
    }
(Ver el programa completo en Type.java.)

Observación: Este programa todavía no es capaz de reaccionar cuando el usuario presiona el botón mostrar:


Interacción con el usuario: eventos

Cuando un usuario interactúa con las componentes gráficas de una aplicación, gatilla eventos. Por ejemplo cuando el usuario:

En este curso sólo nos preocuparemos de los eventos de acción. Estos eventos se gatillan cuando el usuario realiza la interacción fundamental por la que fue concebido un widget. Por ejemplo cuando el usuario:

Las aplicaciones más simples sólo necesitan capturar los eventos de acción. El resto de los eventos sólo se capturan para adornar la interfaz. Por ejemplo:

Los widgets provistos por la AWT saben capturar y responder la mayoría de de los eventos, con excepción de los eventos de acción porque estos tienen relación con la lógica de la aplicación (a veces se habla del negocio de la aplicación refiriéndose a la funcionalidades que ella entrega).


Captura de eventos de acción

Para capturar eventos de acción, se procede de la siguiente forma. Supongamos que la interfaz gráfica consta de un botón, referenciado por la variable b. Cuando el usuario presiona ese botón se dice que gatilla el evento de acción asociado a ese botón. Supongamos entonces que la aplicación necesita responder a este evento, ejecutando un grupo de instrucciones I.


Patrón de programación de una applicación con interfaz gráfica

    import java.awt.*;       // los widgets
    import java.awt.event.*; // los eventos
    import ...;              // otras bibliotecas

    class Gui extends Program {
      // la ventana
      Frame ventana= new Frame("título de la ventana");
      // todos los widgets
      TextArea area= new TextArea(5, 20);
      ...
      void run() {
        ventana.setLayout(new BorderLayout()); // asigna el layout
        panel.setLayout(new FlowLayout());

        // armamos el panel
        panel.add(texto);
        ...

        // armamos la ventana
        ventana.add("North", etiq);
        ...

        // Inscripcion para los eventos
        lista.addActionListener(new SeleccionLista());
        ...

        ventana.pack();
        ventana.show();
      }

      // clases para los eventos de acción
      ...
    }
(Ver el programa completo en GuiEv.java.)


Ejercicio: Escribir un programa que despliegue una interfaz como la de Type.java pero que además incluya un area de texto en donde mostrar el contenido de un archivo. El programa debe capturar el evento de acción asociado al botón ``mostrar'' mostrando en el area de texto el archivo que aparezca indicado en el campo de texto.

Solución:

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

class ShowFile extends Program {
  // Componentes o widgets
  Frame vent;      // La ventana
  Panel panel;     // Un panel para colocar la etiqueta, el campo y el boton
  Label etiq;      // una etiqueta que dice "Archivo: "
  TextField campo; // Un campo de texto para el ingreso del nombre del archivo
  Button boton;    // Un boton para indicar cuando se debe mostrar el archivo
  Button salir;    // Un boton para terminar la aplicacion
  TextArea texto;  // Un area de texto en donde se mostrara el archivo

  void run() {
    // Crear la ventana
    vent= new Frame("Type");
    // Establecer una politica de organizacion (layout) de la ventana
    vent.setLayout(new BorderLayout());
    // Crear los widgets
    panel= new Panel();
    panel.setLayout(new FlowLayout());
    etiq= new Label("Archivo: ");
    campo= new TextField(15);
    boton= new Button("mostrar");
    salir= new Button("salir");
    texto= new TextArea(5, 15);
    // Colocarlos en el panel y la ventana
    panel.add(etiq);
    panel.add(campo);
    panel.add(boton);
    panel.add(salir);
    vent.add("Center", texto);
    vent.add("South", panel);
    // Agregar oyentes
    ActionListener oyente= new MuestraArchivo(this);
    boton.addActionListener(oyente);
    campo.addActionListener(oyente);
    salir.addActionListener(new Salir());
    // Mostrar la ventana
    vent.pack();
    vent.show();
  }
}

class MuestraArchivo extends Program
                     implements ActionListener {
  ShowFile show;
  MuestraArchivo(ShowFile show) {
    this.show= show;
  }
  public void actionPerformed(ActionEvent e) {
    String nomArch= show.campo.getText();
    show.campo.setText("");
    TextReader lect= new TextReader(nomArch);
    StringBuffer buff= new StringBuffer();
    while(true) {
      String lin= lect.readLine();
      if (lect.eofReached())
        break;
      buff.append(lin);
      buff.append("\n");
    }
    show.texto.setText(buff.toString());
  }
}

class Salir extends Program
                    implements ActionListener {
  public void actionPerformed(ActionEvent e) {
    System.exit(0);
  }
}
(Ver el programa completo en ShowFile.java.)