Aprende Java Aprende Php Aprende C++ Aprende HTML 5 Aprende JavaScript Aprende JSON Aprende MySQL Aprende SQLServer Aprende Visual Basic 6 Aprende PostgreSQL Aprende SQLite Aprende Redis Aprende Kotlin Aprende XML Aprende Linux VSC Aprende Wordpress Aprende Laravel Aprende VueJS Aprende JQuery Aprende Bootstrap Aprende Netbeans Aprende Android
Sigueme en Facebook Sigueme en Twitter Sigueme en Instagram Sigueme en Youtube Sigueme en TikTok Sigueme en Whatsapp
Home / Java / Sub Tablas: Agregar tablas dentro de otras tablas en Java

Sub Tablas: Agregar tablas dentro de otras tablas en Java

Por jc mouse miércoles, enero 29, 2020

El JTable de Java es un gran componente para mostrar datos en una tabla de una forma rápida y sencilla, sin embargo en varias oportunidades se queda corta antes nuestras necesidades por lo que es necesario implementar nuestras propias tablas de datos, para ser capaces de mostrar por ejemplo, datos que a su vez contengan más datos, es decir, tablas compuestas.

Utilizaremos el IDE Netbeans y Java 8.

En este post implementaremos paso a paso el código necesario para crear una tabla que permita entre sus celdas el uso de sub tablas, para ser más precisos, una tabla que permita mostrar una lista de autores quienes tienen en su haber uno o varios libros como se ve en la imagen siguiente:

custom jtable

Paso 1. El proyecto

Creamos un proyecto Java con el nombre de «Sub Tablas Java» y la siguiente estructura:

proyecto java

Paso 2. El modelo

El modelo esta representados por dos clases, la clase Autor y la clase Libro las cuales están relacionadas por el tipo de relación «uno a muchos», es decir, un Autor puede escribir muchos libros.

A continuación el código de las clases, no se olviden de colocar los métodos GET y SET

Clase Autor:

package org.example.modelo;

import java.util.ArrayList;

/**
 * @see https://www.jc-mouse.net/
 * @author mouse
 */
public class Autor {
    
    private String nombre;
    private String pais;
    private ArrayList<Libro> libros;

    public Autor() {
    }

    public Autor(String nombre, String pais, ArrayList<Libro> libros) {
        this.nombre = nombre;
        this.pais = pais;
        this.libros = libros;
    }
    
    //GET...
    
    //SET...
}

Clase Libro:

package org.example.modelo;
/**
 * @see https://www.jc-mouse.net/
 * @author mouse
 */
public class Libro {

    private String titulo;
    private String genero;

    public Libro() {
    }

    public Libro(String titulo, String genero) {
        this.titulo = titulo;
        this.genero = genero;
    }
    
    //GET...
    
    //SET...
}

Paso 3. El Modelo de la Tabla

Debemos implementar nuestro propio modelo para la tabla para poder interactuar  con nuestros datos los cuales son para este ejemplo objetos de tipo Autor que a su vez contienen otros objetos, para realizar esto usaremos la clase AbstractTableModel.

package org.example.componentes;

import java.util.ArrayList;
import javax.swing.table.AbstractTableModel;
import org.example.modelo.Autor;
/**
 * @see https://www.jc-mouse.net/
 * @author mouse
 */
public class TablaModelo extends AbstractTableModel {

    final String[] columnNames = {"Autor", "Pais de Origen", "Libro,Genero"};
    private ArrayList<Autor> list = new ArrayList<>();

    public void addAutor(Autor autor) {
        list.add(autor);
        //Notifica a los listeners que se ha insertado un nuevo dato 
        //en el rango [firstRow, lastRow]
        fireTableRowsInserted(list.size() - 1, list.size() - 1);
    }

    @Override
    public int getRowCount() {
        return list.size();
    }

    @Override
    public int getColumnCount() {
        return columnNames.length;
    }

    @Override
    public Object getValueAt(int rowIndex, int columnIndex) {
        Autor autor = list.get(rowIndex);
        switch (columnIndex) {
            case 0:
                return autor.getNombre();
            case 1:
                return autor.getPais();
            case 2:
                return autor.getLibros();
            default:
                return null;
        }
    }

    @Override
    public String getColumnName(int col) {
        return columnNames[col];
    }

    @Override
    public boolean isCellEditable(int row, int col) {
        return false;
    }
}

También declaramos los nombres de las columnas, y aquí un detalle importante, nuestras sub tablas se mostraran dentro de una celda en una sola columna por lo que debemos también personalizar los headers de la tabla, pero esto lo veremos más adelante, por el momento solo declaramos los nombres de las columnas de la sub tabla en un String separada por comas, es decir de la forma «Libro,Genero».

Paso 4. El renderizador de celdas

Dado que los datos que se mostraran en la tabla no son los que usualmente se utilizan, debemos crear nuestro propio renderizador de celdas para personalizar la salida de datos por pantalla, sino hacemos esto, podemos obtener lo siguiente:

personalizar celdas de tabla

Implementamos la interface TableCellRenderer en nuestra clase CeldaRenderer de la siguiente forma:

package org.example.componentes;

import java.awt.Component;
import java.util.ArrayList;
import javax.swing.JTable;
import javax.swing.table.AbstractTableModel;
import javax.swing.table.TableCellRenderer;
import org.example.modelo.Libro;

/**
 * @see https://www.jc-mouse.net/
 * @author mouse
 */
public class CeldaRenderer implements TableCellRenderer {

    private int alturaMinima = -1;
    private int alturaActual = -1;

    @Override
    public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {

        //si el valor recibido es un array
        if (value instanceof ArrayList) {
            ArrayList<Libro> libros = (ArrayList<Libro>) value;
          
            //de acuerdo al tamaño de la lista de libros
            //se ajusta la altura de la subtabla
            if (alturaMinima == -1) {
                alturaMinima = table.getRowHeight();
            }
            
            if (!libros.isEmpty()) {
                if (alturaActual != libros.size() * alturaMinima) {
                    alturaActual = libros.size() * alturaMinima;
                    table.setRowHeight(row, alturaActual);                    
                }
            }
            //fin ajustes de altura de subtabla
            
            //El objeto devuelto sera un JTable
            return new JTable(new AbstractTableModel() {

                //sabemos que el numero de columnas de nuestra
                //sub tabla es de 2
                final int cols = 2;

                @Override
                public int getColumnCount() {
                    return cols;
                }

                @Override
                public int getRowCount() {
                    return libros.size();
                }

                @Override
                public Object getValueAt(int rowIndex, int columnIndex) {                    
                    Libro libro = libros.get(rowIndex);
                    switch (columnIndex) {
                        case 0:
                            return libro.getTitulo();
                        case 1:
                            return libro.getGenero();
                        default:
                            return null;
                    }
                }

                @Override
                public boolean isCellEditable(int row, int col) {
                    return false;
                }

            });

        } else {//no hacemos nada
            return table.getDefaultRenderer(value.getClass())
                    .getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
        }
    }
}

El código anterior lo que hace es, si el valor obtenido para la celda es un array, formateara la salida de esta en forma de un JTable, caso contrario retornara el valor por defecto.

Paso 5. El renderizador del encabezado

Como ya mencionamos anteriormente, como nuestros datos no son los usuales para un JTable, debemos personalizar la salida en pantalla de estos, ya lo hicimos para los datos del JTable, y ahora debemos hacer lo propio para los nombres de las columnas o el header de nuestra tabla.

Los nombres de nuestras columnas están dados por cadenas de texto, pero para el caso de nuestra sub tabla, usamos el formato «Libro,Genero», por lo que nuestro render lo que hará, es separar estos en un array usando el método split(«,») para luego mostrar cada elemento del array resultante en JLabels independientes y formateados. El código para la clase EncabezadoRenderer es el siguiente:

package org.example.componentes;

import java.awt.Component;
import java.awt.GridLayout;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JTable;
import javax.swing.SwingConstants;
import javax.swing.border.BevelBorder;
import javax.swing.border.SoftBevelBorder;
import javax.swing.table.TableCellRenderer;

/**
 * @see https://www.jc-mouse.net/
 * @author mouse
 */
public class EncabezadoRenderer extends JPanel implements TableCellRenderer {

    public EncabezadoRenderer() {
        setLayout(new GridLayout());
    }

    @Override
    public Component getTableCellRendererComponent(JTable table, Object value,
            boolean isSelected, boolean hasFocus, int row, int column) {
        String[] str = value.toString().split(",");
        removeAll();
        for (String s : str) {
            JLabel label = new JLabel();
            label.setBorder(new SoftBevelBorder(BevelBorder.RAISED));
            label.setHorizontalAlignment(SwingConstants.CENTER);
            label.setText(s);
            add(label);
        }
        return this;
    }
}

Paso 6. Implementación del JTable

Finalmente debemos implementar los renders y el modelo a nuestra tabla, para esto, en nuestro diseñador de interfaz, en el frame SubTablaFrame, agregamos un JTable de la siguiente forma:

user gui table

Desde el editor de código, declaramos el método iniciarTabla() y llamamos desde el constructor.

public class SubTablaFrame extends javax.swing.JFrame {

    /**
     * Creates new form SubTablaFrame
     */
    public SubTablaFrame() {
        initComponents();
        //
        iniciarTabla();
    }

    private void iniciarTabla() {
        //Los datos
        TablaModelo model = new TablaModelo();
        //1 libro
        ArrayList<Libro> libros1 = new ArrayList<>();
        libros1.add(new Libro("Cuento de Navidad", "Novela"));        
        Autor autor1 = new Autor("Charles Dickens", "UK", libros1);
        //2 libros
        ArrayList<Libro> libros2 = new ArrayList<>();
        libros2.add(new Libro("El hobbit", "Novela"));
        libros2.add(new Libro("El Silmarillion", "Novela"));        
        Autor autor2 = new Autor("J. R. R. Tolkien", "Sud Africa", libros2);
        //3 libros
        ArrayList<Libro> libros3 = new ArrayList<>();
        libros3.add(new Libro("La isla misteriosa", "Novela"));
        libros3.add(new Libro("La jangada", "Novela"));
        libros3.add(new Libro("Doctor ox", "Cuentos"));
        Autor autor3 = new Autor("Julio Verne", "Francia", libros3);
        //cero libros
        Autor autor4 = new Autor("H. G. Wells", "UK", new ArrayList<>());

        //se agregan al modelo
        model.addAutor(autor1);
        model.addAutor(autor2);
        model.addAutor(autor3);
        model.addAutor(autor4);
        
        //se establece el modelo de datos
        jTable1.setModel(model); 
        //se asignan los render a las celdas y el encabezado
        jTable1.getTableHeader().setDefaultRenderer(new EncabezadoRenderer());        
        
        TableColumnModel tcm = jTable1.getColumnModel();
        for (int it = 0; it < tcm.getColumnCount(); it++) {
            tcm.getColumn(it).setCellRenderer(new CeldaRenderer());
        }
    }

Nuestra tabla con subtablas ya esta lista

custom jtable

enjoy!

Tags

Artículos similares

Escalar imagen en java

Esta aplicacion permite escalar una imagen desde java sin perder las proporciones de la misma, utiliza SCALE_AREA_AVERAG[...]

Control de Stock en Java (Parte 1)

Un SGA «Sistema de Gestión de Almacenes»  es un programa informático destinado a gestionar las entradas y salidas de pro[...]

Cargar fuente TTF

Necesitamos: Android Studio 2 tipos de fuente TTF Agregar Archivo de fuente al proyecto Paso 1: Crear carpeta assets Cli[...]

CODOTA: Programación Inteligente

La Inteligencia Artificial (AI – Artificial Intelligence) estará presente en casi cada aspecto de nuestra vida en[...]

Gráficos iReport con parámetros tipo Date

Matando dos pájaros de un solo tiro :), doy respuesta a un par de preguntas que están relacionadas, en este post veremos[...]

GSON: Generar objetos java desde JSON

En un post anterior vimos como utilizar GSON para serializar un objeto java en JSON, en esta oportunidad se vera el proc[...]