Codemaker Interfaces

Interfaces Tema 7

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#

Estructura de la aplicación: VentanaPrincipal con BorderLayout, el menú a la izquierda y un CardLayout con las pantallas en el centro

El resultado:

Pantalla «Alumnos»Tras pulsar «Nuevo alumno»
La aplicación con el menú a la izquierda y la tabla de alumnos a la derechaLa misma ventana mostrando el formulario de 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:

Java
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:

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.

Java
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.

Java
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:

Java
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:

Java
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#

CardLayout apila los paneles como cartas y solo se ve el de arriba; show cambia la tarjeta visible

PasoCódigo
Crear el panel de las pantallasJPanel contenido = new JPanel(tarjetas);, con CardLayout tarjetas = new CardLayout();
Añadir cada pantalla, con un nombrecontenido.add(new PanelAlumnos(alumnos), "alumnos");
Mostrar unatarjetas.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 filasnew DefaultTableModel(new String[] {"Nombre", "Edad", "Nivel"}, 0)
Añadir una filamodelo.addRow(new Object[] {"Ana", 15, "Master"});
Vaciar la tablamodelo.setRowCount(0);
Saber qué fila ha elegido el usuarioint fila = tabla.getSelectedRow(); (-1 si no hay ninguna)
Leer una celdamodelo.getValueAt(fila, 0) (fila y columna, desde 0)
Que no se pueda escribir en las celdastabla.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íntomaQué pasaCómo se arregla
Pulsas el menú y no cambia la pantallaEl nombre de show no coincide con el de addUsa exactamente el mismo texto en los dos, mayúsculas incluidas
La tabla sale sin los nombres de las columnasLa tabla no está dentro de un JScrollPaneadd(new JScrollPane(tabla));
ArrayIndexOutOfBoundsException: Index -1 out of bounds… al leer una filaNo hay ninguna fila seleccionada y getSelectedRow() ha devuelto -1Comprueba antes: if (fila != -1)
NullPointerException al arrancarUsas un componente antes de crearlo con newCrea los componentes antes de usarlos, y los atributos de la clase, al declararlos