Profe Macón
Explora, aprende, crea.
profemacon.net
Programación Avanzada · Java Swing

De una clase vacía a una interfaz completa: ProductoVista

Vamos a construir una ventana de productos paso a paso. La idea no es memorizar Swing: es observar qué hace cada instrucción, probar variantes y descubrir por qué necesitamos contenedores y distintos layouts.

HerenciaJFrameSettersJLabel ContenedoresLayoutsJPanelJScrollPane crearVentana()
1 El punto de partida

ProductoVista es un JFrame

Ya conocemos la herencia. Ahora vamos a usarla con una clase real de la biblioteca de Java. Nuestra clase ProductoVista será una especialización de JFrame.

ProductoVista.java
import javax.swing.JFrame;

public class ProductoVista extends JFrame {

}
Pregunta para pensar
Si todavía no escribimos ningún método dentro de ProductoVista, ¿podrá usar métodos propios de JFrame?
ProductoVista
es un ↓
JFrame
hereda de ↓
Frame → Window → Container → Component
Idea clave
La relación de herencia se lee como “es un”: un objeto ProductoVista también puede utilizar los comportamientos que recibe de la jerarquía de JFrame.
2 Crear el objeto y mostrarlo

La primera ventana… casi invisible

En Main creamos un objeto de nuestra clase y después le pedimos que se muestre.

Main.java
public class Main {
    public static void main(String[] args) {

        ProductoVista v = new ProductoVista();
        v.setVisible(true);
    }
}
— □ ×
¿Qué pasó?
El objeto existe y la ventana se hizo visible, pero todavía no definimos sus dimensiones. Por eso apenas vemos su decoración exterior.
3 Configurar el JFrame

El constructor empieza a darle forma

Podemos aprovechar el constructor de ProductoVista para configurar las características iniciales de cada ventana que creemos.

ProductoVista.java
public ProductoVista() {
    setTitle("Gestión de productos");
    setSize(800, 600);
    setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}

Una alternativa a setSize() es:

setBounds(100, 100, 800, 600);
Setter¿Qué cambia?
setTitle(...)El texto de la barra superior.
setSize(w,h)El ancho y el alto.
setBounds(x,y,w,h)Posición y dimensiones a la vez.
setDefaultCloseOperation(...)Qué ocurre cuando se presiona ×.

Las constantes de cierre

DO_NOTHING_ON_CLOSENo realiza automáticamente ninguna acción.
HIDE_ON_CLOSEOculta la ventana.
DISPOSE_ON_CLOSELibera los recursos de esa ventana.
EXIT_ON_CLOSEFinaliza la aplicación.
Pruebe: cambie una constante por otra, ejecute nuevamente el programa y observe qué sucede. La idea es aprender viendo el efecto de cada decisión.
4 Un detalle que sorprende

¿Por qué setBackground() parece no funcionar?

setBackground(Color.BLUE);

Podemos cambiar el color de fondo del JFrame, pero en la zona útil de la ventana hay otro contenedor por delante: el content pane.

Lo importante
El interior visible de un JFrame se organiza a través de su content pane. Más adelante vamos a reemplazarlo por nuestro propio JPanel panelPrincipal.
JFrame
contiene ↓
JRootPane
incluye ↓
Content Pane
aquí vemos ↓
Componentes de la interfaz
Experimento opcional
getContentPane().setBackground(Color.BLUE);

Ahora sí debería observarse el color en la zona interior, mientras todavía no agreguemos otros componentes que la cubran.

5 Primer componente

Agregar un JLabel

La vista va a necesitar componentes. Si un componente forma parte del estado de la vista, podemos declararlo como atributo privado.

ProductoVista.java
private JLabel lblTitulo;

public ProductoVista() {
    setTitle("Gestión de productos");
    setSize(800, 600);
    setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

    lblTitulo = new JLabel("PRODUCTOS");
    add(lblTitulo);
}
En este momento no buscamos todavía una interfaz bonita. El objetivo es observar una idea: un componente debe ser agregado a un contenedor para formar parte de la interfaz.
Cambie el texto del JLabel, pruebe su alineación y modifique su fuente. Ejecute después de cada cambio.
6 El problema de la distribución

Los layouts organizan los componentes

Cada contenedor trabaja con un LayoutManager a la vez. Ese layout decide cómo se distribuyen los componentes que están directamente dentro de ese contenedor.

Algunos layouts que vamos a combinar

  • BorderLayout: NORTH, SOUTH, EAST, WEST y CENTER.
  • GridLayout: organiza componentes en filas y columnas de igual tamaño.
  • FlowLayout: coloca los componentes uno junto a otro siguiendo un flujo.
La clave conceptual
No necesitamos encontrar un layout perfecto para toda la ventana. Podemos crear varios contenedores, cada uno con el layout que mejor resuelva su parte.
Contenedor A
usa 1 layout
BorderLayout
puede contener
Contenedor B
GridLayout
Contenedor C
FlowLayout
7 Primero diseñar

Dibujemos la ventana que queremos

Antes de escribir más código, hacemos un boceto. Nuestra vista de productos tendrá:

PRODUCTOS
JLabel
Código
txtCódigo
Nombre
txtNombre
Precio
txtPrecio
Stock
txtStock
Aceptar Eliminar Cancelar
Código
Nombre
Precio
Stock
001
Ejemplo
350
4
002
Otro producto
590
8
Problema
¿Qué layout único nos permite crear exactamente todo esto? Ninguno. Entonces necesitamos dividir el problema.
8 Dividir para resolver

Un sistema de contenedores dentro de contenedores

JFrame ProductoVista
content pane ↓
panelPrincipal · BorderLayout
contiene
NORTH
panelFormulario · BorderLayout
CENTER
JScrollPane listado
panelFormulario contiene
NORTH
lblTitulo
CENTER
panelCampos · GridLayout(2,4)
SOUTH
panelBotones · FlowLayout

Mapa de responsabilidades visuales

ContenedorLayoutQué contiene
panelPrincipalBorderLayoutFormulario arriba y listado en el centro.
panelFormularioBorderLayoutTítulo, campos y botones.
panelCamposGridLayout(2,4)4 etiquetas y 4 campos.
panelBotonesFlowLayoutAceptar, Eliminar y Cancelar.
JScrollPanegestión propiaLa tabla de productos.
Resultado del razonamiento
El diseño complejo aparece al combinar pequeños diseños simples.
9 Ordenar la construcción

El método privado crearVentana()

El constructor no debería convertirse en una larga lista de creación y configuración de componentes. Podemos delegar esa tarea a un método privado.

public ProductoVista() {
    crearVentana();
}

private void crearVentana() {
    // 1. Configurar el JFrame
    // 2. Crear los paneles
    // 3. Crear los componentes
    // 4. Configurar componentes
    // 5. Agregar cada componente al contenedor correcto
    // 6. Definir el panel principal como content pane
}
¿Por qué privado? Porque es un detalle interno de construcción de ProductoVista. Desde afuera necesitamos usar la vista, no indicarle paso a paso cómo construirse.
Conexión con POO
La interfaz también tiene estado (sus componentes) y responsabilidades (construirse y organizarse). No dejamos de trabajar con objetos por empezar a usar Swing.
10 Culminación

Una primera versión completa de ProductoVista

Esta versión construye toda la interfaz, pero todavía no define qué hacen los botones. Esa responsabilidad se incorporará más adelante.

ProductoVista.java
import java.awt.BorderLayout;
import java.awt.FlowLayout;
import java.awt.Font;
import java.awt.GridLayout;

import javax.swing.BorderFactory;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.JTextField;
import javax.swing.SwingConstants;
import javax.swing.table.DefaultTableModel;

public class ProductoVista extends JFrame {

    // Componentes que forman parte del estado de la vista
    private JLabel lblTitulo;

    private JLabel lblCodigo;
    private JLabel lblNombre;
    private JLabel lblPrecio;
    private JLabel lblStock;

    private JTextField txtCodigo;
    private JTextField txtNombre;
    private JTextField txtPrecio;
    private JTextField txtStock;

    private JButton btnAceptar;
    private JButton btnEliminar;
    private JButton btnCancelar;

    private JTable tablaProductos;

    public ProductoVista() {
        crearVentana();
    }

    private void crearVentana() {

        // -------------------------------------------------
        // 1. Configuración general de la ventana
        // -------------------------------------------------
        setTitle("Gestión de productos");
        setSize(850, 600);
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        setLocationRelativeTo(null);

        // -------------------------------------------------
        // 2. Panel principal
        // -------------------------------------------------
        JPanel panelPrincipal = new JPanel(new BorderLayout(10, 10));
        panelPrincipal.setBorder(BorderFactory.createEmptyBorder(15, 15, 15, 15));

        // -------------------------------------------------
        // 3. Panel del formulario
        // -------------------------------------------------
        JPanel panelFormulario = new JPanel(new BorderLayout(10, 10));

        // Título
        lblTitulo = new JLabel("PRODUCTOS");
        lblTitulo.setHorizontalAlignment(SwingConstants.CENTER);
        lblTitulo.setFont(new Font("SansSerif", Font.BOLD, 22));

        panelFormulario.add(lblTitulo, BorderLayout.NORTH);

        // -------------------------------------------------
        // 4. Panel de campos: 2 filas x 4 columnas
        // -------------------------------------------------
        JPanel panelCampos = new JPanel(new GridLayout(2, 4, 10, 10));

        lblCodigo = new JLabel("Código:");
        lblNombre = new JLabel("Nombre:");
        lblPrecio = new JLabel("Precio:");
        lblStock = new JLabel("Stock:");

        txtCodigo = new JTextField();
        txtNombre = new JTextField();
        txtPrecio = new JTextField();
        txtStock = new JTextField();

        panelCampos.add(lblCodigo);
        panelCampos.add(txtCodigo);

        panelCampos.add(lblNombre);
        panelCampos.add(txtNombre);

        panelCampos.add(lblPrecio);
        panelCampos.add(txtPrecio);

        panelCampos.add(lblStock);
        panelCampos.add(txtStock);

        panelFormulario.add(panelCampos, BorderLayout.CENTER);

        // -------------------------------------------------
        // 5. Panel de botones
        // -------------------------------------------------
        JPanel panelBotones = new JPanel(new FlowLayout(FlowLayout.CENTER));

        btnAceptar = new JButton("Aceptar");
        btnEliminar = new JButton("Eliminar");
        btnCancelar = new JButton("Cancelar");

        panelBotones.add(btnAceptar);
        panelBotones.add(btnEliminar);
        panelBotones.add(btnCancelar);

        panelFormulario.add(panelBotones, BorderLayout.SOUTH);

        // -------------------------------------------------
        // 6. Tabla y JScrollPane
        // -------------------------------------------------
        String[] columnas = {"Código", "Nombre", "Precio", "Stock"};

        DefaultTableModel modeloTabla =
                new DefaultTableModel(columnas, 0);

        tablaProductos = new JTable(modeloTabla);

        JScrollPane scrollListado =
                new JScrollPane(tablaProductos);

        // -------------------------------------------------
        // 7. Componer el panel principal
        // -------------------------------------------------
        panelPrincipal.add(panelFormulario, BorderLayout.NORTH);
        panelPrincipal.add(scrollListado, BorderLayout.CENTER);

        // Nuestro JPanel pasa a ser el content pane del JFrame
        setContentPane(panelPrincipal);
    }
}
Main.java
public class Main {

    public static void main(String[] args) {

        ProductoVista v = new ProductoVista();
        v.setVisible(true);
    }
}
Todavía no hacemos esto
En esta etapa no agregamos listeners ni métodos para Aceptar, Eliminar o Cancelar. Los botones existen y están ubicados en la interfaz. Su comportamiento será otro problema y otra etapa.
11 Lo que debería quedar claro

Mapa mental de la construcción

ProductoVista extends JFrame
↓
new ProductoVista()
constructor ↓
crearVentana()
↓
crea + configura + organiza componentes
↓
setContentPane(panelPrincipal)
↓
v.setVisible(true)
Material en curso
← Índice del curso