Interfaces Tema 8
Organizar una aplicación
Con todo en el main, un programa de una ventana ya ocupa cien líneas. Una aplicación de verdad, con varias pantallas, se reparte en varias clases: una por ventana y una por pantalla. Así está hecha la maqueta del proyecto final, y así será el tuyo.
La estructura#
El resultado:
| Pantalla «Alumnos» | Tras pulsar «Nuevo alumno» |
|---|---|
![]() | ![]() |
Clases que son ventanas: extends#
Hasta ahora creábamos un JFrame y le íbamos metiendo cosas. Ahora haremos nuestra propia clase de ventana:
public class VentanaPrincipal extends JFrame {
…
}extends JFrame significa que VentanaPrincipal es un JFrame: tiene todo lo que tiene una ventana (setSize, add, setVisible…) y además lo que tú le añadas. Por eso, dentro de la clase, se escribe setSize(…) a secas, sin ventana. delante. Lo mismo con las pantallas: PanelAlumnos extends JPanel es un panel.
super("Mi academia"), la primera línea del constructor, le pasa el título al constructor de JFrame.
Los ficheros#
Cada clase va en su fichero, todos en src.
Alumno.java, los datos, como en el tema de POO de los apuntes de Java:
public class Alumno {
private String nombre;
private int edad;
private String nivel;
public Alumno(String nombre, int edad, String nivel) {
this.nombre = nombre;
this.edad = edad;
this.nivel = nivel;
}
public String getNombre() { return nombre; }
public int getEdad() { return edad; }
public String getNivel() { return nivel; }
}App.java, el programa principal: prepara los datos y abre la ventana.
import javax.swing.SwingUtilities;
import com.formdev.flatlaf.FlatLightLaf;
public class App {
public static void main(String[] args) {
FlatLightLaf.setup();
Alumno[] alumnos = {
new Alumno("Antonio", 17, "DEV"),
new Alumno("María", 16, "DEV"),
new Alumno("Juan", 14, "Master"),
new Alumno("Elena", 10, "Junior")
};
SwingUtilities.invokeLater(() -> new VentanaPrincipal(alumnos).setVisible(true));
}
}VentanaPrincipal.java, el marco: el menú a la izquierda y las pantallas en el centro.
import java.awt.BorderLayout;
import java.awt.CardLayout;
import javax.swing.*;
import net.miginfocom.swing.MigLayout;
public class VentanaPrincipal extends JFrame {
private CardLayout tarjetas = new CardLayout();
private JPanel contenido = new JPanel(tarjetas);
public VentanaPrincipal(Alumno[] alumnos) {
super("Mi academia");
setDefaultCloseOperation(EXIT_ON_CLOSE);
// Centro: una "tarjeta" por pantalla, y solo se ve una
contenido.add(new PanelAlumnos(alumnos), "alumnos");
contenido.add(new PanelNuevo(), "nuevo");
// Izquierda: el menú
JPanel menu = new JPanel(new MigLayout("wrap, insets 16, gap 8", "[160!, fill]"));
JButton verAlumnos = new JButton("Alumnos");
JButton nuevo = new JButton("Nuevo alumno");
verAlumnos.addActionListener(e -> tarjetas.show(contenido, "alumnos"));
nuevo.addActionListener(e -> tarjetas.show(contenido, "nuevo"));
menu.add(verAlumnos);
menu.add(nuevo);
add(menu, BorderLayout.WEST);
add(contenido, BorderLayout.CENTER);
setSize(640, 360);
setLocationRelativeTo(null);
}
}PanelAlumnos.java, la pantalla con la tabla:
import javax.swing.*;
import javax.swing.table.DefaultTableModel;
import com.formdev.flatlaf.FlatClientProperties;
import net.miginfocom.swing.MigLayout;
public class PanelAlumnos extends JPanel {
private DefaultTableModel modelo = new DefaultTableModel(new String[] {"Nombre", "Edad", "Nivel"}, 0);
public PanelAlumnos(Alumno[] alumnos) {
setLayout(new MigLayout("wrap, insets 20, fill", "[grow, fill]", "[][grow, fill]"));
JLabel titulo = new JLabel("Alumnos");
titulo.putClientProperty(FlatClientProperties.STYLE_CLASS, "h2");
add(titulo);
for (Alumno a : alumnos) {
modelo.addRow(new Object[] {a.getNombre(), a.getEdad(), a.getNivel()});
}
JTable tabla = new JTable(modelo);
tabla.setDefaultEditor(Object.class, null); // que no se pueda escribir en las celdas
add(new JScrollPane(tabla));
}
}PanelNuevo.java, la pantalla del formulario:
import javax.swing.*;
import com.formdev.flatlaf.FlatClientProperties;
import net.miginfocom.swing.MigLayout;
public class PanelNuevo extends JPanel {
public PanelNuevo() {
setLayout(new MigLayout("wrap 2, insets 20", "[right][grow, fill]"));
JLabel titulo = new JLabel("Nuevo alumno");
titulo.putClientProperty(FlatClientProperties.STYLE_CLASS, "h2");
add(titulo, "span 2, align left, gapbottom 10");
add(new JLabel("Nombre"));
add(new JTextField());
add(new JLabel("Nivel"));
add(new JComboBox<>(new String[] {"Junior", "Senior", "Master", "DEV"}));
add(new JButton("Guardar"), "skip, growx 0, align right");
}
}Cambiar de pantalla: CardLayout#
| Paso | Código |
|---|---|
| Crear el panel de las pantallas | JPanel contenido = new JPanel(tarjetas);, con CardLayout tarjetas = new CardLayout(); |
| Añadir cada pantalla, con un nombre | contenido.add(new PanelAlumnos(alumnos), "alumnos"); |
| Mostrar una | tarjetas.show(contenido, "nuevo"); |
Tablas: JTable#
Una JTable muestra datos en filas y columnas. Los datos no se guardan en la tabla, sino en su modelo, un DefaultTableModel: la tabla solo lo dibuja.
| Para… | Código |
|---|---|
| Crear el modelo, con los nombres de las columnas y 0 filas | new DefaultTableModel(new String[] {"Nombre", "Edad", "Nivel"}, 0) |
| Añadir una fila | modelo.addRow(new Object[] {"Ana", 15, "Master"}); |
| Vaciar la tabla | modelo.setRowCount(0); |
| Saber qué fila ha elegido el usuario | int fila = tabla.getSelectedRow(); (-1 si no hay ninguna) |
| Leer una celda | modelo.getValueAt(fila, 0) (fila y columna, desde 0) |
| Que no se pueda escribir en las celdas | tabla.setDefaultEditor(Object.class, null); |
La tabla va siempre dentro de un JScrollPane: así tiene barra de desplazamiento y muestra los nombres de las columnas.
Consejo
Cada fila de la tabla sale de un objeto Alumno. Cuando los alumnos vengan de una base de datos (apuntes de SQL), el while (rs.next()) creará un Alumno por fila, y el resto del código no cambia.
Errores frecuentes#
| Síntoma | Qué pasa | Cómo se arregla |
|---|---|---|
| Pulsas el menú y no cambia la pantalla | El nombre de show no coincide con el de add | Usa exactamente el mismo texto en los dos, mayúsculas incluidas |
| La tabla sale sin los nombres de las columnas | La tabla no está dentro de un JScrollPane | add(new JScrollPane(tabla)); |
ArrayIndexOutOfBoundsException: Index -1 out of bounds… al leer una fila | No hay ninguna fila seleccionada y getSelectedRow() ha devuelto -1 | Comprueba antes: if (fila != -1) |
NullPointerException al arrancar | Usas un componente antes de crearlo con new | Crea los componentes antes de usarlos, y los atributos de la clase, al declararlos |

