Objetivo:Introducir al estudiante en la programación de interfaces graficas de usuario utilizando Java
A quien está dirigido:Estudiantes con conocimientos básicos de programación orientada a objetos con Java, en particular deben de conocer los conceptos de:
  • Clases
  • Objetos
  • Herencia
  • Clases abstractas e interfaces
Competencia esperada: Al concluir el presente tutorial el estudiante debe de comprender que partes de la API de Java puede utilizar para construir interfaces graficas de usuario (GUIs), y la forma en que Java implementa el patrón observer.
Estilo:Guía paso a paso, el lector encontrará varias versiones del código, hacemos esto para enfatizar lo que hace cada parte del código.

Interfaces graficas en Java

Para empezar la discución de como realizar interfaces graficas en Java, debemos primero realizar algunas aclaraciones:

Para mayor claridad construiremos un ejemplo completo poco a poco; sin embargo debemos advertir que se asume que el lector está familiarizado con conceptos como: clase, objeto, mensaje, constructora, referencias, imports, herencia e interfaces.
Construiremos una calculadora de senos, cosenos y tangentes de ángulos.

Construcción de una ventana

1 import javax.swing.*;
2
3 public class Trigo extends JFrame{
4 
5 }

En la primera línea simplemente importamos el paquete donde está definida la clase JFrame. Un JFrame es la clase que representa al tipo "Ventana". En otras palabras un "JFrame" es una ventana del sistema operativo. Como nuestra clase hereda (extienda) a la clase JFrame, Trigo será por lo tanto también un JFrame es decir una ventana.
Para comprobar esto añadiremos a nuestra clase un método "main", quedando ahora de la siguiente forma:

1  import javax.swing.*;
2 
3  public class Trigo extends JFrame{
4    public static void main(String[] arg){
5      Trigo miAplicacion = new Trigo();
6      miAplicacion.setBounds(10,10,200,200);
7      miAplicacion.pack();
8      miAplicacion.setVisible(true);
9    }
10  }

Todos los mensajes enviados al objeto "miAplicacion" son métodos heredados de "JFrame". El primer método "setBounds" establece la posición inicial de la ventana en la pantalla y sus dimensiones. El segundo, "pack", en este momento no era realmente necesario ya que la ventana está vacia; pero quisimos incluirlo para no tener que modificar el main más tarde. Este método optimiza la disposición de los elementos dentro de la ventana. Y el último mensaje es simplemente para ordenarle a la venta que se haga visible. Después de compilar y ejecutar el código verá en pantalla algo como lo mostrado en la figura 1.


figura 1

Agregando un botón

La ventana (JFrame) sólo puede contener dos cosas: un menu, y un panel. Cada uno de estos elementos con su espacio ya determinado. Por el momento no utilzaremos el menú. Los botones y demás elementos a agregar no se colocan directamente en la ventana, sino en un objeto interno, el "Panel". Este objeto interno a su vez utiliza otro objeto interno conocido como LayoutManager, que se encarga de controlar la disposción de los elementos dentro del panel.
Existen muchos LayoutManagers, cada uno con su propia lógica para la dispocisión de los elementos, aqui ejemplificaremos el uso de un par de ellos. Modificamos nuestra clase para agragar un botón quedando entonces como:

1   import javax.swing.*;
2   import java.awt.*;
3
4   public class Trigo extends JFrame{
5     private JButton b1;
6
7     public Trigo(){
8       b1 = new JButton("seno");
9       this.getContentPane().add(b1,BorderLayout.NORTH);
10    }
11    public static void main(String[] arg){
12      Trigo miAplicacion = new Trigo();
13      miAplicacion.setBounds(10,10,200,200);
14      miAplicacion.pack();
15      miAplicacion.setVisible(true);
16    }
17  }

Después de compilar y ejecutar el código tendremos algo como:


Figura 2

Ahora el tamaño de la ventana cambio y se adapto al único elemento que contiene (el botón). El significado del código agregado es el siguiente:
En la línea 2 importamos el paquete donde se encuentra declarado la clase "BorderLayout". Incluimos un "JButton" (un botón) como atributo de nuestra clase "Trigo" y agregamos además una constructora, que es donde creamos el objeto "b1" para después añadirlo al panel de la ventana con la instrucción de la línea 9. Esta última instrucción merece la pena una explicación ya que la utilizaremos bastante a través del ejemplo.
La parte que a continuación se muestra en azul se encarga de obtener una referencia al panel de la ventana.

this.getContentPane().add(b1,BorderLayout.NORTH);

Mientras que la parte que se encuentra en rojo es la que le ordena al Panel que agregue el botón, además de que le ordena que para añadirlo utilice un "LayoutManager" de tipo "BorderLayout". Este tipo de "LayoutManager" divide al panel en 5 regiones (Norte, Sur, Este, Oeste y Centro); donde cada región puede contener sólo un elemento. En este caso añadimos el botón a la zona "Norte" y para ordenarlo utilizamos la constante de clase "NORTH" definida en la clase "BorderLayout".

De forma analoga incluiremos un campo de texto "JTextField" en la zona "Sur" de nuestro panel.

1   import javax.swing.*;
2   import java.awt.*;
3
4   public class Trigo extends JFrame{
5     private JButton b1;
6     private JTextField campo1;
7
8     public Trigo(){
9       b1 = new JButton("seno");
10      campo1 = new JTextField();
11      this.getContentPane().add(b1,BorderLayout.NORTH);
12      this.getContentPane().add(campo1,BorderLayout.SOUTH);
13    }
14    public static void main(String[] arg){
15      Trigo miAplicacion = new Trigo();
16      miAplicacion.setBounds(10,10,200,200);
17      miAplicacion.pack();
18      miAplicacion.setVisible(true);
19    }
20  }

Figura 3

Para que observe bien la definición de zonas hechas en el panel por el "BorderLayout" aumente la ventana y verá que el centro queda un espacio vacío (la parte gris), ya que el botón se encuentra al Norte y el campo de Texto al Sur.


Figura 4

Añadiendo acciones al boton

Hasta ahora sólo hemos mostrado como agregar elementos a la ventana, ya tenemos dos elementos un botón y un campo de texto. Sin embargo, el botón al ser oprimido no produce ninguna acción. El botón es el ente que genera eventos (es oprimido) pero no existe hasta el momento ningún objeto interesado en sus eventos. Es decir que no existe ningún oyente de los eventos. Para registrar un oyente el botón cuenta con el método addActionListener. La firma del este método es:

void addActionListener(ActionListener evento)

Esto significa que el elemento a registrarse como oyente de los eventos del botón debe ser un "ActionListener". Para nuestro ejemplo, lo que deseamos es que la ventana que contiene al botón "miAplicacion" sea la que se registre con el botón como oyente de eventos. El problema con esto es que "miAplicacion" es de tipo "Trigo" o bien de tipo "JFrame" pero no es un "ActionListener". Para que las instancias de tipo "Trigo" también sean de tipo "ActionListener" tenemos que hacer que la clase "Trigo" implemente la interfaz "ActionListener"; porque de forma analoga a lo que sucede con la herencia (extends) cuando una clase implementa una interfaz se establece una relación "es un" entre ellas. Para anunciar que "Trigo" implementara la interfaz "ActionListener" modificamos la definición de la clase de la siguiente forma:

public class Trigo extends JFrame implements ActionListener

El hecho de afirmar que la clase "Trigo" implementa la interfaz "ActionListener" implica que ahora esta clase contiene todos los métodos definidos en la interfaz. Cabe recordar que todos los métodos de una interfaz son abstractos por lo tanto o sobre-escribimos los métodos agregandoles un cuerpo o bien tendremos que declarar a nuestra clase "Trigo" como abstracta. Como en nuestro ejemplo no deseamos que nuestra clase "Trigo" sea abstracta implementaremos todos los métodos de la interfaz "ActionListener". Afortunadamente, esta interfaz sólo define un método:

public void actionPerformed(ActionEvent e)

Este será el método que autómaticamente será ejecutado cuando el botón le avise de su evento a la ventana. Para fines de este ejemplo deseamos que la acción a realizar cuando se oprima el botón sea que en el campo de texto aparezca un letrero en que se diga cuantas veces ha sido oprimido el botón. Por lo tanto incluiremos una nueva variable (para contar el número de clicks sobre el botón, linea 8), el método "actionPerformed" (de la linea 18 a la 21) y cambiaremos la deficinición de la clase (linea 5). Ademas de registrar a la ventana como oyente de los eventos del botón (linea 12). Quedando por lo tanto nuestra clase como:

      1 import javax.swing.*;
      2 import java.awt.*;
      3 import java.awt.event.*;
      4
      5 public class Trigo extends JFrame implements ActionListener{
      6   private JButton b1;
      7   private JTextField campo1;
      8   private int numClicks = 0;
      9
     10   public Trigo(){
     11     b1 = new JButton("seno");
     12     b1.addActionListener(this);
     13     campo1 = new JTextField();
     14     this.getContentPane().add(b1,BorderLayout.NORTH);
     15     this.getContentPane().add(campo1,BorderLayout.SOUTH);
     16   }
     17
     18   public void actionPerformed(ActionEvent e){
     19     numClicks++;
     20     campo1.setText(numClicks+"");
     21   }
     22
     23   public static void main(String[] arg){
     24     Trigo miAplicacion = new Trigo();
     25     miAplicacion.setBounds(10,10,200,200);
     26     miAplicacion.pack();
     27     miAplicacion.setVisible(true);
     28   }
     29 }

Prueba ahora dando clicks al botón.

Armando la calculadora

El ejemplo anterior sirve para mostrar los conceptos básicos, pero la promesa era hacer una calculadora de funciones trigonometricas. El primer paso que realizaremos será modificar un poco la interfaz, dejaremos el TextField como medio de entrada para el ángulo del que deseamos saber el seno, y añadiremos otro TextField para reportar el resultado del cálculo realizado. Tambien incluiremos unas etiquetas para marcar la entrada y salida de nuestra calculadora. Eliminaremos la variable que cuenta los clicks y para mostrar que los Paneles pueden contener otros paneles utilizaremos un par de paneles para ir organizando nuestra interfaz.

      1 import javax.swing.*;
      2 import java.awt.*;
      3 import java.awt.event.*;
      4
      5 public class Trigo extends JFrame implements ActionListener{
      6   private JButton b1;
      7   private JTextField campo1;
      8   private JTextField campo2;
      9   private JLabel etq1;
     10   private JLabel etq2;
     11   private Panel panelEntrada, panelSalida;
     12   private JPanel panelDeLaVentana;
     13
     14   public Trigo(){
     15     //Creamos el boton
     16     b1 = new JButton("seno");
     17
     18     //Registramos a la ventana como oyente
     19     b1.addActionListener(this);
     20
     21     //Creamos las etiquetas
     22     etq1 = new JLabel("Angulo: ");
     23     etq2 = new JLabel("Resultado: ");
     24
     25     //Creamos los campos de Texto
     26     campo1 = new JTextField();
     27     campo2 = new JTextField();
     28
     29     //Cambiamos la propiedades de los TextFields
     30     campo2.setEditable(false);
     31     campo2.setColumns(10);
     32     campo2.setBackground(Color.lightGray);
     33     campo1.setColumns(10);
     34
     35     //Creamos los paneles auxiliares
     36     panelEntrada = new Panel();
     37     panelSalida = new Panel();
     38
     39     //Obtenemos la referencia al panel principal
     40     panelDeLaVentana = (JPanel)this.getContentPane();
     41
     42     //Agregamos los componentes del panel de entrada
     43     panelEntrada.add(campo1,BoxLayout.X_AXIS);
     44     panelEntrada.add(etq1,BoxLayout.X_AXIS);
     45
     46     //Agregamos los componentes del panel de salida
     47     panelSalida.add(campo2,BoxLayout.X_AXIS);
     48     panelSalida.add(etq2,BoxLayout.X_AXIS);
     49
     50     //Agregamos todo al panel Principal
     51     panelDeLaVentana.add(panelEntrada,BorderLayout.NORTH);
     52     panelDeLaVentana.add(b1,BorderLayout.CENTER);
     53     panelDeLaVentana.add(panelSalida,BorderLayout.SOUTH);
     54   }
     55
     56   public void actionPerformed(ActionEvent e){
     57   }
     58
     59   public static void main(String[] arg){
     60     Trigo miAplicacion = new Trigo();
     61     miAplicacion.setBounds(10,10,200,200);
     62     miAplicacion.pack();
     63     miAplicacion.setVisible(true);
     64   }
     65 }

Ahora despues de compilar y ejecutar tendremos:


Figura 5

En la discusión anterior habiamos mencionado que un "BorderLayout" sólo te permite poner una sola cosa en cada una de las regiones. Esta regla se sigue cumpliendo, el "BorderLayout" del panel principal contiene una sola cosa en la region Norte (linea 51); contiene el Panel llamado "panelEntrada", es este panel el que a su vez contiene dos cosas una etiqueta y un campo de texto (lineas 43 y 44) utilizando su propia lógica para la disposición de los elementos, en este caso utilizando un BoxLayout. Para la parte de la salida es algo similar, pero observe además que la región del centro del panel principal "panelDeLaVentana" esta ocupada por el botón.
De nueva cuenta una vez con la interfaz organizada procederemos a darle funcionalidad al botón. Parte del trabajo ya está hecho:

Por lo tanto sólo nos falta esribir la lógica de ejecución para responder al evento del botón. Lo que deseamos ahora ejecutar como respuesta al evento puede resumirse en 3 sencillos pasos:

  1. Leer el ángulo proporcionado en el "TextField" de entrada (campo1).
  2. Calcular el seno del ángulo proporcionado
  3. Escribir el resultado en el campo de texto correspondiente (campo2)

Al observar la API podemos darnos cuenta que entre el hecho de leer la entrada y calcular el seno hay un paso adicional, porque el "TextField" reporta su contenido como un "String" mientras que la función para calcular el seno, definida en el paquete Math de Java, utiliza un "double" como entrada. Por lo tanto, tendremos que convertir la cadena de caracteres en un double para después calcular el seno. Para realizar esta conversión utilizaremos el método "parseDouble" definido en la "Wrapper class" Double.

Double.parseDouble(String val)

Agregando estas modificaciones el código queda como:

      1 import javax.swing.*;
      2 import java.awt.*;
      3 import java.awt.event.*;
      4
      5 public class Trigo extends JFrame implements ActionListener{
      6   private JButton b1;
      7   private JTextField campo1;
      8   private JTextField campo2;
      9   private JLabel etq1;
     10   private JLabel etq2;
     11   private Panel panelEntrada, panelSalida;
     12   private JPanel panelDeLaVentana;
     13
     14   public Trigo(){
     15     //Creamos el boton
     16     b1 = new JButton("seno");
     17
     18     //Registramos a la ventana como oyente
     19     b1.addActionListener(this);
     20
     21     //Creamos las etiquetas
     22     etq1 = new JLabel("Angulo: ");
     23     etq2 = new JLabel("Resultado: ");
     24
     25     //Creamos los campos de Texto
     26     campo1 = new JTextField();
     27     campo2 = new JTextField();
     28
     29     //Cambiamos la propiedades de los TextFields
     30     campo2.setEditable(false);
     31     campo2.setColumns(10);
     32     campo2.setBackground(Color.lightGray);
     33     campo1.setColumns(10);
     34
     35     //Creamos los paneles auxiliares
     36     panelEntrada = new Panel();
     37     panelSalida = new Panel();
     38
     39     //Obtenemos la referencia al panel principal
     40     panelDeLaVentana = (JPanel)this.getContentPane();
     41
     42     //Agregamos los componentes del panel de entrada
     43     panelEntrada.add(campo1,BoxLayout.X_AXIS);
     44     panelEntrada.add(etq1,BoxLayout.X_AXIS);
     45
     46     //Agregamos los componentes del panel de salida
     47     panelSalida.add(campo2,BoxLayout.X_AXIS);
     48     panelSalida.add(etq2,BoxLayout.X_AXIS);
     49
     50     //Agregamos todo al panel Principal
     51     panelDeLaVentana.add(panelEntrada,BorderLayout.NORTH);
     52     panelDeLaVentana.add(b1,BorderLayout.CENTER);
     53     panelDeLaVentana.add(panelSalida,BorderLayout.SOUTH);
     54   }
     55
     56   public void actionPerformed(ActionEvent e){
     57     double angulo = Double.parseDouble(campo1.getText());
     58     double resultado = Math.sin(angulo);
     59     campo2.setText(resultado+"");
     60   }
     61
     62   public static void main(String[] arg){
     63     Trigo miAplicacion = new Trigo();
     64     miAplicacion.setBounds(10,10,200,200);
     65     miAplicacion.pack();
     66     miAplicacion.setVisible(true);
     67   }
     68 }

Es hora de probar la calculadora, pero no hay que olvidar que de acuerdo a la API, la función seno de la clase Math asume que el valor representa un ángulo en radianes.

Licencia de Creative Commons
Tutorial GUI Java by Francisco Ignacio Chávez Castañeda is licensed under a Creative Commons Reconocimiento-CompartirIgual 3.0 Unported License.