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:
|
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. |
Para empezar la discución de como realizar interfaces graficas en Java, debemos primero realizar algunas aclaraciones:
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.
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:
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 }
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.
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.
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:
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:
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.