Programación - RafaC - 2004/2006

Página Principal -


Tema 5: Desarrollo de aplicaciones gráficas


1.- Introducción
2.- Ventanas: la clase Frame
3.- Componentes Básicos
4.- Eventos de teclado y de ratón
5.- Estilos
6.- Dibujando
7.- Applets
8.- Diálogos y mensajes


Apéndice A: Un poquito de Swing




1.- Introducción

1.1 AWT y Swing
1.2 Un poco de filosofía
1.3 La jerarquía Component

1.1 AWT y Swing

Una de las razones del éxito de Java es la posibilidad desde las primeras versiones, de una gran cantidad de paquetes de clases destinadas al diseño de interfaces gráficas. En otros lenguajes (C, C++, etc.), las librerías de componentes gráficos dependían del fabricante del compilador particular que se decidiera utilizar. Esto favorecía la aparición de dialectos del lenguaje y la falta de uniformidad en el modelo propuesto.
La variedad de clases gráficas y sus inmensas posibilidades de diseño hacer que un buen programador en Java no sea sólo aquel que conoce los aspectos básicos del lenguaje y su filosofía; es necesario también un conocimiento detallado de las clases del API (application program interface) de Java y sus posibilidades.
Se pueden distinguir, principalmente, 2 grupos de clases destinadas al diseño de interfaces gráficos en Java:
AWT
Swing
La primera (Abstract Window Toolkit) es la que vamos a estudiar en este curso. El conjunto de clases Swing nació a partir de AWT, simplificando los aspectos más engorrosos de AWT, dando mayor flexibilidad al programador para diseñar sus propios componentes gráficos (gracias all uso de beans) e incorporando numerosos componentes nuevos.
La pregunta que surge al leer esto es:

Si Swing es más sencillo, más flexible y más potente que AWT,
¿por qué no limitarse a estudiar las clases de Swing?


La razón principal es que, a pesar de ser Swing más fácil de manejar está basado en conceptos más complejos, difíciles de entender si primero no se conoce AWT; Swing contiene demasiado de AWT como para, simplemente, ignorarlo. La mayoría de los autores piensa que es mejor seguir al estudiar el lenguaje la evolución que han seguido sus diseñadores. Por eso nos limitaremos aquí al estudio de AWT. Si se comprenden los conceptos de este tema pasar posteriormente a Swing no supondrá ningún problema. Otro buena razón es que al ser AWT más antiguo, los applets de internet escritos en AWT funcionan en muchos más exploradores que los escritos con Swing. Por tanto usando AWT nos aseguramos una mayor compatibilidad.


Observación: Por cada componente en AWT, el correspondiente componente en Swing suele tener el mismo nombre pero precedido de una letra J. Por ejemplo, los botones se representan en AWT mediante objetos de la clase Button y en Swing mediante objetos de la clase JButton, las ventanas mediante las clases Frame y JFrame, y así sucesivamente.

1.2 Un poco de filosofía

El concepto básico de la programación gráfica es el componente. Un componente es cualquier cosa que tenga un tamaño una posición, pueda pintarse en pantalla y pueda recibir eventos (es decir, pueda recibir acciones por parte del usuario). Un ejemplo típico es un botón; el evento más normal que puede recibir es que el usuario de la aplicación pulse el botón. Podemos resumir los conceptos básicos de la programación gráfica con AWT:
Todos los componentes son o bien contenedores (como Frame) o bien componentes básicos (como Button).
Los componentes básicos siempre deben formar parte de un contenedor para ser visibles.
Los contenedores a su vez se pueden insertar en otros contenedores.
La posición de un componente en un contenedor depende del tamaño del contenedor y de su estilo (Layout).
Todo componente incluye un atributo de tipo Graphicsque se encarga de dibujarlo cuando hace falta. Si queremos dibujar en el componente tendremos que pedirle que nos "deje" su objeto de tipo Graphics.
Si queremos que un componente haga algo cuando le ocurra un evento determinado debemos pasarle un objeto tipo Listener al que él avisará llegado el momento.

Las idea clave de esta forma de proceder es la llamada delegación de eventos. En una aplicación gráfica La función main se encargará de crear la ventana principal, donde se dispondrán los componentes y se indicará a cada componente a qué clase debe avisar cuando le suceda un evento. A partir de este momento será el propio lenguaje el que se encargará de avisar a las clases adecuadas de la existencia de un evento como respuesta a las acciones del usuario. Esto significa que se pierde el concepto de ejecución secuencial que hemos visto en los programas con entrada/salida de texto.

1.3 La jerarquía Component

La clase Component es una clase abstracta de la que derivan todos los componentes visuales AWT. Tiene más de 100 métodos de los que vamos a mencionar sólo los más habituales:

Clase Component
MétodoDescripción
String getName() Devuelve el nombre del componente
void setName(String) Para fijar el nombre del componente
Dimension1 getSize() Devuelve el tamaño del componente
void setSize(int ancho, int alto) Para modificar el tamaño del componente
void setSize(Dimension) Análogo al anterior
Color getBackground Devuelve el color de fondo del componente
void setBackground(Color) Fija el color de fondo del componente
Color getForeground Devuelve el color de primer plano del componente
void setForeground(Color) Fija el color de primer plano del componente
Font2 getFont() Devuelve el tipo de letra asociado al componente
void setFont(Font) Fija el tipo de letra del componente
Boolean getVisible() Indica si el componente es visible
void setVisible(Boolean) Muestra/oculta el componente (útil para ventanas)
Boolean getEnabled() Indica si el componente está activo
void setEnabled(Boolean) Activa/desactiva el componente (útil para botones y opciones de menú)
Graphics getGraphics() Devuelve el objeto tipo Graphics que puede dibujar en el componente
repaint() Llamaremos a este método para pedirle al componente que se redibuje
repaint(int x, int y, int width, int height) Llamaremos a este método para pedirle al componente que redibuje el rectángulo indicado
void Paint(Graphics g) Redefiniremos este método (usando herencia) cuando queramos dibujar algo en el componente
void Update(Graphics g) Método que por defecto borra el componente y llama a paint() cuando hay que repintar un componente. Se utiliza sobre todo en animaciones
Point3 getLocation() Indica la posición de la esquina superior izquierda del componente en su contenedor
void setLocation(int x, int y) Mueve el componente a la posición indicada
void setLocation(Point p) Mueve el componente a la posición indicada
Container getParent() Indica a qué contenedor pertenece el componente

Algunas observaciones sobre estos métodos:
  1. La clase Dimension de AWT sirve para almacenar el ancho y el alto de un componente. Sus métodos más importantes son double getWidth(), double getHeight() y sus correspondientes set.
  2. La clase Font de AWT representa los tipos de letra. Es una clase compleja que no vamos a ver en profundidad. La forma más habitual de uso es como en este ejemplo:

    // Arial de 12 pts en negrita
    Font fuenteNegrita = new Font("Arial",Font.BOLD,12);

    Las constantes de formato son: Font.PLAIN, Font.BOLD y Font.ITALIC.
  3. La clase Point de AWT se utiliza para representar las coordenadas de un punto (x,y). Tiene una constructora Point(x,y) y métodos tales como int getX(), int getY(), void setX(int x), void setY(int y) y void move(int x, int y).

El siguiente diagrama muestra esquemáticamente las clases derivadas de Component:

Aunque no vamos a estudiarlas todas, conviene saber para qué sirve cada una de ellas, y referirnos a la ayuda de Java cuando sea necesario:
Componentes Básicos
ClaseDescripción
Button Botones con un texto (Ej: Aceptar)
Canvas Rectángulo para dibujar
Choice Lista desplegable (ej: lista de países para elegir)
CheckBox Casillas cuadradas para marcar (como en un test)
Label Etiqueta: caracteres que se muestran en un contenedor
List Lista, similar a Choice pero constantemente desplegada
Scrollbar Barra de desplazamiento
TextField Campo de edición que puede utilizar el usuario para introducir datos
TextArea Similar a TextField pero permite introducir texto que ocupe varias líneas

Contenedores
ClaseDescripción
Panel Contenedor básico que necesita formar parte de una ventana (o página)
Applet Panel preparado para formar parte de una página HTML
Window Una ventana sin título ni casillas para minimizar,cerrar,etc.
Frame Una ventana normal, con título
Dialog Ventana que se muestra sobre la ventana actual (Ej.: la típica ventana para preguntar "¿Está seguro de ...." y dos botones de Aceptar y Cancelar)
FileDialog Similar al anterior, pero especializado para la selección de ficheros (se suele utilizar en las opciones de "Abrir" o "Grabar como" )


2.- Ventanas: la clase Frame

2.1 Abriendo las ventanas
2.2 Cerrando las ventanas
2.3 Ejemplo
2.4 Utilización de la herencia

2.1 Abriendo las ventanas

La clase Frame representa en AWT una ventana tal y como estamos acostumbrados a verlas en un entorno gráfico. Dado que el resto de los componentes formarán parte de una ventana, parece lógico que comencemos por ella.

Aviso: No es posible explicar en un espacio limitado todas las características de un componente visual complejo como Frame, o como el resto de los estudiados en este tema. Por tanto nos limitaremos a los aspectos básicos, pero avisando de que en la ayuda de Java se pueden encontrar métodos, constructoras, etc. no discutidos aquí.
Para crear e inicializar una ventana AWT (Frame) vamos a seguir los siguientes pasos:

Crear la variable tipo Frame
Para eso utilizaremos alguna de las constructoras de la clase, que son:
Tamaño y posición iniciales
El tamaño se fija con el método void setSize(int, int) y la posición con setLocation(int,int). Ambos pertenecen a la clase Component. Ejemplo:

        ventana.setSize(300, 100);
        ventana.setLocation(100,50);    
    
Hay que observar que el tamaño y la posición dependen de la configuración de la pantalla. Para conocer el tamaño en pixels de la pantalla podemos utilizar el método Toolkit.getDefaultToolkit().getScreenSize();, que devuelve un objeto de tipo Dimension. Por ejemplo, para que la ventana aparezca centrada y ocupe un tercio de la pantalla tanto en ancho como en alto podemos escribir:
   Dimension d = Toolkit.getDefaultToolkit().getScreenSize();
   // calculamos el tamaño de la ventana a partir del ancho de la pantalla
   int ancho=d.width/3;
   int alto=d.height/3;        
   ventana.setSize(ancho, alto);
   ventana.setLocation(d.width/2-ancho/2,d.height/2-alto/2);           
    
Otras inicializaciones (opcional)
También podemos fijar los colores, el tipo de letra, el título (con void setTitle(String)) o el icono que mostrará la ventana al minimizarse (con void setImage(Image)). Por ejemplo:

        ventana.setBackground(new Color(20,140,10));
        ventana.setForeground(Color.blue);
        Font fuente = new Font("Arial", Font.PLAIN, 20);
        ventana.setFont(fuente);
        ventana.setTitle("Ejemplo de ventana ");
    
Fijar el estilo (opcional)
Hablaremos de los "estilos" más adelante. De momento baste con decir que el método setLayout(Layout) determina el estilo de la ventana, es decir cómo se distribuyen los componentes en la ventana. Por ejemplo:

        FlowLayout estilo = new FlowLayout();
        ventana.setLayout(estilo);    
    
hará que los componentes se sitúen uno al lado del otro, de izquierda a derecha y de arriba a abajo.

Observación: Todos los estilos (como FlowLayout) son subclases de la clase Layout y por eso pueden ser utilizados como argumentos del método setLayout. Es un ejemplo de polimorfismo.
Incorporar los componentes
En este paso se añaden los componentes que se desee incluir en la ventana. Para eso se utiliza el método void add(Component) heredado de la clase Container. Por ejemplo:

        Label etiq = new Label("Te estoy mirando...");
        ventana.add(etiq);
    

Observación: Otro ejemplo de polimorfismo: etiq es de tipo Label, pero Label hereda de Component y por eso etiq puede ser argumento del método add, cuyo argumento está declarado de tipo Component.

Aviso: Es importante que no olvidemos incorporar los componentes básicos a un contenedor; en otro caso no podrán ser visibles.
Mostrar la ventana
Esto se hace con el método setVisible heredado de Component.

        ventana.setVisible(true);
    

Aviso: Un error habitual es olvidar este paso con lo que la ventana no se mostrará

Observación: La utilización de setVisible permite "cambiar" de una ventana a otra, haciendo visible la que estaba oculta y viceversa.

Poniendo todo el código anterior junto obtenemos la siguiente ventana:

2.2 Cerrando las ventanas

Una ventana puede recibir los siguientes tipos de eventos: Para tener acceso a ellos debemos:

Escribir una clase para atenderlos. Esta clase debe heredar de WindowAdapter o bien implementar el interfaz WindowListener

Pasarle a la ventana un objeto de ese tipo a la ventana mediante el método addWindowListener. La ventana llamará al método adecuado correspondiente a cada evento.

Los métodos son: De todos ellos el que nos interesa ahora es windowClosing que se utiliza cuando el usuario trata de cerrar la ventana. Para que al pulsar se cierre realmente tendremos que incluir una llamada a System.exit(0), que fuerza el fin de la aplicación. La clase puede ser por ejemplo:

class ParaAcabar extends WindowAdapter {

    public void windowClosing(WindowEvent e) {
        System.exit(0);
    }
}    
Y el código para que la ventana utilice este método:

        ParaAcabar acabar = new ParaAcabar();
        ventana.addWindowListener(acabar);

Observación: Si en lugar de heredar de WindowAdapter hubiéramos implementado su interfaz correspondiente (WindowListener) habríamos tenido que incluir todos los métodos anteriores en la clase, aunque sólo estemos interesados en uno de ellos.

Observación: En swing el componente correspondiente se llama JFrame, y permite indicar que se quiere salir de la aplicación al cerrar la ventana sin necesidad de escribir un objeto escucha, simplemente con:

JFrame ventana = new ...
....
ventana.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

2.3 Ejemplo

El siguiente programa reúne todos los conceptos anteriores:
Principal.java
import java.awt.*;
import java.awt.event.*;

public class Principal {
    
    public static void main(String[] args) {
       // objeto de tipo ventana    
       Frame ventana = new Frame("Ventana de prueba");
       
       // ventana cuadrada centrada en la pantalla y 
       // ocupando un tercio de la  pantalla
        Dimension d = Toolkit.getDefaultToolkit().getScreenSize();
        // calculamos el tamaño de la ventana a partir del ancho de la pantalla
        int ancho=d.width/3;
        int alto=d.height/3;        
        ventana.setSize(ancho, alto);
        ventana.setLocation(d.width/2-ancho/2,d.height/2-alto/2);           
       
        // colores, título y fuente
        ventana.setBackground(new Color(20,140,10));
        ventana.setForeground(Color.blue);
        Font fuente = new Font("Arial", Font.PLAIN, 20);
        ventana.setFont(fuente);
        ventana.setTitle("Ejemplo de ventana ");
       
         // estilo
        FlowLayout estilo = new FlowLayout();
        ventana.setLayout(estilo);    
       
        // componentes 
        Label etiq = new Label("Te estoy mirando...");
        ventana.add(etiq);
          
        // añadimos el "listener" para cerrar la ventana
        ParaAcabar acabar = new ParaAcabar();
        ventana.addWindowListener(acabar);    

        // hacemos la ventana visible
        ventana.setVisible(true);      
    }
}

// clase escucha que se ejecuta al tratar de cerrar la ventana
class ParaAcabar extends WindowAdapter {
    public void windowClosing(WindowEvent e) {
        System.exit(0); // abandonar la aplicación
    }
}    
Es interesante observar que la clase para cerrar la ventana se encuentra, por comodidad, en el mismo fichero que la clase principal. Esto es posible porque esta clase no es pública.

Observación: En cada fichero .java puede haber una única clase pública, pero también se permite incluir otras clases -no públicas- que sirvan de clases auxiliares de la clase pública.

2.4 Utilización de la herencia

Es muy normal separar el código asociado a la ventana del main, haciendo una clase aparte que se encargue de la gestión de la ventana. Entonces la aplicación queda compuesta de dos clases: la clase Ventana, y la clase Principal que se limita a crear la ventana y a hacerla visible. La configuración de la ventana (colores, tamaño, posición, componentes, escuchas, etc.) se hará entonces en la constructora de la clase Ventana, que se definirá hija de la clase Frame:
Ventana.java

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

public class Ventana extends Frame {
    
    public Ventana() {
       // ventana cuadrada centrada en la pantalla y 
       // ocupando un tercio de la  pantalla
        Dimension d = Toolkit.getDefaultToolkit().getScreenSize();
        // calculamos el tamaño de la ventana a partir del ancho de la pantalla
        int ancho=d.width/3;
        int alto=d.height/3;        
        setSize(ancho, alto);
        setLocation(d.width/2-ancho/2,d.height/2-alto/2);           
       
        // colores, título y fuente
        setBackground(new Color(20,140,10));
        setForeground(Color.blue);
        Font fuente = new Font("Arial", Font.PLAIN, 20);
        setFont(fuente);
        setTitle("Ejemplo de ventana ");
       
         // estilo
        FlowLayout estilo = new FlowLayout();
        setLayout(estilo);    
       
        // componentes 
        Label etiq = new Label("Te estoy mirando...");
        add(etiq);
          
        // añadimos el "listener" para cerrar la ventana
        ParaAcabar acabar = new ParaAcabar();
        addWindowListener(acabar);      
    }   
}


// clase escucha que se ejecuta al tratar de cerrar la ventana
class ParaAcabar extends WindowAdapter {
    public void windowClosing(WindowEvent e) {
        System.exit(0); // abandonar la aplicación
    }
}    
De esta forma la clase ventana tiene todos los métodos de la clase Frame más todos los que nosotros añadamos posteriormente. La clase principal queda simplemente:
Principal.java
public class Principal {
    
    public static void main(String[] args) {
       // objeto de tipo ventana    
       Ventana ventana = new Ventana();
       
        // hacemos la ventana visible
        ventana.setVisible(true);      
    }
}
y en el resto del capítulo a menudo la omitiremos para evitar repetir el código, que es independiente de la ventana.

3.- Componentes Básicos

3.1 Etiquetas: la clase Label
3.2 Botones: la clase Button
3.3 Entrada de datos: la clase TextField
3.4 Áreas de texto: la clase TextArea
3.5 Marcas casillas: la clase Checkbox

3.1 Etiquetas: la clase Label

La clase Label(etiqueta) se utiliza para mostrar Strings en un componente.

Constructoras
Tiene 3 constructoras:
Métodos
Aparte de los métodos heredados de Object y Component, esta clase tiene dos métodos importantes:

3.2 Botones: la clase Button

Este componente es básico; sobre el suelen recaer las acciones del usuario y a menudo en sus escuchas asociadas se realiza la parte más complicada del programa. La filosofía que se sigue es válida para otros componentes que no discutimos aquí, como los menús. Constructoras
Tiene 2 constructoras:
Métodos
Algunos de los métodos más importantes, además de los heredados de Component, son: Vamos a ver un primer ejemplo. En este ejemplo se separa la aplicación en dos clases independientes: la clase con el main y la clase con la ventana:

Ventana.java


package ventanas;
import java.awt.*;
import java.awt.event.*;

public class Ventana extends Frame {
    Button botón;
    Label etiq;
    
    // constructora
    public Ventana() {        
        // titulo, estilo, tamaño y posición iniciales        
        setTitle("Ejemplo de ventana con boton (v.1) ");        
        setLayout(new FlowLayout());        
        setSize(300, 100); setLocation(100,50);                
        // le damos un poco de color a la ventana
        setBackground(Color.yellow);
        
        // una etiqueta
        etiq = new Label("Un botón:");
        add(etiq);
        
        // creamos el botón
        botón = new Button("Púlsame");
        botón.setBackground(Color.blue);
        botón.setForeground(Color.white);
        
        // lo incorporamos a la ventana
        // importante: si no se hace esto no sera visible
        add(botón);
            
        // añadimos el "listener" para cerrar la ventana
        addWindowListener(new ParaAcabar());        
    }
}

// clase para cerrar la ventana
class ParaAcabar extends WindowAdapter {
    public void windowClosing(WindowEvent e) {  System.exit(0); }
}
    
En este ejemplo aparece la ventana pero al pulsar el botón todavía no hace nada. En el siguiente apartado veremos como hacer que el botón "reaccione" cuando es pulsado.
Eventos
La idea es que no será el propio botón sino un objeto escucha el que será informado por Java para que actúe cuando el botón sea pulsado. Para lograr esto hay que:
  1. Escribir una clase adecuada a la que pertenecerá el objeto escucha. Esta clase debe, en el caso de los botones, implementar el interfaz java.awt.event.ActionListener.
  2. Declarar un objeto del tipo anterior (normalmente en la constructora de la ventana, a la vez que se crea el botón).
  3. Asociar el objeto de tipo escucha con el botón o, dicho con la terminología habitual de Java, registrar el objeto como escucha del botón. Esto se hace utilizando el método void addActionListener(ActionListener l).
El interfaz ActionListener tiene un sólo método: void actionPerformed(ActionEvent e), al que se invocará cuando ocurra un evento sobre el botón (normalmente que ha sido pulsado). El objeto ActionEvent nos servirá para saber más información acerca del evento, y normalmente se us cuando el mismo objeto de tipo ActionListener se utiliza de escucha para más de un botón, ya que nos permite saber cuál de los botones ha sido pulsado. En particular contiene 2 métodos que pueden ser útiles. El siguiente ejemplo hace que el botón, al ser pulsado escriba por pantalla el mensaje "Gracias".
Ventana.java

package ventanas;

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

public class Ventana extends Frame {
    Button botón;
    Label etiq;
    
    // constructora
    public Ventana() {
        
        // titulo, estilo, tamaño y posición iniciales        
        setTitle("Ejemplo de ventana con boton (v.2) ");        
        setLayout(new FlowLayout());        
        setSize(300, 100); setLocation(100,50);                
        // le damos un poco de color a la ventana
        setBackground(Color.yellow);
        
        // una etiqueta
        etiq = new Label("Un botón:");
        add(etiq);
        
        // creamos el botón
        botón = new Button("Púlsame");
        botón.setBackground(Color.blue);
        botón.setForeground(Color.white);
        
        // lo incorporamos a la ventana
        // importante: si no se hace esto no sera visible
        add(botón);
        
        // preparamos la escucha del boton
        Escucha e = new Escucha();
        // la registramos
        botón.addActionListener(e);
                
        // añadimos la escucha para cerrar la ventana
        addWindowListener(new ParaAcabar());        
        
    }
}

// clase para cerrar la ventana
class ParaAcabar extends WindowAdapter {
    public void windowClosing(WindowEvent e) {        
       System.exit(0);    
     }
}

// escucha del boton
class Escucha implements ActionListener {
      public void actionPerformed(ActionEvent e) {
        System.out.println("Gracias");
      }
}
    
Ejercicio: Hacer que el botón escriba al hacer click el número de veces que ha sido pulsado desde que ha comenzado la aplicación (solución en el siguiente apartado).
Interacción con otros componentes gráficos
Supongamos que pretendemos que el botón cambie de color de fondo cada vez que se le pulse. Para ello podemos utilizar el método setBackground y generar un color aleatorio utilizando el método Math.random(). Un primer intento consiste en modificar la clase Escucha de la siguiente forma:

// escucha del boton
class Escucha implements ActionListener {
      public void actionPerformed(ActionEvent e) {
        // generamos un color aleatorio
        Color c = new Color((int)(Math.random()*256),(int)(Math.random()*256), (int)(Math.random()*256)); 
        // cambiamos el color del boton
        boton.setBackground(c);
      }
}
    
Pero al compilar el código anterior obtenemos un error:

C:\JCreator LE\MyProjects\gracias\ventanas\Ventana.java:67: cannot resolve symbol
symbol  : variable boton  
location: class ventanas.Escucha
        boton.setBackground(c);
        ^
    
La razón es que la variable boton no es visible dentro de la clase Escucha. Afortunadamente podemos obtener el botón a partir de la variable ActionEvent e de la siguiente forma:
    
// escucha del boton
class Escucha implements ActionListener {
      public void actionPerformed(ActionEvent e) {
       Button boton = (Button) e.getSource();
        
        // generamos un color aleatorio
        Color c = new Color((int)(Math.random()*256),(int)(Math.random()*256), (int)(Math.random()*256)); 
        // cambiamos el color del boton
        boton.setBackground(c);
      }
}
    
Esta solución no se puede aplicar si queremos interactuar con otro componente distinto del botón.
Ejemplo: Supongamos que queremos que al pulsar el botón se muestre en la etiqueta el número de veces que se ha pulsado el botón desde que comenzó la aplicación.
En este caso no nos vale de nada la variable ActionEvent e; la etiqueta está definida en la clase ventana y debemos ''obtenerla'' de otra forma. Vamos a ver dos posibilidades:
  1. Definir un atributo en la clase escucha que contendrá una referencia al componente externo deseado.
    Este atributo se inicializará mediante la constructora.


    En nuestro caso:

    Ventana.java

    
    package ventanas;
    
    import java.awt.*;
    
    import java.awt.*;
    import java.awt.event.*;
    
    public class Ventana extends Frame {
        
        // constructora
        public Ventana() {
            
            // titulo, estilo, tamaño y posición iniciales        
            setTitle("Ejemplo de ventana con boton (v.3) ");        
            setLayout(new FlowLayout());        
            setSize(300, 100);
            setLocation(100,50);        
            
            // le damos un poco de color a la ventana
            setBackground(Color.yellow);
            
            // una etiqueta
            Label etiq = new Label("Aún no has pulsado");
            add(etiq);
            
            // creamos el boton
            Button boton = new Button("Púlsame");
            boton.setBackground(Color.blue);
            boton.setForeground(Color.white);
            
            // incorporamos el boton al frame
            // importante: si no se hace esto no sera visible
            add(boton);
            
            // preparamos la escucha del boton
            Escucha e = new Escucha(etiq);
            // la registramos
            boton.addActionListener(e);
            
            
            // añadimos el "listener" para cerrar la ventana
            addWindowListener(new ParaAcabar());        
            
            // la mostramos   
            setVisible(true);        
    
        }
    }
    
    
    // clase para cerrar la ventana
    class ParaAcabar extends WindowAdapter {
        public void windowClosing(WindowEvent e) {        
           System.exit(0);    
         }
    }
    
    
    // escucha del boton
    class Escucha implements ActionListener {
          int i; // para contar las veces que se ha pulsado
          Label etiqueta; // etiqueta que se modificará
          
          
          public Escucha(Label etiqueta) {
            this.etiqueta = etiqueta;
            i=0;
          }
          
          public void actionPerformed(ActionEvent e) {
            i++;
            etiqueta.setText("Has pulsado "+i+" veces");
          }
    }
        
    Ahora sí que el programa funcionará correctamente:

  2. Escribir la clase escucha dentro de la clase Ventana.

    Java permite escribir una clase auxiliar dentro de la clase con la que ''colabora''. Así podemos definir la clase escucha como una subclase privada de la clase Ventana, que al ser miembro de la clase tiene acceso a los atributos:
    package ventanas;
    
    import java.awt.*;
    
    import java.awt.*;
    import java.awt.event.*;
    
    public class Ventana extends Frame {
        private int contador;
        private Label etiq; 
        private Button botón;
        
        // constructora
        public Ventana() {
            contador = 0;
            
            // titulo, estilo, tamaño y posición iniciales        
            setTitle("Ejemplo de ventana con boton (v.3) ");        
            setLayout(new FlowLayout());        
            setSize(300, 100);  setLocation(100,50);        
            
            // le damos un poco de color a la ventana
            setBackground(Color.yellow);
            
            // una etiqueta
            etiq = new Label("Aún no has pulsado");
            add(etiq);
            
            // creamos el boton
            botón = new Button("Púlsame");
            botón.setBackground(Color.blue);
            botón.setForeground(Color.white);
            
            // incorporamos el boton al frame
            // importante: si no se hace esto no sera visible
            add(botón);
            
            // preparamos la escucha del boton
            Escucha e = new Escucha();
            // la registramos
            botón.addActionListener(e);
                    
            // añadimos el "listener" para cerrar la ventana
            addWindowListener(new ParaAcabar());                
        }
    
      // escucha del botón
      private class Escucha implements ActionListener {
          public void actionPerformed(ActionEvent e) {
            contador++;
            etiq.setText("Has pulsado "+contador+" veces");
          }
      }
    } // fin de la clase Ventana
    
    // clase para cerrar la ventana
    class ParaAcabar extends WindowAdapter {
      public void windowClosing(WindowEvent e) {System.exit(0);}
    }
    
Esta solución es más sencilla; nos ahorramos la constructora, la copia de la referencia para acceder a los objetos, etc. A cambio es menos elegante y más limitada; por ejemplo no nos permite definir la clase escucha en un fichero aparte, para poder compartirla por varias aplicaciones.
Utilizando la misma escucha para varios botones
Supongamos que queremos tener una etiqueta que haga de contador comenzando en 0. Incluiremos dos botones, uno para incrementar el contador y otro para decrementarlo. A la hora de establecer las escuchas hay dos posibilidades:
  1. Escribir dos clases escucha, una para el botón de incrementar y otra para el de decrementar.
  2. Utilizar la misma escucha para ambos.
El segundo método es, en este caso, más cómodo, pero tenemos que ser capaces de distinguir dentro del método actionPerformed cuál de los dos botones ha sido pulsado, para así incrementar o decrementar el contador. Para esto podemos utilizar el método setActionCommand de la clase Button y getActionCommand de ActionEvent, tal y como muestra el programa siguiente:
Ventana.java

package ventanas;

import java.awt.*;

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

public class Ventana extends Frame {
    
    // constructora
    public Ventana() {
        
        // titulo, estilo, tamaño y posición iniciales        
        setTitle("contadores ");        
        setLayout(new FlowLayout());        
        setSize(200, 100);
        setLocation(100,50);        
        
        // le damos un poco de color a la ventana
        setBackground(Color.yellow);
        
        // las etiquetas
        Font fuente = new Font("Arial", Font.PLAIN, 20);
        Label etiq = new Label("Contador: ");
        etiq.setFont(fuente);
        add(etiq);
        
        Label etiq2 = new Label("0");
        etiq2.setFont(fuente);
        add(etiq2);

        // preparamos la escucha del boton
        Escucha e = new Escucha(etiq2);
        
        // creamos los botones
        Button botonInc = new Button("Incremento");
        botonInc.setActionCommand("inc");
        botonInc.setBackground(Color.blue);
        botonInc.setForeground(Color.white);
        add(botonInc);
        botonInc.addActionListener(e);
        

        Button botonDec = new Button("Decremento");
        botonDec.setActionCommand("dec");
        botonDec.setBackground(Color.red);
        botonDec.setForeground(Color.white);
        add(botonDec);
        botonDec.addActionListener(e);
        
        // añadimos el "listener" para cerrar la ventana
        addWindowListener(new ParaAcabar());        
    }
}

// clase para cerrar la ventana
class ParaAcabar extends WindowAdapter {
    public void windowClosing(WindowEvent e) {        
       System.exit(0);    
     }
}

// escucha del boton
class Escucha implements ActionListener {
      int contador; // para contar las veces que se ha pulsado
      Label etiqueta; // etiqueta que se modificará
      
      public Escucha(Label etiqueta) {
        this.etiqueta = etiqueta;
        contador=0;
      }
      
      public void actionPerformed(ActionEvent e) {
        if (e.getActionCommand().equals("inc"))
            contador++;
        else
            contador--;    
        etiqueta.setText(" "+contador+" ");
      }
}

    
El programa tendrá el siguiente aspecto:

3.3 Entrada de datos: la clase TextField

Este componente se utiliza comúnmente para leer datos de teclado.

Constructoras
Para TextField:
Métodos
La clase TextField coincide con las clases anteriores en la definición de los métodos setText(String cadena) y String getText(). Algunos otros métodos de interés:
Eventos
En cuanto a los eventos, la diferencia principal con la clase Button es que el método ActionEvent de la clase escucha se utiliza cuando se pulsa Enter. También se puede controlar cual es la tecla pulsada, como veremos al hablar de los eventos de teclado, pero estos eventos no son específicos de la clase TextField sino comunes a todos los Component.
Ejemplo: Clase para representar una ventana de entrada a un sistema, con login y password:
PalabraClave.java

package claves;

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


public class PalabraClave extends Frame {
    private Label etiq,etiq2,etiq3;
    private Button aceptar;
    private TextField login;
    private TextField pass;
    
    
    public PalabraClave() {

        // titulo, estilo, tamaño y posición iniciales        
        setTitle("Entrada al Sistema");        
        setLayout(new FlowLayout());        

       // ventana centrada
        Dimension d = Toolkit.getDefaultToolkit().getScreenSize();
        int ancho=300, alto=200;        
        setSize(ancho, alto);
        setLocation(d.width/2-ancho/2,d.height/2-alto/2);        
        
        // tipo de letra
        Font fuente = new Font("Arial", Font.PLAIN, 18);
        setFont(fuente);

        // un poco de color
        setBackground(Color.cyan);
                
        // preparamos la entrada de datos         
        etiq = new Label("Login:   ");
        add(etiq);
        
        login = new TextField(8);
        add(login);
        
        etiq2 = new Label("Password:");
        etiq2.setFont(fuente);
        add(etiq2);

        pass = new TextField(10);
        pass.setEchoChar('*');
        add(pass);

        aceptar = new Button("Aceptar");
        add(aceptar);
        
        etiq3 = new Label("Pulsa Aceptar para Continuar");
        add(etiq3);
        
        // preparamos las escuchas 
        EscuchaAceptar e = new EscuchaAceptar();
        aceptar.addActionListener(e);
        pass.addActionListener(e);
                
        // esta vale sólo para el campo de login
        EscuchaSiguiente pasaAlSiguiente = new EscuchaSiguiente();
        login.addActionListener(pasaAlSiguiente);
                
        // añadimos la escucha para cerrar la ventana
        addWindowListener(new ParaAcabar());                
    }   

    // escuchas como subclases privadas ///////////////////////

    // escucha del boton Aceptar
    private class EscuchaAceptar implements ActionListener {
      
      public void actionPerformed(ActionEvent e) {
        
        if (válidos(login.getText(), pass.getText()))
            etiq3.setText("Datos válidos");
        else
            etiq3.setText("Datos no válidos");
            
      }
      
      private boolean válidos(String login, String pass) {
        // aquí se comprueba
        return login.equals("Bertoldo") && pass.equals("nolose");
      }
    }

    // clase escucha para el primer campo de edición; le pasa el foco
    // al siguiente 
    private class EscuchaSiguiente implements ActionListener {
        public void actionPerformed(ActionEvent e) {        
            // componente sobre el que ha ocurrido el evento
            Component c = (Component) e.getSource();
     
            // indicarle que pase el foco al siguiente elemento
            c.transferFocus();
        }
    }
    ////////////////////// fin escuchas //////////////////////////////
    
} // Ventana


// clase para cerrar la ventana
class ParaAcabar extends WindowAdapter {
    public void windowClosing(WindowEvent e) {System.exit(0);}
}
    
La ejecución del programa tendrá el siguiente aspecto:

3.4 Áreas de texto: la clase TextArea

Es similar a TextField con la diferencia de que al presentarse en pantalla permite introducir código que ocupa más de una línea.
Constructoras
Métodos y Eventos
Los métodos y eventos son como los de TextField con algunos métodos añadidos, entre los que podemos destacar:

3.5 Marcar casillas: la clase Checkbox

La clase Checkbox nos permite mostrar texto acompañado de casillas con dos estados: marcada o no marcada. Varios Checkbox pueden agruparse de forma que nos aseguremos de que sólo una casilla está marcada en cada momento. Vamos a ver las dos posibilidades en dos ejemplos separados: Casillas no agrupadas
En este caso basta con declarar y añadir los Checkbox independientemente. La etiqueta asociada a la casilla se puede fijar en la constructora o bien posteriormente con el método setLabel(String). El estado (activada o no) se puede modificar y consultar con los métodos setState(boolean) y getState(), respectivamente. El siguiente ejemplo muestra el uso de este componente:
Ventana.java
import java.awt.*;
import java.awt.event.*;

public class Ventana extends Frame {
    // la ventana contiene 4 casillas
    private Checkbox casilla1,casilla2,casilla3,casilla4;
    // dos etiquetas
    Label etiq1, etiq2;
    // y un botón
    private Button aceptar;
    
    public Ventana() {
       // objeto de tipo ventana    
       setTitle("Prueba de Checkbox");
       setSize(380, 150);
       setLayout(new FlowLayout());
       Font fuenteNegrita = new Font("Arial",Font.BOLD,16);
       setFont(fuenteNegrita) ;
       
       etiq1 = new Label("Marque sus aficiones favoritas y pulse Aceptar");
       add(etiq1);
       
       // una forma de construir una casilla
       casilla1 = new Checkbox();
       casilla1.setLabel("Deportes");
       
       // otra forma
       casilla2 = new Checkbox("Lectura");
       casilla3 = new Checkbox("Viajes");
       casilla4 = new Checkbox("Cine");
       // hacemos que la casilla Cine aparezca marcada
       casilla4.setState(true);
       
       add(casilla1); add(casilla2); add(casilla3); add(casilla4);
       
       aceptar = new Button("Aceptar");
       add(aceptar);
       // escucha del botón
       aceptar.addActionListener(new EscuchaBotón());
       
       // etiqueta donde se mostrará el resultado
       etiq2 = new Label("");
       
       // añadimos la escucha para cerrar la ventana
       addWindowListener(new ParaAcabar());        
    }   
    
    //////////////////////////////////////////////////////
    // escucha del boton
    class EscuchaBotón implements ActionListener {
        public void actionPerformed(ActionEvent e) {
          String aficiones = "Ha elegido: ";
          if (casilla1.getState()) aficiones += casilla1.getLabel()+" ";
          if (casilla2.getState()) aficiones += casilla2.getLabel()+" ";
          if (casilla3.getState()) aficiones += casilla3.getLabel()+" ";
          if (casilla4.getState()) aficiones += casilla4.getLabel()+" ";
          
          // añadimos a la ventana un label con los objetos elegidos
          add(new Label(aficiones));
        }
     }
}

// clase escucha que se ejecuta al tratar de cerrar la ventana
class ParaAcabar extends WindowAdapter {
    public void windowClosing(WindowEvent e) {
        System.exit(0); // abandonar la aplicación
    }
}   
El resultado es:

Casillas agrupadas
Similar al anterior, pero agrupando las casillas por medio de un objeto tipo CheckboxGroup, para lo que se usa una tercera constructora que permite indicar el grupo al que pertenece la casilla, así como si está activa (cada grupo tendrá como máximo una casilla marcada). Los métodos de CheckboxGroup Checkbox getSelectedCheckbox() y void setSelectedCheckbox(Checkbox) sirven para obtener y cambiar, respectivamente, el Checkbox seleccionado. Ejemplo:
Ventana.java
import java.awt.*;
import java.awt.event.*;

public class Ventana extends Frame {
    // grupo de 3 casillas
    private CheckboxGroup grupo;
    private Checkbox c1,c2,c3;
    // y una etiqueta
    Label etiq1;
    
    public Ventana() {
       setTitle("Prueba de CheckboxGroup");
       setSize(300, 80);
       setBackground(Color.yellow);
       setLayout(new FlowLayout());
       Font fuenteNegrita = new Font("Arial",Font.BOLD,16);
       setFont(fuenteNegrita) ;
       
       etiq1 = new Label("Destino:");
       add(etiq1);
       
       // primero se construye el "agrupador"
       grupo = new CheckboxGroup();
       // ahora se crean las casillas indicando que están en el grupo
       c1 = new Checkbox("Madrid",grupo,false);
       c2 = new Checkbox("París",grupo,false);
       c3 = new Checkbox("Berlín",grupo,true);
       // añadir las casillas a la ventana; el grupo NO se añade
       add(c1); add(c2); add(c3);
         
       // añadimos la escucha para cerrar la ventana
       addWindowListener(new ParaAcabar());                 
    }   
}

// clase escucha que se ejecuta al tratar de cerrar la ventana
class ParaAcabar extends WindowAdapter {
    public void windowClosing(WindowEvent e) {
        System.exit(0); // abandonar la aplicación
    }
}   
El resultado es:



4.- Eventos de teclado y de ratón

4.1 Eventos de teclado
4.2 Eventos de ratón

4.1 Eventos de teclado

Los eventos de teclado detectan la pulsación de teclas. Cualquier componente puede registrar una escucha de teclado, al igual que una escucha para el ratón. El componente que recibe la entrada de teclado en cada momento es único; se dice que ese componente "tiene el foco". Un componente puede pedir el foco utilizando el método requestFocus(). Para detectar los eventos de teclado, un componente debe:
Escribir una clase que implemente el interfaz KeyListener.
Declarar un objeto de la clase anterior.
Registrarlo con addKeyListener(KeyListener escucha), un método de la clase Component.
El interfaz KeyListener dispone de los siguientes métodos: Por su parte, el parámetro de tipo KeyEvent contiene los siguientes métodos que nos ayudan a identificar la tecla concreta:
Ejemplo: Hacer que la posición de una etiqueta cambie con las teclas de cursor. El programa también avisará si se pulsa la tecla Bloq. Núm.
Teclas.java

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

public class Teclas extends Frame {
   private Label etiq; // único componente
    
   public Teclas() {
        // titulo, estilo, tamaño y posición iniciales        
        setTitle("Teclas");        
        setLayout(new FlowLayout());        

       // ventana centrada
        Dimension d = Toolkit.getDefaultToolkit().getScreenSize();
        int ancho=300, alto=200;        
        setSize(ancho, alto);
        setLocation(d.width/2-ancho/2,d.height/2-alto/2);        
        
        // tipo de letra
        Font fuente = new Font("Arial", Font.PLAIN, 18);
        setFont(fuente);
        // un poco de color
        setBackground(Color.cyan);
                
        // preparamos la entrada de datos 
        etiq = new Label("Pulsa 'S' para salir  ");
        add(etiq);
        
        // escucha de teclado para la ventana; 
        // le pasamos como parámetro la etiqueta
        EscuchaTeclas  e = new EscuchaTeclas(etiq);
        addKeyListener(e);
        
        // añadimos la escucha para cerrar la ventana
        addWindowListener(new ParaAcabar());        
        
        // si no hacemos esto el foco se lo queda la etiqueta!!!
        requestFocus();              
    }   
}

// clase para cerrar la ventana
class ParaAcabar extends WindowAdapter {
    public void windowClosing(WindowEvent e) {        
       System.exit(0);    
     }
}

// escucha de teclaro
class EscuchaTeclas implements KeyListener {
    private Label etiq;
    
    public EscuchaTeclas(Label etiq) {
        this.etiq = etiq;
    }
    
    public void keyPressed(KeyEvent e){
        if (e.isActionKey()==false && e.getKeyChar() == 'S') {
            System.exit(0);
        } else {
            
        // guardamos el código de la tecla especial
        int tecla = e.getKeyCode();
            
        // la posición actual de la etiqueta
        Point pos = etiq.getLocation();
        switch(tecla) {
                case KeyEvent.VK_UP: 
                            etiq.setLocation(pos.x,pos.y-1);
                            break;
                case KeyEvent.VK_DOWN: 
                            etiq.setLocation(pos.x,pos.y+1);
                            break;
                case KeyEvent.VK_LEFT:   
                            etiq.setLocation(pos.x-1,pos.y);
                            break;
                case KeyEvent.VK_RIGHT: 
                            etiq.setLocation(pos.x+1,pos.y);
                            break;
                case KeyEvent.VK_NUM_LOCK:
                            etiq.setText("Bloque numérico");
                            break;
                default:                    
            }                   
        }
    }
    
    // hay que incluir estos métodos aunque no se necesiten
    public void keyReleased(KeyEvent e) {    }
    
    public void keyTyped(KeyEvent e) {    }
}
    

4.2 Eventos de ratón

Existen dos interfaces para el control del ratón, cada uno correspondiendo a un tipo de evento.
MouseListener
Sirve para detectar las pulsaciones del ratón y responde a los eventos del tipo MouseEvent. El interfaz consta de 5 métodos: La clase MouseEvent tiene entre otros métodos: Ejemplo: Vamos a mover un botón para que el usuario no pueda pulsarlo nunca:
CorreRaton.java

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

public class CorreRaton extends Frame {
    public CorreRaton() {
           // titulo, estilo, tamaño y posición iniciales        
        setTitle("Botón tímido");        
        setLayout(new FlowLayout());        
        setBackground(Color.cyan);

       // ventana centrada
        Dimension d = Toolkit.getDefaultToolkit().getScreenSize();
        int ancho=300, alto=150;        
        setSize(ancho, alto);
        setLocation(d.width/2-ancho/2,d.height/2-alto/2);            

        // preparamos la entrada de datos 
        Button botón = new Button("Púlsame");
        add(botón);
        
        // escucha de teclado para la ventana
        EscuchaRatón  e = new EscuchaRatón(botón);
        botón.addMouseListener(e);
        
        // añadimos la escucha para cerrar la ventana
        addWindowListener(new ParaAcabar());        
        
        // la mostramos   
        setVisible(true); 
        
        // si no hacemos esto el foco se lo queda la etiqueta!!!
        requestFocus();              
    }   
}


// clase para cerrar la ventana
class ParaAcabar extends WindowAdapter {
    public void windowClosing(WindowEvent e) {        
       System.exit(0);    
     }
}

class EscuchaRatón implements MouseListener {
    Button botón;
    
    // le pasamos el botón y el tamaño de la pantalla
    public EscuchaRatón(Button botón) {
        this.botón = botón;
    }
    
    public void mouseEntered(MouseEvent e) {
        Point p = botón.getLocation();
    
        if (p.x<150) p.x = 160 + ((int)(Math.random()*120));
        else p.x = 20 + ((int)(Math.random()*120));
        p.y = 20 + ((int) (Math.random()*100));
                    
        botón.setLocation(p);
    }
    
    // no olvidar incluir estos métodos!!
    public void mouseClicked(MouseEvent e) {
    }
    public void mouseExited(MouseEvent e) {
    }
    public void mousePressed(MouseEvent e) {
    }
    public void mouseReleased(MouseEvent e) {
    }

}

MouseMotionListener
Detecta los movimientos del ratón, y también cuando el ratón se arrastra pulsado (para marcar zonas). Este interfaz tiene dos métodos:


5.- Estilos

5.1 Introducción
5.2 Estilo FlowLayout
5.3 Estilo BorderLayout
5.4 Estilo GridLayout
5.5 Paneles

5.1 Introducción

El estilo (o diseño) de un contenedor indica cómo se dispondrán los componentes básicos en él.
Se establece mediante el método void setLayout(LayoutManager l) de la clase Container.
La clase LayoutManager es la clase de la que heredan todos los estilos (layouts). Cada contenedor tiene un estilo por defecto. Por ejemplo, en el caso de Frame este es BorderLayout, y en el de Panel FlowLayout.

Observación: Aunque cada contenedor tiene un único estilo, podemos mezclar estilos incorporando contenedores dentro de contenedores. La clase Panel está pensada con este propósito.
Igual que en los puntos anteriores, aquí no vamos a ver todos los estilos de los que dispone Java (¡hay 21 estilos diferentes!), contentándonos con ver algunos de los más comunes y esperando que sea suficiente para captar la "filosofía" y que a partir de éstos comprender el resto sea más sencillo.

5.2 Estilo FlowLayout

En este estilo los componentes se colocan uno al lado del otro, en la misma fila. Cuando no caben más se cambia de fila. El orden en el que se colocan es de izquierda a derecha y de arriba a abajo. De los estilos sólo nos interesarán, en general, sus constructoras, no los métodos que contienen:
Constructoras
Tiene 3 constructoras:
Ejemplo: Añadimos 10 botones para ver como se colocan en la ventana con FlowLayout
EstiloFlowLayout.java

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

public class EstiloFlowLayout extends Frame {
    
   public EstiloFlowLayout() {
        // titulo, estilo, tamaño y posición iniciales        
        setTitle("FlowLayout");        
        setBackground(Color.cyan);
        setSize(300,200);

        // componentes centrados a la derecha, con una distancia entre ellos
        // de 20 pixels en x y 30 en y 
        setLayout(new FlowLayout(FlowLayout.RIGHT, 20,30));        

        // añadimos unos botones de pega         
        for (int i=0; i<10; i++)
            add(new Button(" "+i+" "));
        
        // añadimos la escucha para cerrar la ventana
        addWindowListener(new ParaAcabar());        
    }   
}

// clase para cerrar la ventana
class ParaAcabar extends WindowAdapter {
    public void windowClosing(WindowEvent e) {System.exit(0); }
}


El resultado es:

5.3 Estilo BorderLayout

Es el estilo por defecto de Frame.
El estilo BorderLayout divide el contenedor en 5 áreas: Norte, Sur, Este, Oeste y centro, indicadas por las 5 constantes: BorderLayout.NORTH, BorderLayout.SOUTH, BorderLayout.EAST, BorderLayout.WEST y BorderLayout.CENTER.
Cada componente puede ocupar un área únicamente; y a ser posible debe ocuparla por entero. Si se añaden dos componentes a la misma área, sólo será visible el último de ellos.
El área que ocupa el componente se indica al incorporarlo al contenedor con add. Si no se indica nada se colocará en el centro.
Constructoras
Ejemplo
Ventana.java

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

public class Ventana extends Frame {
    
   public Ventana() {
        // titulo, estilo, tamaño y posición iniciales        
        setTitle("BorderLayout");        
        setBackground(Color.cyan);
        setSize(300,200);

        // componentes centrados a la derecha, con una distancia entre ellos
        // de 20 pixels en x y 30 en y 
        setLayout(new BorderLayout());        


        // creamos 5 botones
        Button este = new Button("Este");
        este.setBackground(Color.blue);
        
        Button oeste = new Button("Oeste");
        oeste.setBackground(Color.red);

        Button norte = new Button("Norte");
        norte.setBackground(Color.yellow);

        Button sur = new Button("Sur");
        sur.setBackground(Color.green);

        Button centro = new Button("Centro");
        centro.setBackground(Color.pink);

        // añadimos botones en cada zona
        add(este, BorderLayout.EAST);
        add(oeste, BorderLayout.WEST);
        add(norte, BorderLayout.NORTH);
        add(sur, BorderLayout.SOUTH);
        add(centro);  // equivalente a: add(centro, BorderLayout.CENTER);
                 
        // añadimos la escucha para cerrar la ventana
        addWindowListener(new ParaAcabar());        
    }   
}

// clase para cerrar la ventana
class ParaAcabar extends WindowAdapter {
    public void windowClosing(WindowEvent e) { System.exit(0); }
}
El resultado es:

Observación: Casi nunca se inserta un botón directamente en un componente con estilo BorderLayout. Lo normal es introducirlos en otro componente (por ejemplo en un Panel) que a su vez se inserta en el contenedor de estilo BorderLayout.

5.4 Estilo GridLayout

Es un estilo que divide el contenedor en "casillas". Permite fijar el número de componentes por fila y por columna. Todas las casillas serán del mismo tamaño, tamaño suficiente para que quepan todos los componentes.
Los componentes se van colocando en la siguiente posición libre, comenzando desde arriba a la izquierda.
Constructoras
Ejemplo
Grid.java

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

public class Grid extends Frame {
    
    public Grid() {
        // titulo, estilo, tamaño y posición iniciales        
        setTitle("GridLayout");        
        setBackground(Color.yellow);
        setSize(250,150);

        // componentes en 4 filas, a 2 columnas,
        // 10 pixels de separación horizontal entre componentes
        // y 5 pixels de separación vertical
        setLayout(new GridLayout(4,2,10,5));        
        
        add(new Label("Nombre: ", Label.RIGHT)); 
        TextField nombre = new TextField(10);
        add(nombre);        
        
        add(new Label("Apellidos: ", Label.RIGHT)); 
        TextField apellidos = new TextField(20);
        add(apellidos);        

        add(new Label("Dirección: ", Label.RIGHT)); 
        TextField dirección = new TextField(30);
        add(dirección);    
        
        Button continuar = new Button("Siguiente");
        add(continuar);    
        
        Button abandonar = new Button("Cancelar");
        add(abandonar);
        
        // faltarían todas las escuchas para que el programa haga algo real

        // añadimos la escucha para cerrar la ventana
        addWindowListener(new ParaAcabar());        
    }   
}

// clase para cerrar la ventana
class ParaAcabar extends WindowAdapter {
    public void windowClosing(WindowEvent e) { System.exit(0);  }
}
El resultado es:

5.5 Paneles

La clase Panel representa un contenedor que no puede existir por su cuenta, sólo insertado en otro contenedor (como por ejemplo en un Frame(). Su estilo por defecto es FlowLayout, aunque como en todos los contenedores se puede modificar con setLayout. Se utiliza a menudo para combinar diferentes estilos en una misma ventana.

Observación: Una fase importante al desarrollar aplicaciones con interfaz gráficos es la del diseño del interfaz: qué paneles compondrán cada ventana y con qué estilos.

Ejemplo: Vamos a escribir el aspecto previo que tendría una aplicación para jugar al ajedrez; incluyendo el tablero vacío, un título inicial y botones para comenzar y salir. El resultado debe ser:
Antes de escribir el programa pensamos en el diseño de la ventana:
Con este diseño podemos escribir el código:
Tablero.java

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


public class Tablero extends Frame {
    public Tablero() {
        
        // titulo, estilo, tamaño y posición iniciales        
        setTitle("Tablero");        
        setBackground(Color.green);

       // ventana centrada
        Dimension d = Toolkit.getDefaultToolkit().getScreenSize();
        int ancho=300, alto=350;        
        setSize(ancho, alto);
        setLocation(d.width/2-ancho/2,d.height/2-alto/2);        
        
        // preparamos el layout de la ventana
        setLayout(new BorderLayout(20,20));     

        // ponemos la etiqueta
        Font fuente = new Font("Arial", Font.BOLD, 20);
        Label etiq = new Label("A J E D R E Z ", Label.CENTER);
        etiq.setFont(fuente);
        etiq.setForeground(new Color(100,0,50));
        
        add(etiq, BorderLayout.NORTH);
        
        // preparamos el tablero; será el panel central
        Panel tablero = new Panel();
        tablero.setLayout(new GridLayout(8,8));

        for (int i=1; i<=8; i++)
           for (int j=1; j<=8; j++)
                if ((i+j) % 2 == 0) {
                    Button blanca = new Button(" ");
                    blanca.setBackground(Color.white);          
                    blanca.setEnabled(false);
                    tablero.add(blanca);
                    }
                else {
                    Button negra = new Button(" ");
                    negra.setBackground(Color.black);
                    negra.setEnabled(false);
                    tablero.add(negra);    
                }
                               
        // lo ponemos en el centro
        add(tablero,BorderLayout.CENTER);
                  
        // tablero para los botones
        Panel botones = new Panel();
        Button empezar = new Button("Empezar");
        Button acabar = new Button("Acabar");
        // no dejamos que pulsen acabar si no se está jugando
        acabar.setEnabled(false);
        
        botones.add(empezar);
        botones.add(acabar);
        
        add(botones, BorderLayout.SOUTH);
         
        // paneles para dejar margen a la izquierda y a la derecha
        Panel izq = new Panel();
        Panel der = new Panel(); 
        add(izq,BorderLayout.EAST);
        add(der,BorderLayout.WEST);

        // añadimos la escucha para cerrar la ventana
        addWindowListener(new ParaAcabar());        
    }   
}

// clase para cerrar la ventana
class ParaAcabar extends WindowAdapter {
    public void windowClosing(WindowEvent e) {        
       System.exit(0);    
     }
}
    


6.- Dibujando

6.1 Introducción
6.2 La clase Canvas
6.3 La clase Graphics
6.4 Mostrando imágenes
6.5 Un poco de animación

6.1 Introducción

¿Cómo dibujar?
Para dibujar en Java hay que conocer inicialmente los siguiente conceptos. De todo esto se deduce que para dibujar podemos "pedirle prestado" su objeto Graphics() al componente en el que queramos dibujar. Ejemplo: Botón subrayado (primera versión).

Ventana.java



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

public class Ventana  extends Frame{
    public Ventana() {
      // titulo, estilo, tamaño y posición iniciales        
        setTitle("Botón con dibujo V.1");        
        setLayout(new FlowLayout());
        Font fuente = new Font("Arial", Font.BOLD, 40);
        setFont(fuente);
   
        // ventana centrada
        Dimension d = Toolkit.getDefaultToolkit().getScreenSize();
        int ancho=200, alto=90;        
        setSize(ancho, alto);
        setLocation(d.width/2-ancho/2,d.height/2-alto/2);           

        Button botón = new Button("Aceptar");     
        add(botón);
        
         // añadimos el "listener" para cerrar la ventana
        addWindowListener(new ParaAcabar());        
        
        // la mostramos   
        setVisible(true);        

        // dibujamos sobre el botón
        Graphics g = botón.getGraphics();
        g.setColor(Color.green);
        g.fill3DRect(5,40,70,10,true);        
        g.fill3DRect(80,40,90,10,true);        
       
    }
}

// clase para cerrar la ventana
class ParaAcabar extends WindowAdapter {
    public void windowClosing(WindowEvent e) {        
       System.exit(0);    
     }
}

El resultado es:
El problema está en que cada vez que se repinta el botón no se vuelve a pintar la línea. De hecho el dibujo sólo se hace una vez, al estar en la constructora, y cada vez que hay que pintar el botón.
El método paint
Para arreglar este problema hay que saber algunas cosas más. Así pues, la solución está en sobreescribir el método paint() del componente en el que queremos dibujar; y para ello deberemos hacer una nueva clase que herede de la clase de dicho componente, tal y como muestra el siguiente ejemplo: Ejemplo: Botón subrayado (segunda versión).
BotonSubrayado.java

import java.awt.*;

public class BotonSubrayado extends Button {
    
    public BotonSubrayado(String nombre) {
        super(nombre);
    }
    
    public void paint(Graphics g) {
        // llamamos al pintar original
        super.paint(g);
        g.setColor(Color.green);
        g.fill3DRect(5,40,70,10,true);        
        g.fill3DRect(80,40,90,10,true);         
    }
}

Ventana.java

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

public class Ventana  extends Frame{
    public Ventana() {
      // titulo, estilo, tamaño y posición iniciales        
        setTitle("Botón con dibujo V.1");        
        setLayout(new FlowLayout());
        Font fuente = new Font("Arial", Font.BOLD, 40);
        setFont(fuente);
      
        // ventana centrada
        Dimension d = Toolkit.getDefaultToolkit().getScreenSize();
        int ancho=200, alto=90;        
        setSize(ancho, alto);
        setLocation(d.width/2-ancho/2,d.height/2-alto/2);           

        BotonSubrayado botón = new BotonSubrayado("Aceptar");        
        add(botón);
                
         // añadimos el "listener" para cerrar la ventana
        addWindowListener(new ParaAcabar());        
        
        // la mostramos   
        setVisible(true);        

        Graphics g = botón.getGraphics();
        g.setColor(Color.green);
        g.fill3DRect(5,40,70,10,true);        
        g.fill3DRect(80,40,90,10,true);        
    }
}

// clase para cerrar la ventana
class ParaAcabar extends WindowAdapter {
    public void windowClosing(WindowEvent e) {        
       System.exit(0);    
     }
}
Ahora cada vez que haya que volver a pintar el botón se dibujará también la línea

6.2 La clase Canvas

Aunque hemos visto que se puede dibujar en cualquier componente, lo normal es querer dibujar sólo en un espacio en blanco destinado a tal fin. Con este propósito se incluye en java el componente Canvas. El procedimiento para incluir gráficos como parte de una ventana es:
  1. Declarar una clase que herede de canvas.
  2. Sobrescribir el método void paint(Graphics g) (y/o void update(Graphics g)) de la nueva clase de forma que se dibuje el gráfico deseado.
  3. Añadir un objeto de la clase nueva como componente de la ventana.
Una aplicación hecha siguiendo esta idea tendrá al menos tres clases, con la siguiente estructura:

Lienzo.java


import java.awt.*;

// clase para dibujar
public class Lienzo extends Canvas {
...    
    // aquí se incluirá el código para dibujar
    public void paint(Graphics g) {
    ....
    }   
.....     
}

Ventana.java


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

public class Ventana extends Frame {
......    

    public Ventana() {
     ....
     ....
     Lienzo l = new Lienzo();
     
     add(l);
     
     .......
     ......
    }    
}
......

Principal.java


public class Principal {
    public static void main(String[] args) {
        new Ventana();
    }   
}

6.3 La clase Graphics

De esta clase no nos interesan sus constructoras, porque nunca vamos a construir un objeto Graphics; sólo vamos a utilizar un objeto ya existente.
Métodos Principales
abstract  void clearRect(int x, int y, int width, int height)
Borra el rectángulo especificado por sus parámetros; es decir lo pinta del color de fondo.
abstract  void copyArea(int x, int y, int width, int height, int dx, int dy)
Copia el área del componente especificada por los parámetros a una distancia dx and dy. Si se quiere que se copie a la izquierda habrá que dar un valor dx negativo. Análogamente, para copiar más arriba de la posición actual habrá que indicar un valor dy negativo.
 void draw3DRect(int x, int y, int width, int height, boolean raised)
Dibuja un rectángulo al que se da aspecto de 3d, bien mostrándolo ligeramente elevado (si raised es true) o hundido (raised false)
abstract  void drawArc(int x, int y, int width, int height, int startAngle, int arcAngle)
Dibuja un arco (circular o elíptico), empezando en el ángulo startAngle y finalizando en arcAngle. El arco estará inscrito en el rectángulo indicado por los parámetros, con centro en el centro de dicho rectángulo.
Ejemplo: El siguiente arco está escrito con la instrucción g.drawArc(50,50,150,100,0,260);. También se muestra en el dibujo el rectángulo (dibujado con g.drawRect(50,50,150,100);).
abstract  boolean drawImage(Image img, int x, int y, Color bgcolor, ImageObserver observer)
Dibuja la imagen. Las coordenadas (x,y) marcan donde estará situada la esquina superior izquierda del dibujo y bgcolor el fondo que se mostrará en la parte transparente de la imagen. El parámetro observer indica un objeto al que se va avisando cuando según se va mostrando la imagen. Nosotros lo lo utilizaremos y lo pondremos a null.
abstract  boolean drawImage(Image img, int x, int y, ImageObserver observer)
Análogo al anterior. El color de fondo no varía.
abstract  boolean drawImage(Image img, int x, int y, int width, int height,  bgcolor, ImageObserver observer)
Análogo a los anteriores con la salvedad de que el gráfico se reescalará para ajustarse al rectángulo indicado.
abstract  boolean drawImage(Image img, int x, int y, int width, int height, ImageObserver observer)
Análogo al anterior.
abstract  void drawLine(int x1, int y1, int x2, int y2)
Dibuja una línea, en el color actual, entre los dos puntos.

Observación: Si el punto final es igual al inicial se dibuja un punto. Esto es importante porque no existe ningún método para dibujar puntos, por lo que lo normal es utilizar drawLine(x,y,x,y).
abstract  void drawOval(int x, int y, int width, int height)
Dibuja un óvalo inscrito en el rectángulo indicado. Si width = height se dibujará una circunferencia
abstract  void drawPolygon(int[] x, int[] y, int n)
Dibuja un polígono. Para ello traza las rectas (x[0],y[0]), (x[1],y[1]) ... (x[n-2],y[n-2]), (x[n-1],y[n-1]), (x[n-1],y[n-1]), (x[0],y[0]).
abstract  void drawPolyline(int[] xPoints, int[] yPoints, int nPoints)
Dibuja una secuencia de líneas. La diferencia con drawPolygon es que no incluye la última línea para "cerrar" el polígono.
 void drawRect(int x, int y, int width, int height)
Dibuja un rectángulo.
abstract  void drawRoundRect(int x, int y, int width, int height, int arcWidth, int arcHeight)
Dibuja un rectángulo con los ángulos "redondeados".
abstract  void drawString(String str, int x, int y)
Muestra la cadena. El punto (x,y) representa la posición inicial (marcada por un punto en la figura de abajo) para el primer carácter de la cadena.
 void fill3DRect(int x, int y, int width, int height, boolean raised)
Análogo a draw3DRect pero dibujando el rectángulo relleno con el color actual.
abstract  void fillArc(int x, int y, int width, int height, int startAngle, int arcAngle)
Análogo a drawArc pero dibujando el arco relleno con el color actual.
abstract  void fillOval(int x, int y, int width, int height)
Análogo a fillOval pero con el óvalo relleno del color actual.
abstract  void fillPolygon(int[] xPoints, int[] yPoints, int nPoints)
Análogo a drawPolygon pero rellenando el polígono con el color actual.
abstract  void fillRect(int x, int y, int width, int height)
Análogo a drawRect pero rellenando el rectángulo del color actual.
abstract  void fillRoundRect(int x, int y, int width, int height, int arcWidth, int arcHeight)
Análogo a drawRoundRect pero rellenando el rectángulo redondeado con el color actual.
 void finalize()
Para liberar la memoria requerida por un objeto Graphics que ya no es necesario. Esto se hace porque los objetos Graphics son muy costosos en cuanto a recursos de memoria.
abstract  Color getColor()
Devuelve el color actual.
abstract  Font getFont()
Devuelve la fuente (tipo de letra) actual.
 FontMetrics getFontMetrics()
Devuelve un valor de tipo FontMetrics representando las características métricas de la fuente de letra actual. La clase FontMetrics contiene diversos métodos para conocer las medidas de los caracteres dentro del tipo actual. Por ejemplo el método stringWidth(String cadena) devolverá la anchura en pixels requerida para mostrar la cadena.
abstract  FontMetrics getFontMetrics(Font f)
Devuelve las características métricas de la fuente de letra que se le pasa como parámetro.
abstract  void setColor(Color c)
Cambia el color actual al color indicado.
abstract  void setFont(Font font)
Establece la nueve fuente de letra.
abstract  void setPaintMode()
Indica que al dibujar se borra el fondo. Es el modo de actuación por defecto.
abstract  void setXORMode(Color c1)
Al dibujar los pixels que sean del color de fondo se cambian al color del parámetro y viceversa.
 String toString()
Representación como cadena de caracteres del objeto Graphics.
abstract  void translate(int x, int y)
Fija un nuevo centro de coordenadas.

6.4 Mostrando imágenes

Para mostrar imágenes que están en disco en forma .gif o .jpg podemos seguir el procedimiento siguiente:
  1. Leer la imagen del fichero con el método Toolkit.getDefaultToolkit().getImage(String nombre). En la que el nombre es el nombre del fichero. Este método devuelve un valor de tipo Image, por lo que la instrucción podría ser del estilo:
    Image img = Toolkit.getDefaultToolkit().getImage("foto.gif");
    La clase Image es la que se utiliza para representar imágenes genéricas en Java.
  2. Utilizar el método showImage de Graphics. Este método esta sobrecargado tal y como vimos en el punto anterior.

Ejemplo: Vamos a mostrar una secuencia de imágenes para producir el efecto de una animación.

Saluda.java


import java.awt.*;
import javax.swing.*;

public class Saluda extends Canvas  {
    
    Image t[]; // array de imágenes
    
    public Saluda() {

        t = new Image[10];
        
        for  (int i = 0; i<10; i++) 
           t[i] = Toolkit.getDefaultToolkit().getImage("t"+(i+1)+".gif");
           
    }
    
    public void paint(Graphics g) {
        g.drawImage(t[0],0,0, 200,200, Color.white, null);
        
    }
    
    public void saluda() {
        Graphics g = getGraphics();
        for  (int i = 1; i<10; i++) {
            g.drawImage(t[i],0,0,200,200, Color.white, null);
            
            // perdemos un poco de tiempo para que se vea
            try{ Thread.sleep(150);} catch(Exception e) {}
        }       
        repaint();
        
    }
    
}

Ventana.java



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

public class Ventana extends Frame {

      public Ventana() {    
        // titulo, estilo, tamaño y posición iniciales        
        setTitle("saluda a los señores");        

       // ventana centrada
        Dimension d = Toolkit.getDefaultToolkit().getScreenSize();
        int ancho=200, alto=250;        
        setSize(ancho, alto);
        setLocation(d.width/2-ancho/2,d.height/2-alto/2);            
       
       // el lienzo irá en el centro de la pantalla
       Saluda lienzo = new Saluda();
       add(lienzo);
       
       // el botón con su escucha correspondiente
       Button boton = new Button("saluda!");
       
       Escucha e = new Escucha(lienzo);
       boton.addActionListener(e);
       
       add(boton, BorderLayout.SOUTH);
        
        // añadimos el "listener" para cerrar la ventana
        addWindowListener(new ParaAcabar());        
        
        // la mostramos   
        setVisible(true);        

    }
}


// clase para cerrar la ventana
class ParaAcabar extends WindowAdapter {
    public void windowClosing(WindowEvent e) {        
       System.exit(0);    
     }
}


// escucha del boton
class Escucha implements ActionListener {
      Saluda lienzo;      
      
      public Escucha(Saluda lienzo) {
        this.lienzo = lienzo;
      }
      
      public void actionPerformed(ActionEvent e) {
        lienzo.saluda();
      }
}

Principal.java


public class Principal {
    public static void main(String[] args) {
        new Ventana();
    }   
}

6.5 Un poco de animación

Vamos a mostrar por medio de un ejemplo un par de técnicas sencillas que se pueden utilizar para mejorar animaciones de gráficos. El ejemplo consiste en una pelota que se mueve rebotando contra las paredes de la ventana.
Versión inicial
La idea es, como siempre, sobreescribir la clase paint del lienzo para que dibuje un circulo (la pelota), pero de forma que la posición de la pelota dependa de unas variables x, y, y escribir un método que modifique estas variables y obligue al lienzo a repintarse para simular la animación.

Principal.java



public class Principal {
    static public void main(String [] args) {
        new Ventana();
    }
}

Ventana.java




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

public class Ventana extends Frame {

      public Ventana() {    
        // titulo, estilo, tamaño y posición iniciales        
        setTitle("Animación - V1");        

       // ventana centrada
        Dimension d = Toolkit.getDefaultToolkit().getScreenSize();
        int ancho=400, alto=250;        
        setSize(ancho, alto);
        setLocation(d.width/2-ancho/2,d.height/2-alto/2);            
       
       // el lienzo irá en el centro de la pantalla
       CorrePelota lienzo = new CorrePelota();
       add(lienzo);
               
        // añadimos el "listener" para cerrar la ventana
        addWindowListener(new ParaAcabar());        
        
        // la mostramos   
        setVisible(true);        

        // buclew de animación
        for (long i=0; i<10000000; i++)  {
              // mover la pelota
             lienzo.correCorre();  
             // perder el tiempo
              try { Thread.sleep(10); } catch(Exception e) {}
         }          
    }
}

// clase para cerrar la ventana
class ParaAcabar extends WindowAdapter {
    public void windowClosing(WindowEvent e) {        
       System.exit(0);    
     }
}


CorrePelota.java


import java.awt.*;

public class CorrePelota extends Canvas {
    private final int radio = 30;
    
    int x = 20,y = 20; // pos. inicial
    int incX = 1, incY = 1; // dir. inicial
    
    public CorrePelota() {
        setBackground(Color.black);
        setForeground(Color.yellow);
                
    }
        
    public void correCorre() {
        
        // calculamos la nueva posición de la pelota
        x += incX; y += incY;
        
        // si hay choque invertimos la dirección
        if (x>370) 
            incX = -((int) (Math.random()*2+1));
        if (x<0)    
            incX = (int) (Math.random()*2+1);
        if (y>190) 
            incY = -((int) (Math.random()*2+1));
        if (y<0)    
            incY = (int) (Math.random()*2+1);
                
        repaint();        
    }
    
    public void paint(Graphics g) {        
        g.fillOval(x,y,radio,radio);                         
    }
}


El programa funciona, pero la pelota al moverse tiene un extraño "parpadeo". Vamos a ver como solucionarlo.

Yo me lo pinto y yo me lo borro
El problema es que antes de pintar el nuevo dibujo debe borrarse el anterior; y de eso se encarga update, que antes de llamar a paint borra el lienzo completo.
Como esto tarda en hacerse en ocasiones el refresco de pantalla "pilla" a la animación justo después de borrar la figura pero antes de volver a dibujarla y eso provoca el parpadeo. Una solución es impedir que update() borre la figura anterior y en su lugar hacerlo nosotros, que en lugar de borrar todo el gráfico sólo borraremos la figura anterior.
Para hacer esto sólo tenemos que modificar la clase CorrePelota:

CorrePelota.java


import java.awt.*;

public class CorrePelota extends Canvas {
    private final int radio = 30;
    
    int x = 20,y = 20; // pos. inicial
    int incX = 1, incY = 1; // dir. inicial
    int antx, anty; // posición antigua de la pelota
    
    public CorrePelota() {
        setBackground(Color.black);
        setForeground(Color.yellow);                
    }
        
    public void correCorre() {
        
        // guardamos la posición antigua para borrarla
        antx = x; anty = y;
        
        // calculamos la nueva posición de la pelota
        x += incX; y += incY;
        
        // si hay choque invertimos la dirección
        if (x>370) 
            incX = -((int) (Math.random()*2+1));
        if (x<0)    
            incX = (int) (Math.random()*2+1);
        if (y>190) 
            incY = -((int) (Math.random()*2+1));
        if (y<0)    
            incY = (int) (Math.random()*2+1);
                
        repaint();        
    }
    
    public void update(Graphics g) {
        paint(g);
    }
    
    public void paint(Graphics g) {        
        // borramos la anterior
        g.clearRect(antx,anty,radio,radio);

        g.fillOval(x,y,radio,radio);                         
    }
}


Aunque el parpadeo disminuye ligeramente sigue existiendo; se tarda demasiado en dibujar el óvalo.
Doble buffer
La idea es no dibujar ni borrar directamente en la pantalla, sino en un objeto de tipo Image que nos hará de "buffer" o de copia del lienzo. Una vez que hayamos dibujado en el objeto sólo tendremos que volcarlo a pantalla con drawImage. Así el usuario no podrá ver el proceso de borrado y dibujado; sólo la figura en la nueva posición.
La nueva versión de la clase CorrePelota es:

CorrePelota.java


import java.awt.*;

public class CorrePelota extends Canvas {
    private final int radio = 30;
    
    int x = 20,y = 20; // pos. inicial
    int incX = 1, incY = 1; // dir. inicial
    int antx, anty; // posición antigua de la pelota
    
    // el doble buffer
    Image copia;
    Graphics gCopia;
    boolean primeraVez = true;
    
    public CorrePelota() {
        setBackground(Color.black);
        setForeground(Color.yellow);
    }

        
    public void correCorre() {
        // guardamos la posición antigua para borrarla
        antx = x; anty = y;
        
        // calculamos la nueva posición de la pelota
        x += incX; y += incY;
        
        // si hay choque invertimos la dirección
        if (x>370) 
            incX = -((int) (Math.random()*2+1));
        if (x<0)    
            incX = (int) (Math.random()*2+1);
        if (y>190) 
            incY = -((int) (Math.random()*2+1));
        if (y<0)    
            incY = (int) (Math.random()*2+1);
                
        repaint();        
    }
    
    public void update(Graphics g) {
        
        // para que la pinte
        paint(g);
    }
    
    public void paint(Graphics g) {        
         if (primeraVez) {
            copia = createImage(400,250);
            gCopia = copia.getGraphics();           
            primeraVez = false;
         }
         gCopia.clearRect(antx,anty,radio,radio);        
         gCopia.fillOval(x,y,radio,radio);                                 
         g.drawImage(copia,0,0,this);
    }
}




Observación: En swing existen métodos específicos para tratar el doble buffer que permiten mostrar animaciones de mejor calidad.


7.- Applets

7.1 ¿Qué es un Applet?
7.2 Páginas WEB
7.3 Vida (y muerte) de un Applet
7.4 Ejemplo

7.1 ¿Qué es un Applet?

Un applet es un programa Java que se ejecuta dentro de una página html. El código (.class) viaja junto con la página al ordenador del usuario, donde es ejecutado por la máquina virtual del explorador correspondiente.

Observación: Para que se pueda ver el applet dentro de la página hay que tener activada la máquina virtual de Java del explorador
Los applets son especialmente utilizados en páginas de tipo científico y pedagógicas, para ilustrar conceptos, simular experimentos. Un ejemplo típico es cuando en nuestra página queremos mostrar un gráfica que depende de ciertos parámetros introducidos por el usuario.

7.2 Páginas WEB

Páginas html
Una página WEB es, normalmente un texto escrito en el lenguaje html. La forma más simple de una página en html es

pagina.html


<html>
<head>
    <title> Una página </title>
</head >
<body >

Esta es una página mínima 
</body>
</html>

Podemos escribir este texto en un fichero (por ejemplo con el block de notas), grabarlo y abrirlo con el explorador. En general una página html puede contener: Sin embargo no puede incluir acciones dinámicas, como por ejemplo pedir datos al usuario, procesarlos y mostrar el resultado, o mostrar una animación
Incluyendo Java en páginas WEB
La llamada al código java se introduce mediante el delimitador html applet


<html>
<head>
    <title> Un mini-applet </title>
</head >
<body >

Aquí incluimos una llamada al applet: <p>
<applet code=“Mini.class" width=150 Height=25></applet> <p>
Y luego seguimos con el texto de la página

</body>
</html>

El resultado es:


Importante: El código java compilado (Mini.class en el ejemplo) debe encontrarse en la misma carpeta que la página html.

7.3 Vida (y muerte) de un Applet

Para escribir un applet debemos escribir una clase que herede de la clase Applet (import java.applet.*). Hay 4 métodos en esta clase que nos interesan:
  1. init(): Inicializa el applet. Es en esta función (y no en la constructora) donde debemos crear todos los componentes del applet e insertarlos.
  2. start(): se llama una vez creado el applet, para que comience la ejecución. Por ejemplo en una animación sería start quien comenzaría el movimiento. En este punto conviene ser cuidadosos: si start entra en un bucle sin fin puede dejar "colgada" la página. para solucionar esto se utilizan hebras (Threads) que se ejecutan de forma concurrente sin bloquear la página.
  3. stop(): Se ha detenido el applet. Se puede volver a iniciar con start().
  4. destroy(): Limpieza final y eliminación del applet.


7.4 Ejemplo

El siguiente ejemplo mueve un dibujo ''cab.gif'' por la pantalla. Usa hebras (que no han sido explicadas en clase: interfaz Runnable y función run()).



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

public class Imagen extends Applet implements Runnable{
  Image coche;
  int x=600,y=20;
  Thread corre = new Thread(this);


  //Inicializar el applet
  public void init() {
    MediaTracker tracker = new MediaTracker(this);      
    coche = getImage(getCodeBase(),"cab.gif");
    tracker.addImage(coche, 1);
    try { tracker.waitForAll();} catch(Exception e) {e.printStackTrace();}    
    this.setBackground(Color.white);

  }


  public void run() {
    try {
      Thread.sleep(1000);
      } catch (Exception e) {;}

  while (true) {
    repaint();
    try {
      Thread.sleep(40);
      } catch (Exception e) {;}
    }
  }

  public void start() {
    corre.start();
    }

  public void paint(Graphics g) {
    update(g);
    }

  public void update(Graphics g) {
      x--;
      g.drawImage(coche,x,y,70,50,Color.white,this);
      if (x==-70) x = 600; // volvemos a empezar
    }
}


8.- Diálogos y Mensajes

8.1 La clase Dialog
8.2 La clase FileDialog
8.3 La clase JOptionPane

8.1 La clase Dialog

Los diálogos son ventanas "hijas" de la ventana principal que se utilizan normalmente para mostrar información, errores, o pedir confirmación. El ejemplo más habitual es la ventana con el mensaje "Desea salir de la aplicación?" con los botones de aceptar y cancelar. Hay dos tipos de diálogos: Dado que los diálogos son un tipo especial de ventanas, no tenemos que describir sus métodos, tan sólo sus constructoras:
Constructoras

8.2 La clase FileDialog

Es un caso especial de diálogo modal orientado a la selección de ficheros (a menudo asociados a opciones del estilo de "Abrir" o "Guardar como"). Hay que tener en cuenta que el diálogo sólo selecciona nombres de ficheros; no abre ni guarda nada.
Constructoras
La constructora más completa tiene la sintaxis:

FileDialog(Frame vent, String msg, int mode)


Donde vent es la ventana madre, msg es el título del cuadro de diálogo y mode debe ser o bien FileDialog.LOAD si el diálogo se utiliza para abrir un fichero o FileDialog.SAVE si se utiliza para grabar un fichero.
Métodos
Además de estos métodos es interesante conocer la constante File.separator que indica el separador de directorios (la barra inclinada hacia la derecha o la izquierda dependiendo del sistema operativo).
El siguiente ejemplo ilustra como se puede utilizar este diálogo para abrir un fichero:


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

public class Ventana extends Frame {
    
public Ventana() {
  setTitle("Entrada de datos");
  setLayout(new FlowLayout());
  setSize(300,100);
  setLocation(300,200);
  Label etiqInfo = new Label("Fichero seleccionado: ");
  Label etiqSel = new Label();
  add(etiqInfo);
  add(etiqSel);
        
  // añadimos el "listener" para cerrar la ventana
  addWindowListener(new ParaAcabar());        
              
  // la mostramos   
   setVisible(true);        

  // creamos un dialogo para abrir un fichero
  FileDialog fd = new FileDialog(this, "Abrir...",FileDialog.LOAD);
  
  // al abrir el dialogo, como es modal, la aplicación queda 
  // bloqueada
  fd.setVisible(true);
  
  String fname = fd.getFile();
  
  // si el usuario ha cancelado el dialogo se devuelve null
  if (fname != null) {
     String fdir = fd.getDirectory();
     String name = fdir + fname;
     // mostramos el nombre elegido en la ventana
     etiqSel.setText(name);
  
  } // if                      
 }
}

// clase para cerrar la ventana
class ParaAcabar extends WindowAdapter {
    public void windowClosing(WindowEvent e) {        
       System.exit(0);    
     }
}

8.3 La clase JOptionPane

Los diálogos más comunes son los diálogos modales que se utilizan para presentar información, o bien informar de un error, y que incluyen un botón Aceptar o bien botones Aceptar y Cancelar. Por eso en swing se incluyo una clase especial para representar este tipo de diálogos, la clase JOptionPane, que resulta muy útil a la hora de presentar información debido a su sencillez.

Observación: Es posible (y bastante normal) mezclar en una misma aplicación componentes awt con componentes swing.
Los principales métodos de esta clase son cuatro métodos estáticos: También hay 5 constantes que pueden utilizarse para indicar el tipo de mensaje que se está mostrando. La selección de la constante adecuada influirá en el icono que se presentará en la ventana modal: Por último, aunque podemos incluir en el diálogo los botones que deseemos, hay 4 constantes que definen los conjuntos de botones más usuales: El primero muestra sólo un botón de aceptar (el significado del resto es obvio a partir del nombre). Vamos a ver algunos ejemplos en el siguiente programa:

import java.awt.*;
import java.awt.event.*;
import javax.swing.*; // para JOptionPane

public class Ventana extends Frame {
    
public Ventana() {
  setTitle("Entrada de datos");
  setLayout(new FlowLayout());
  setSize(200,100);
  setLocation(300,200);
        
  // el primer argumento es la ventana madre,
  // el segundo el mensaje a mostrar, el tercero el titulo
  // de la ventana y el cuarto el tipo de dialogo
  JOptionPane.showMessageDialog(this, 
    "El fichero se ha grabado correctamente",
     "informacion", JOptionPane.INFORMATION_MESSAGE);

   // muestra el dialogo con un simbolo de interrogacion
   if (JOptionPane.showConfirmDialog(null, 
    "desea salir de la aplicacion?", "aviso", 
    JOptionPane.YES_NO_OPTION) == 0) 
       System.exit(0);

   // igual pero con un simbolo de informacion
   JOptionPane.showConfirmDialog(this, 
     "Desea continuar?", "informacion",
     JOptionPane.YES_NO_CANCEL_OPTION, 
     JOptionPane.INFORMATION_MESSAGE);

    // para pedir un dato
    String nombre = 
       JOptionPane.showInputDialog("Su nombre:"); 

    // elegir un dato de una lista
   Object[] valores = { "Norte","Sur", "Este","Oeste" };
   Object selectedValue = 
    JOptionPane.showInputDialog(
             null, // ventana madre
            "Elige una direccion", // mensaje
            "Direcciones", // titulo
             JOptionPane.INFORMATION_MESSAGE, // tipo mensaje
             null, // icono
             valores, // valores
             valores[0] // valor marcado inicialmente
             );
                            
        // añadimos el "listener" para cerrar la ventana
        addWindowListener(new ParaAcabar());        
   }
}

// clase para cerrar la ventana
class ParaAcabar extends WindowAdapter {
    public void windowClosing(WindowEvent e) {        
       System.exit(0);    
     }
}
Este programa muestra las siguientes ventanas de diálogo (consecutivamente, cada una tras cerrar la anterior):






Página Principal-


2004-2006 Rafael Caballero