14.sesión Java - GUI
14.sesión Java - GUI
Las aplicaciones informáticas no están al margen de esto y hay que reconocer que una buena
presencia de la misma (intuitiva, accesible, ágil, etc...) hace que la experiencia de usuario sea más
satisfactoria. Botones, ventanas, listas desplegables, cajas de texto, etc..., forman parte del arsenal
piezas con las que armar un buen interfaz que haga cómodo el manejo de una aplicación.
Y concretamente en Java, el camino recorrido hasta ahora en esta materia ha tenido sus más
y sus menos. Todo por un detalle que es cara a veces y cruz las otras: la JVM. La
cuasiindependencia que disfruta Java para ofrecer multiplataformidad, tiene sus peajes. Por eso, al
abordar la implementación de un interfaz gráfico de usuario (GUI), hay que tener claro que está
condicionado por la interacción del sistema operativo en el que corra el programa. Porque este SO
es el que marca cómo se van a ver los componentes (ventanas, botones, listas desplegables, cajas de texto, etc..) en
pantalla, al margen del trajín que comporta "molestar" al SO para esas labores.
En esta secuencia nos centraremos en la tecnología Swing, dejando JavaFX para que sea
abrodado desde el módulo de Entornos de Desarrollo.
Swing
Swing es el nombre que se le quedó a esta tecnología, aunque en realidad es como se llamó
al proyecto dedicado a la construcción de componentes gráficos para Java casi finalizando el siglo
XX. La tecnología fue denominada inicialmente como JFC (Java Foundation Classes) siendo los
componentes Swing GUI una parte de las características de esa JFC. Su filosofía se basa en poner a
disposición del desarrollador contenedores y componentes gráficos así como un modelo de eventos
para gestionar la interactividad entre ellos y el usuario. Todo ello en forma de clases, interfaces y
adaptadores.
Y tan importante como conocer las clases que soportan los componentes GUI, es aprender la
técnica de construcción de los interfaces gráficos.
Ahora al principio jugaremos un poco con interfaces simples que nos servirán para
familiarizarnos con algunos componentes, dejando sentada la idea de que el año que viene, en el
módulo de Desarrollo de Interfaces, se implementarán GUIs más profesionales.
Manos a la obra
1) JFrame marco = new JFrame("Mi primer interfaz GUI"); con esta instancia de JFrame, generas la ventana
principal de la aplicación (que viene con sus botones de minimizar, ampliar y cerrar) a la que se le puede añadir
un título en la barra superior.
3) marco.setSize(300, 300); con esto estableces el tamaño que tendrá la ventana principal de la
aplicación. Prueba a modificarlo como te parezca.
4) JButton boton1 = new JButton("Pulsa"); con esta instancia de JButton generas un botón, añadiéndole el
letrerito que irá por delante. Prueba a modificar el letrerito.
5) marco.add(boton1); de esta forma, una de las que existen, el botón se añade a la ventana como si lo
pincháramos en un tablón de anuncios. Lo sitúa en medio por defecto.
Ahora trata de crear un segundo botón con letrerito diferente y observa qué ocurre. ¡Vaya!
¿Qué ha pasado? Todavía es pronto para conocer los bajos fondos que operan en una interfaz con
Swing, pero quédate con la copla que, así a pelo, jugar con varios componentes tiene sus problemas.
Ello requerirá de más conocimientos que iremos adquiriendo al ir de ejercicio en ejercicio como las
abejas arrastran el polen de las flores por donde se posan. Porque entre esto y lo de que el botón ha
ocupado todo el marco, ya empiezan los misterios.
De ahí que ahora pasemos a un segundo ejercicio que nos pueda aclarar alguno de estos
contratiempos.
import java.awt.*;
import javax.swing.*;
class GuiSwing2 {
public static void main(String[] args) {
JFrame marco = new JFrame("Mi segundo interfaz GUI");
marco.setSize(400, 300);
marco.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
marco.setLayout(new FlowLayout());
JLabel etiqueta = new JLabel("Nombre: ");
JTextField texto = new JTextField(20);
JButton boton = new JButton("Saludar");
marco.add(etiqueta);
marco.add(texto);
marco.add(boton);
marco.setVisible(true);
}
}
Apenas se ha hecho nada diferente con respecto al código anterior. Pero ahora hemos
incluido tres componentes (etiqueta, campo de texto y botón), y han aparecido los tres. Observando
detenidamente este código caemos en la cuenta de que:
1) JLabel etiqueta = new JLabel("Nombre: "); no sólo de botones viva la interfaz. También existen etiquetas
y esta la forma de generarlacon el texto que se le quiera poner (en este caso "Nombre:").
2) JTextField texto = new JTextField(20); y así se instancia un campo de texto. En este caso con una
limitación de tamaño en pantalla 20 caracteres. Aunque se pueden teclear más de 20 caracteres,
puesto que sólo es una limitación visual. Puedes probar a ponerle sólo 10 caracteres.
4) import java.awt.*; los acomodadores como FlowLayout vienen de herencia del AWT, de ahí que se
ponga esta sentencia en el código. Prueba a comentar este import, a ver qué pasa.
Prueba ahora a estrechar el marco con el ratón. ¿Entiendes mejor lo de FlowLayout? Pues
comenta ahora la línea de código donde se establece este FlowLayout. ¿Qué ocurre ahora? Me temo
que lo mismo que pasó al añadir el segundo botón a GuiSwing1. Prueba entonces de nuevo a
incorporar dos botones a GuiSwing1 pero esta vez añadiendo la línea de código que activa el
FlowLayout.
Nota: por cierto, ahora ya no hay fondo de color metálico degradado. El fondo se quedó
blanco y le he puesto un borde negro a la captura para destacarlo.
Nota: ahora se ve el botón más ajustado a lo que suele verse en cualquier interfaz gráfico.
Eso sí, el botón es meramente decorativo puesto que por mucho que se pulse sobre él, no
realizará ninguna acción.
import javax.swing.*;
class GuiSwing3 {
public static void main(String[] args) {
JFrame marco = new JFrame("Campo, botón y etiqueta");
marco.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
JLabel etiqueta = new JLabel("No tienen actividad");
JPanel panel = new JPanel();
JButton boton = new JButton("Enviar");
JTextField campotexto = new JTextField(16);
panel.add(campotexto);
panel.add(boton);
panel.add(etiqueta);
marco.add(panel);
marco.setSize(300, 300);
marco.setVisible(true);
}
}
De nuevo poco cambio respecto al código anterior. No obstante, hay un actor nuevo en el
partido: exacto, un JPanel. ¿Y eso paqué? Si nos fijamos bien en el código, vemos que una vez
creado el JPanel, los tres componentes han sido incluidos en dicho contenedor. Pero lo más curioso
es que posteriormente, dicho panel se adhiere al marco. Lo que significa que el panel (JPanel) hace
las veces de intermediario con el propósito simplificar el posicionamiento de componentes. En
realidad, alguno estará pensando que no hacía falta porque los componentes ya se las entendían
solos con el marco. A medida que aumente la complejidad de los interfaces se entenderá mejor el
porqué de los paneles. Mientras, vayamos practicando con ellos.
1) JPanel panel = new JPanel(); instancia un panel para recoger componentes en su interior. Pregunta:
¿y cómo puede ser que sin aplicarle al panel ningún Layout haya colocado los componentes uno al
lado del otro y si falta sitio en la línea siguiente? De hecho es la aplicación de un FlowLayout lo
que se observa. Pausa para la intuición... Efectivamente, un JPanel viene dotado por defecto con
un acomodador de componentes tipo FlowLayout.
2) JTextField texto = new JTextField("Texto por defecto", 16); prueba una segunda posibilidad del constructor
del componente JTextField para que aparezca texto en el campo. Advertencia: no machaca al
comenzar a escribir sobre él.
Nota: si un JPanel trae de serie un acomodador FlowLayout, ¿un marco JFrame qué
acomodador tiene asignado por defecto? Eso te toca a ti investigarlo y sacar las
conclusiones de porqué se comportó en GuiSwing1 como se comportó.
Nota: tal como indica la cadena de la etiqueta, los componentes campo de texto y botón no
tienen actividad. Aunque se inserte texto y se pulse el botón, nada sucede.
En algunos interfaces gráficos de ususario se suelen ver botones en los que en vez de texto
aparecen iconos representativos. Pues Swing también sabe hacer eso:
import javax.swing.*;
class GuiSwing4 {
public static void main(String[] args) {
JFrame marco = new JFrame("Mi botón iconizado");
marco.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
marco.setSize(300, 300);
Icon icono = new ImageIcon("src/icono.jpg");
JButton boton1 = new JButton(icono);
marco.setLayout(new FlowLayout());
boton1.setPreferredSize(new Dimension(80, 40));
marco.getContentPane().add(boton1);
marco.setVisible(true);
}
}
1) Icon icono = new ImageIcon("src/icono.jpg"); instancia un elemento ImageIcon pasándole el nombre del
fichero que contiene la imagen que se va a superponer en el botón. En nuestro caso se ha puesto
una imagen con fondo blanco pero puedes añadir una con fondo transparente. Eso sí, habrá que
indicarle en qué carpeta del proyecto IntelliJ IDEA está almacenada.
3) marco.getContentPane().add(boton1); si te fijas no se ha
utilizado un JPanel para adherir el botón. Es por ello que
el botón se adhiere a una especie de panel que los
JFrames tienen incorporados por defecto como pse puede
ver en lal ilustración. Si no se pone, el botón se adhiere por defecto al mismo.
Por cierto, no quería dejar pasar la ocasión para indicar que el tercer contenedor (ya hemos visto
JFrame y JPanel) que Swing tiene en cartera es JWindow. Como muestra sirva el siguiente código que
visualiza en pantalla un marco y una ventana para que se puedan observar sus diferencias (no pongo
captura; fíjate cuando lo ejecutes):
import javax.swing.*;
class GuiSwingCont {
public static void main(String[] args) {
JFrame marco = new JFrame("El marco");
marco.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
marco.setSize(300, 300);
marco.setLocation(100, 100);
JWindow ventana = new JWindow();
ventana.setSize(300, 300);
ventana.setLocation(500, 100);
marco.setVisible(true);
ventana.setVisible(true);
}
}
Ahora hablaremos de los acomodadores (también se les puede llamar organizadores) de componentes.
Dijimos hace un rato que no sólo existía FlowLayout, que era el que tenía por defecto un
contenedor como JPanel. Si investigaste bien, encontrarías que los marcos tenían el suyo de serie
denominado BorderLayout. Cada uno de su padre y de su madre, de ahí que tengan distribución
organizativa y comportamientos diferentes. Y por si fuera poco, se pueden cambiar como ya pudiste
comprobar en el código GuiSwing2 en la sentencia marco.setLayout(new FlowLayout());. Ahora ya sabes
que un marco tiene predeterminados el acomodador BorderLayout y lo que hacía dicha sentencia
era sustituirlo por FlowLayout.
Bien pues ha llegado el momento de conocer los más útiles para nosotros, no sin dejar claro
que la elección del o los acomodadores es una faceta delicada a la hora de construir una interfaz
gráfica. De ahí que haya que conocerlos meridianamente bien porque sino difícilmente se consigue
posicionar los componentes donde uno quiere.
Empezamos por FlowLayout. Es del que más noticias tenemos y cuyo lema tenemos
fresquito: "te los iré poniendo uno al lado del otro en la misma línea (de izquierda a derecha), hasta que
ésta se acabe y entonces te los pondré en la siguiente línea". Por lo que dependerá del tamaño del
marco, la disposición de los componentes. Este ejemplo te lo deja claro:
import javax.swing.*;
class GuiSwingFlow {
public static void main(String[] args) {
JFrame marco = new JFrame("Conociendo FlowLayout");
marco.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
JPanel panel = new JPanel();
for(int i = 0; i < 10; i++) {
panel.add(new JLabel("Etiqueta " + i));
}
marco.add(panel);
marco.setSize(300, 300);
marco.setVisible(true);
}
}
¿Qué puedes hacer para aprender con este código? Estrechar o ensanchar el marco, cambiar
alguna etiqueta por botón. Sustituir el método setSize() por el método pack(). Cómo cambiar la
alineación del panel, por defecto centrada: panel.setLayout(new FlowLayout(FlowLayout.LEFT));. Esto lo pone
a la izquierda como ya imaginas. También conocer como modificar los 5 píxeles de separación
(horizontal y vertical) por defecto entre componentes: panel.setLayout(new FlowLayout(FlowLayout.LEFT, 15, 15)); .
El resto de posibilidades te las dejo a ti, ahora que ya tienes las nociones básicas.
Pasemos ahora a BorderLayout. Sólo sabíamos de éste que era la organización que traía de
serie un marco. Pero desconocíamos cuál es su representación real en pantalla. Consiste en dividir
el contenedor al que se le aplique en cinco zonas: arriba, abajo, izquierda, derecha y centro.
Como puede observarse en el siguiente código, se ha colocado un botón en cada zona y estos
han acabado ocupando todo el espacio, como ya ocurrió en el primer código de esta secuencia. Por
último, resaltar la manera en la que se han establecido las separaciones, horizontal (5) y vertical
(10), en este acomodador de componentes: marco.setLayout(new BorderLayout(5, 10));.
import java.awt.*;
import javax.swing.*;
class GuiSwingBorder {
public static void main(String[] args) {
JFrame marco = new JFrame("Conociendo Border Layout");
marco.setLayout(new BorderLayout(5, 10));
marco.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
JButton boton1, boton2, boton3, boton4, boton5;
boton1 = new JButton("Arriba (NORTH)");
boton2 = new JButton("Abajo (SOUTH)");
boton3 = new JButton("Derecha (EAST)");
boton4 = new JButton("Izquierda (WEST)");
boton5 = new JButton("Enmedio (CENTER)");
marco.add(boton1, BorderLayout.NORTH);
marco.add(boton2, BorderLayout.SOUTH);
marco.add(boton3, BorderLayout.EAST);
marco.add(boton4, BorderLayout.WEST);
marco.add(boton5, BorderLayout.CENTER);
marco.setSize(425, 350);
marco.setVisible(true);
}
}
¿Qué puedes hacer para aprender con este código? Estrechar o ensanchar el marco, cambiar
algún botón por campo de texto. Sustituir el método setSize() por el método pack(). También
conocer como modificar los píxeles de separación (horizontal y vertical) por defecto entre zonas. El resto
de posibilidades te las dejo a ti, ahora que ya tienes las nociones básicas.
Por ahora vamos a dejarlo con el tercer acomodador denominado GridLayout, porque sino
podríamos echar la peonada. A este no lo quiere nadie de inicio, aunque la venganza la sirve fría
puesto que al final la gran mayoría de los interfaces gráficos acaban recurriendo a este acomodador.
Su característica organizacional reside en una rejilla en forma de tabla con tantas filas y columnas
como se le diga.
import java.awt.*;
import javax.swing.*;
class GuiSwingGrid {
public static void main(String[] args) {
JFrame marco = new JFrame("Conociendo GridLayout");
marco.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
JPanel panel = new JPanel();
panel.setLayout(new GridLayout(3, 4, 10, 5));
for(int i = 0; i < 12; i++) {
panel.add(new JButton("Botón " + i));
}
marco.add(panel);
marco.setSize(400, 300);
marco.setVisible(true);
}
}
¿Qué puedes hacer para aprender con este código? Pues lo mismo que vienes haciendo con
los anteriores.
Sólo queda decir que una de las cosas que se acaba aprendiendo a medida que se va
implementando un interfaz detrás de otro es que, por ejemplo, partiendo de un marco con
acomodador BorderLayout, la zona NORTH puede contener varios componentes acomodados por
un FlowLayout y en la zona CENTER sus componentes estén regidos por un GridLayout. Así que
como ves, acaba convirtiéndose en todo un arte lo de construir interfaces.
Aunque sólo hemos conocido por ahora una manera de asociar el cierre de un
marco a una acción, existe alguna más como se puede ver:
JFrame.EXIT_ON_CLOSE: Abandona aplicación
JFrame.DISPOSE_ON_CLOSE: Libera los recursos asociados a la ventana
JFrame.DO_NOTHING_ON_CLOSE: No hace nada
JFrame.HIDE_ON_CLOSE: Cierra la ventana, sin liberar sus recursos
Es por eso, que debemos adentrarnos ahora en el mundillo de los eventos, que es como se
denomina por el territorio Java a las acciones que el usuario propicia al interactuar con los
elementos de una GUI. Podrás comprender que para ello Java ha puesto a disposición de los
desarrolladores, herramientas (clases e interfaces) que dotan de capacidad de respuesta a la aplicación
ante estos eventos que genera dicho usuario.
Estas herramientas se denominan Listeners y Adapters, aunque con el tiempo los Adapters
han ido perdiendo fuelle quedando en la actualidad los Listeners como herramienta principal para
estos menesteres. ¿Y qué es un Listener? Pues el objeto que a modo de controlador aéreo, está
pendiente de que en la aplicación alguien pueda pulsar sobre un botón, elegir una opción de una
lista desplegable o de un grupo de botones de radio. Y claro estar pendiente para luego decirle al
desarrollador, aquí te dejo un espacio para que cuando yo detecte movimiento escribas el código
que entiendes oportuno para lo sucedido.
En las aplicaciones Java que vinimos desarrollando hasta ahora (teclado y pantalla), el método
main() era el que indicaba el orden de ejecución de las operaciones de nuestro programa. Pero
hemos de caer en la cuenta de que en las aplicaciones GUI, el orden en el que se ejecutan las
operaciones dependerá de las acciones que realice el usuario. Gracias a Java, el desarrollador sólo
ha de responsabilizarse en el conocimiento de las acciones que ha de realizar la aplicación, cuándo
se producirán y definir/programar los pertinentes manejadores de eventos, que serán activados
automáticamente cuando sus eventos asociados tengan lugar.
Antes de nada, debes saber que los eventos también son herencia de AWT, por lo que las
aplicaciones que se desarrollen con estas características han de encabezarse con este import:
import java.awt.event.*;
Como bien puedes ver en el siguiente código que muestra un botón, que al ser pulsado,
muestra un mensaje que aparecerá en la ventana de ejecución del entorno donde estés trabajando:
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
class GuiSwingAct1 {
public static void main(String[] args) {
JFrame marco = new JFrame("Swing con Actividad");
marco.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
marco.setLayout(new FlowLayout());
JLabel etiqueta = new JLabel("Pulsa el botón");
JButton botonPal = new JButton("Palante");
botonPal.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
System.out.println("Pulsaste el botón");
}
});
marco.add(etiqueta);
marco.add(botonPal);
marco.pack();
marco.setVisible(true);
}
}
¡Jo, qué cutre, ¿no?! Pues ya podría mostrarlo en el mismo interfaz, ¿verdad? Pues la verdad
es que quedaría más profesional. Pues podemos aplicarnos el cuento y modificar el código de
manera que el mensaje lo muestre mediante una etiqueta. Y ya puestos a trabajar poco, que
aproveche la misma etiqueta que muestra la leyenda "Pulsa el botón" para indicar que se ha pulsado
el botón.
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
class GuiSwingAct2 {
public static void main(String[] args) {
JFrame marco = new JFrame("Swing con Actividad");
marco.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
marco.setLayout(new FlowLayout());
JLabel etiqueta = new JLabel("Pulsa el botón");
JButton btnPal = new JButton("Palante");
ActionListener actionListener = new ActionListener() {
public void actionPerformed(ActionEvent e) {
etiqueta.setText("Botón Palante pulsado");
}
};
btnPal.addActionListener(actionListener);
marco.add(etiqueta);
marco.add(btnPal);
marco.pack();
marco.setVisible(true);
}
}
¿Lo podríamos hacer con dos botones? Pues eso, ¡con DOS BOTONES!
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
class GuiSwingAct3 {
public static void main(String[] args) {
JFrame marco = new JFrame("Swing con Actividad");
marco.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
marco.setLayout(new FlowLayout());
JLabel etiqueta = new JLabel("Pulsa en cualquier botón");
JButton btnPal = new JButton("Palante");
JButton btnCancel = new JButton("Cancelar");
ActionListener actionListener = new ActionListener() {
public void actionPerformed(ActionEvent e) {
if(e.getSource() == btnPal)
etiqueta.setText("Botón Palante pulsado");
else
etiqueta.setText("Botón Cancelar pulsado");
}
};
btnPal.addActionListener(actionListener);
btnCancel.addActionListener(actionListener);
marco.add(etiqueta);
marco.add(btnPal);
marco.add(btnCancel);
marco.pack();
marco.setVisible(true);
}
}
Pero como no todo van a ser botones, vamos a ser más listos trabajando con las listas. En
este caso, las desplegables desde las que vamos a seleccionar un tipo de letra y dicha acción
provoque la aparición de una etiqueta escrita con el tipo de letra seleccionado.
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
class GuiSwingAct4 {
public static void main(String[] args) {
JFrame marco = new JFrame("Swing Lista Fuentes");
marco.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
JLabel etiqSalida = new JLabel("Texto del tipo de letra");
JPanel listaPan = new JPanel();
JLabel etiqueta = new JLabel("Elige fuente:");
JComboBox fuente = new JComboBox();
fuente.setEditable(false);
fuente.addItem("Serif");
fuente.addItem("SansSerif");
fuente.addItem("Monospaced");
ActionListener actionListener = new ActionListener() {
public void actionPerformed(ActionEvent e) {
String tipo = (String) fuente.getSelectedItem();
int estilo = Font.BOLD; int tam = 20;
Font tipoFuente = new Font ( tipo, estilo, tam);
etiqSalida.setFont(tipoFuente);
marco.repaint();
}
};
fuente.addActionListener(actionListener);
listaPan.add(etiqueta);
listaPan.add(fuente);
marco.add(listaPan, "South");
marco.add(etiqSalida, "Center");
marco.setSize(275,150);
marco.setVisible(true);
}
}
Reto
Curioso, ¿verdad? Pues te reto a que este mismo ejercicio lo realices pero en vez de utilizar
una lista desplegable, implementes un grupo de botones de radio que permitan elegir la fuente con
la que se quiere representar la etiqueta. Se trata de que investigues cuál es el componente que Swing
ha prefabricado para presentar un grupo de botones de radio, de forma que solo una opción pueda
ser seleccionada.
Reto
Otro reto podría ser que ampliaras el ejercicio GuiSwingAct4 de forma que existan varias
listas desplegables en las que puedas seleccionar el tipo de letra como ya hace, pero también el
tamaño y el estilo (negrita, cursiva, subrayado). Obviamente el resultado por pantalla debe ser coherente
con los datos seleccionados en las listas desplegables.
ListaAmigos.java
Reto
Ya dijimos al principio de esta secuencia que Swing había reaprovechado muchos de los
componentes de AWT, además de los "escuchadores" (Listeners). Pero la modernidad exigió a Swing
la creación de componentes que AWT no diseñó en su momento. Un ejemplo de estos nuevos
componentes es JSlider, que permite seleccionar un valor dentro de un rango moviendo un
señalador. Si nos fijamos, no aparece el import java.awt.*; que hasta ahora veníamos colocando y es
import javax.swing.event.*; el que proporciona las clases para gestionar sus eventos.
import javax.swing.*;
import javax.swing.event.*;
class GuiSwingAct5 {
public static void main(String[] args) {
JFrame marco = new JFrame("JSlider con Actividad");
marco.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
JLabel etiqueta = new JLabel();
JPanel panel = new JPanel();
JSlider slider = new JSlider(0, 200, 100);
//Pinta y espacia la info del slider
slider.setPaintTrack(true);
slider.setPaintTicks(true);
slider.setPaintLabels(true);
slider.setMajorTickSpacing(50);
slider.setMinorTickSpacing(5);
/* Coloca a las bravas la detección de los
cambios al mover la flecha del slider
y reemplaza el texto con su valor actual */
slider.addChangeListener(new ChangeListener() {
public void stateChanged(ChangeEvent event) {
etiqueta.setText("Valor actual = " + slider.getValue());
}
});
panel.add(slider);
panel.add(etiqueta);
marco.add(panel);
//Para colocar texto inicial con el valor del JSlider
etiqueta.setText("Valor actual = " + slider.getValue());
marco.setSize(300, 300);
marco.setVisible(true);
}
}
Si te fijas, todos los códigos que se han mostrado hasta ahora se implementan dentro la clase
main(). Esto se hecho así por facilitar la adquisición de los conocimientos sobre contenedores,
componentes y eventos. Pero a nadie se le escapa, y por lo visto en secuencias anteriores, que en
Java se busca no hacer las cosas en el método main() para facilitar una reutilización profesional de
las soluciones.
slider.addChangeListener(new ChangeListener() {
public void stateChanged(ChangeEvent event) {
etiqueta.setText("Valor actual = " + slider.getValue());
}
});
¿Viste por algún lado una clase que recogía el método stateChanged()? No, ¿verdad? Por
eso se llama al constructor [new ChangeListener()] en el momento de colocarle la escucha al objeto
slider y sirve de envoltorio para justificar la sobreescritura del método stateChanged(). El hecho de
implementarlo incrustado dentro del método main() facilita que se pueda acceder al objeto slider
directamente sin tener que crear objeto ni nada.
Pero mosquea mucho ver que ChangeListener es en realidad un interfaz, lo sabemos porque
viene acompañado de la palabra reservada implements, y la teoría nos dice que un interfaz no se
instancia. Ya pero es el truco barato que cuela en Java cuando se quiere hacer todo a lo "Juan
Palomo" (dentro del mismo método), en el caso anterior main(), teniendo en cuenta que no le hemos
suministrado explícitamente la interfaz. Ahora en esta segunda versión vamos a sacar de main() el
método stateChanged() sobreescrito, dado que vamos a indicarle explícitamente el interfaz, lo que
nos permite utilizarlo como método aparte:
import javax.swing.*;
import javax.swing.event.*; La declaración de los contenedores y componentes
class GuiSwingAct5 implements ChangeListener { se hace en forma de atributo para que los métodos
static JFrame marco; de la clase puedan acceder a ellos directamente. [Es
static JSlider slider; que ahora stateChanged() ya no está en main()]
static JLabel etiqueta;
public static void main(String[] args) {
¿Y por qué static? Por comodidad, porque sino
GuiSwingAct5 control = new GuiSwingAct5();
en main(), al haber instanciado control del tipo
marco = new JFrame("JSlider con Actividad");
GuiSwingAct5, deberíamos hacer referencia a
marco.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
cada atributo como control.marco,
etiqueta = new JLabel();
control.etiqueta, etc...
JPanel panel = new JPanel();
slider = new JSlider(0, 200, 100);
slider.setPaintTrack(true);
slider.setPaintTicks(true);
slider.setPaintLabels(true);
slider.setMajorTickSpacing(50);
slider.setMinorTickSpacing(5);
/* Coloca la detección de cambios
en el objeto de la clase */
slider.addChangeListener(control);
panel.add(slider);
panel.add(etiqueta);
marco.add(panel);
//Para colocar texto con valor del JSlider
etiqueta.setText("Valor actual = " + slider.getValue());
marco.setSize(300, 300);
marco.setVisible(true);
}
//Acción a realizar si detecta cambio en JSlider
Aquí tenemos el método stateChanged()
public void stateChanged(ChangeEvent e) {
fuera de main()
etiqueta.setText("Valor actual = " + slider.getValue());
}
}
Puedes probarlo y verás que funciona idéntico a la primera versión. Pero hemos dicho que
todavía podemos adelgazar más el método main(). ¡Momento Jorge Blass! Para empezar sacándolo
en clase aparte al estilo de la clase Piloto utilizada en muchos códigos de nuestras secuencias. Sin
embargo, eso nos generaría ventajas y desventajas aunque más de las primeras. Nada por aquí, nada
por allí, y de repente nos sacamos de la manga el método constructor de una clase. Atentos:
import java.awt.*;
import javax.swing.*;
import javax.swing.event.*;
class Igu extends JFrame implements ChangeListener {
La declaración de los componentes se hace en
JSlider slider;
forma de atributo para que los métodos de la clase
JLabel etiqueta; Igu puedan acceder a ellos directamente. Ya no
Igu() hace falta static porque en main() se instancia toda
{ la clase Igu.
//Crea y configura componentes
etiqueta = new JLabel();
slider = new JSlider(0, 200, 100); La declaración de los contenedores no existe a priori
porque directamente se ha extendido la clase
slider.setPaintTrack(true);
JFrame para que herede Igu.Ahora Igu es el marco,
slider.setPaintTicks(true); al que se le está colgando el interfaz. Dentro de este
slider.setPaintLabels(true); constructor se han creado y configurado
slider.setMajorTickSpacing(50); componentes además de colocar la escucha.
slider.setMinorTickSpacing(5);
Para rematar la faena, no siempre nos pedirán que los mensajes resultantes de nuestras
selecciones tengan que plasmarse en etiquetas. Swing tiene un componente denominado
JOptionPane que genera una ventana de información como las que muestra el SO Windows
cuando informa de algo o como cuando en un formulario por Internet nos quiere avisar de que se
nos ha olvidado algo o nos hemos equivocado.
import javax.swing.*;
import java.awt.event.*;
class Igu {
JRadioButton botRadio1;
JRadioButton botRadio2;
JButton boton;
ButtonGroup agrupBotRad;
JLabel etiqueta;
public Igu() {
this.setLayout(null);
botRadio1 = new JRadioButton();
botRadio2 = new JRadioButton();
boton = new JButton("Pulsa");
agrupBotRad = new ButtonGroup();
etiqueta = new JLabel("FP estudiada");
botRadio1.setText("Grado Medio");
botRadio2.setText("Grado Superior");
botRadio1.setBounds(120, 30, 120, 50);
botRadio2.setBounds(250, 30, 120, 50);
boton.setBounds(125, 90, 80, 30);
etiqueta.setBounds(20, 30, 150, 50);
this.add(botRadio1);
this.add(botRadio2);
this.add(boton);
this.add(etiqueta);
agrupBotRad.add(botRadio1);
agrupBotRad.add(botRadio2);
//Colocando escucha al grupo de botones
boton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
String fpStu = " ";
if (botRadio1.isSelected()) {
fpStu = botRadio1.getText();
}
else if (botRadio2.isSelected()) {
fpStu = botRadio2.getText();
}
else {
fpStu = "Nada seleccionado";
}
// Muestra mediante ventana de diálogo la opción seleccionada
JOptionPane.showMessageDialog(Igu.this, fpStu);
}
});
}
}
class GuiSwingAct6 {
public static void main(String[] args) {
Igu marco = new Igu();
marco.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
marco.setBounds(100, 100, 400, 200);
marco.setTitle("Botones de radio, botón y ventana");
marco.setVisible(true);
}
}
Reto
Nota: Es tan buen ejercicio pasar un código que está todo incluido en main() a
profesional como hacerlo en sentido inverso.
Y si...
...después de todo lo que llevamos programado, nos viene alguien y nos dice eso de: "pues
IntelliJ IDEA tiene un asistente gráfico para construir interfaces que de paso te genera el código
Java Swing necesario". Pues sí, confirmado. IntelliJ IDEA, al igual que el resto de entornos de
desarrollo para Java, tiene su herramienta gráfica de creación de interfaces.
El resultado de implementar un GUI con el asistente de IntelliJ IDEA es este código que el
propio entorno ha generado:
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
public class GUIAsist1 {
private JTextField dataEntrada;
private JButton boton1;
private JPanel miPanel;
private JLabel etiqueta;
public GUIAsist1() {
boton1.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
etiqueta.setText(dataEntrada.getText());
}
});
}
public static void main(String[] args) {
JFrame marco = new JFrame("GUIAsist1");
marco.setContentPane(new GUIAsist1().miPanel);
marco.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
marco.pack();
frame.setVisible(true);
}
/**
* Method generated by IntelliJ IDEA GUI Designer
* >>> IMPORTANT!! <<<
* DO NOT edit this method OR call it in your code!
*
* @noinspection ALL
*/
private void $$$setupUI$$$() {
miPanel = new JPanel();
miPanel.setLayout(new com.intellij.uiDesigner.core.GridLayoutManager(3, 2, new Insets(0, 0, 0, 0), -1, -1));
dataEntrada = new JTextField();
miPanel.add(dataEntrada, new com.intellij.uiDesigner.core.GridConstraints(0, 0, 1, 2,
com.intellij.uiDesigner.core.GridConstraints.ANCHOR_WEST,
com.intellij.uiDesigner.core.GridConstraints.FILL_HORIZONTAL,
com.intellij.uiDesigner.core.GridConstraints.SIZEPOLICY_WANT_GROW,
com.intellij.uiDesigner.core.GridConstraints.SIZEPOLICY_FIXED, null, new Dimension(150, -1), null, 0, false));
final com.intellij.uiDesigner.core.Spacer spacer1 = new com.intellij.uiDesigner.core.Spacer();
miPanel.add(spacer1, new com.intellij.uiDesigner.core.GridConstraints(1, 1, 2, 1,
com.intellij.uiDesigner.core.GridConstraints.ANCHOR_CENTER,
com.intellij.uiDesigner.core.GridConstraints.FILL_VERTICAL, 1,
com.intellij.uiDesigner.core.GridConstraints.SIZEPOLICY_WANT_GROW, null, null, null, 0, false));
boton1 = new JButton();
boton1.setText("Pulsa");
miPanel.add(boton1, new com.intellij.uiDesigner.core.GridConstraints(1, 0, 1, 1,
com.intellij.uiDesigner.core.GridConstraints.ANCHOR_CENTER,
com.intellij.uiDesigner.core.GridConstraints.FILL_HORIZONTAL,
com.intellij.uiDesigner.core.GridConstraints.SIZEPOLICY_CAN_SHRINK |
com.intellij.uiDesigner.core.GridConstraints.SIZEPOLICY_CAN_GROW,
com.intellij.uiDesigner.core.GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false));
etiqueta = new JLabel();
etiqueta.setText("");
miPanel.add(etiqueta, new com.intellij.uiDesigner.core.GridConstraints(2, 0, 1, 1,
com.intellij.uiDesigner.core.GridConstraints.ANCHOR_WEST, com.intellij.uiDesigner.core.GridConstraints.FILL_NONE,
com.intellij.uiDesigner.core.GridConstraints.SIZEPOLICY_FIXED,
com.intellij.uiDesigner.core.GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false));
}
/**
* @noinspection ALL
*/
public JComponent $$$getRootComponent$$$() {
return miPanel;
}
}
Refactoriza manualmente este código de forma que existan dos clases, teniendo una de ellas
la escucha colgada por interfaz, un constructor dejando para la otra el método main() lo más fino
posible.
import java.awt.FlowLayout;
import javax.swing.JFrame;
import javax.swing.JSpinner;
import javax.swing.JTextField;
import javax.swing.WindowConstants;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
Ejercicio 1
Implementa una GUI que refleje un conversor de moneda de euro a dolar USA que solicite
la introducción de una cantidad (puede contener decimales) en un campo de texto. Mediante dos
botones de radio agrupados se eligirá si se quiere el resultado en euros o en dólares USA siendo un
botón el que desencadene la operación. Una vez calculada la cantidad equivalente, ésta deberá
reflejarse en un segundo campo de texto.
Ejercicio 2
Ejercicio 3
Ejercicio 4
Autoevaluación