Por favor, activa JavaScript y desactiva tu adblock para este sitio

El Javatar

Blog dedicado a la Programación en Java, C, PHP, Phyton, HTML, SQL y Mucho Más

martes, 24 de abril de 2018

SwingUtils - Crear JTable Paginado o Lazy y con Filtros (Java Swing)

En este tutorial, aprenderemos cómo crear de forma rápida un JTable que tenga paginación dinámica, que también permita carga dinámica (lazy), y que además acepte filtros por columna; para esto haremos uso de la librería SwingUtils que posee un componente llamado PaginatedTable, el cual extiende de la clase javax.swing.JPanel y que internamente hace uso de un javax.swing.JTable.

SwingUtils - Crear JTable Paginado o Lazy y con Filtros (Java Swing)

Nativamente, el API de Java Swing no nos permite agregarle de forma rápida un paginador a un JTable, así que si queremos tener esta funcionalidad, deberíamos programarla nosotros mismos. Una forma "sencilla" de hacerlo es implementando un TableRowSorter; sin embargo el problema radica cuando queremos aplicar un filtro, por ejemplo de búsqueda a nuestra tabla, ya que sería un poco complejo el actualizar nuestro paginador.

Por eso, en la librería SwingUtils he creado un componente llamado PaginatedTable el cual está diseñado para trabajar con dos tipos de tablas paginadas. Veamos cuáles son y en qué escenarios usaríamos cada una de ellas:

- Tablas paginadas con carga completa: Son aquellas que usaríamos cuando sabemos que el volumen de datos de nuestra tabla no es muy grande y podríamos cargarlos todos sin afectar de manera radical a la memoria. Con este tipo de carga, obtenemos toda la lista de datos desde el principio, y los resultados de filtros y paginación se devuelven desde esa lista inicial.

- Tablas paginadas con carga dinámica o lazy (perezosa): Son aquellas que usaríamos cuando sabemos que el volumen de datos de nuestra tabla puede llegar a ser muy grande y que cargarlos todos podría causar problemas en la memoria. Con este tipo de carga, vamos obteniendo dinámicamente los datos, es decir, que sólo tenemos en memoria los datos según la paginación y filtros que tengamos aplicados en el momento.

Ahora que sabemos en qué escenarios podemos usar el componente de paginación de tablas de SwingUtils, veamos cómo podríamos usarlo para cada uno de ellos.

Crear una Tabla Paginada o Lazy y con Filtros en Java Swing usando PaginatedTable

Lo primero que debemos hacer es importar la librería SwingUtils a nuestro proyecto. Puedes ver las instrucciones en el siguiente enlace:


Debido a la facilidad de cambiar de implementación entre tablas paginadas con carga completa y tablas paginadas con carga dinámica, el proceso que se aplica para cada uno es el mismo. La diferencia principal está en la forma en cómo obtenemos los datos para cada uno de los casos.

Así que empezamos creando la clase que usaremos como modelo de datos (bean) para nuestro ejemplo:

public class Persona {

    private String codigo;
    private String nombres;
    private String apellidos;
    private String direccion;
    private String telefono;
    
    public Persona() {
        // Constructor vacío por defecto
    }
    
    public Persona(String codigo, String nombres, String apellidos, String direccion, String telefono) {
        this.codigo = codigo;
        this.nombres = nombres;
        this.apellidos = apellidos;
        this.direccion = direccion;
        this.telefono = telefono;
    }
    
    public String getCodigo() {
        return codigo;
    }
    
    public void setCodigo(String codigo) {
        this.codigo = codigo;
    }
    
    public String getNombres() {
        return nombres;
    }
    
    public void setNombres(String nombres) {
        this.nombres = nombres;
    }
    
    public String getApellidos() {
        return apellidos;
    }
    
    public void setApellidos(String apellidos) {
        this.apellidos = apellidos;
    }
    
    public String getDireccion() {
        return direccion;
    }
    
    public void setDireccion(String direccion) {
        this.direccion = direccion;
    }
    
    public String getTelefono() {
        return telefono;
    }
    
    public void setTelefono(String telefono) {
        this.telefono = telefono;
    }

}

A continuación, creamos la clase que usaremos como TableModel de nuestro JTable, el cual extiende de la clase TableModelGeneric de la librería SwingUtils tal como lo vimos en el tutorial sobre cómo crear un JTable con un Modelo de Datos Genérico en Java Swing:

import com.eljavatar.swingutils.core.modelcomponents.FilterMatchModeEnum;
import com.eljavatar.swingutils.core.modelcomponents.ObjectFilter;
import com.eljavatar.swingutils.core.modelcomponents.TableModelGeneric;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

public class TableModelGestionPersonas extends TableModelGeneric<Persona> {

    public TableModelGestionPersonas() {
        this(new ArrayList<>());
    }
    
    public TableModelGestionPersonas(List<Persona> listDatos) {
        super(
                new Class[]{String.class, String.class, String.class, String.class, String.class},
                new String[]{"Código", "Nombres", "Apellidos", "Dirección", "Teléfono"},
                Arrays.asList(new ObjectFilter("Código", "codigo", FilterMatchModeEnum.EXACT), new ObjectFilter("Nombres", "nombres", FilterMatchModeEnum.CONTAINS), new ObjectFilter("Apellidos", "apellidos", FilterMatchModeEnum.CONTAINS)),
                listDatos
        );
    }
    
    @Override
    public Object getValueAt(int rowIndex, int columnIndex) {
        switch (columnIndex) {
            case 0:
                return getListElements().get(rowIndex).getCodigo();
            case 1:
                return getListElements().get(rowIndex).getNombres();
            case 2:
                return getListElements().get(rowIndex).getApellidos();
            case 3:
                return getListElements().get(rowIndex).getDireccion();
            case 4:
                return getListElements().get(rowIndex).getTelefono();
            default:
                return null;
        }
    }

}

El código de la vista sería el siguiente:

import com.eljavatar.swingutils.core.annotations.PaginatedTableView;
import com.eljavatar.swingutils.core.componentsutils.JTableUtils;
import com.eljavatar.swingutils.core.componentsutils.SwingComponentsUtils;
import com.eljavatar.swingutils.core.modelcomponents.LazyDataProvider;
import java.awt.Font;
import java.awt.event.MouseAdapter;
import javax.swing.JOptionPane;

public class PersonasView extends javax.swing.JFrame {

    private final PersonasController personasController;
    
    public PersonasView() {
        initComponents();
        this.personasController = new PersonasController(this);
        setModeloTablaPersonas();
        this.personasController.listarPersonas();
    }
    
    private void setModeloTablaPersonas() {
        SwingComponentsUtils.setFontAllComponents(paginatedTablePersonas.getDataTable(), new Font("Arial", Font.PLAIN, 12));
        paginatedTablePersonas.getDataTable().setSize(600, 300);
        JTableUtils.setProperties(paginatedTablePersonas.getDataTable(), new Font("Arial", Font.BOLD, 13), new TableModelGestionPersonas(), new int[]{16, 18, 18, 34, 14});
        
        PaginationDataProvider<Persona> dataProvider = this.personasController.createDataProvider();
        //LazyDataProvider<Persona> dataProvider = this.personasController.createLazyDataProvider();
        
        paginatedTablePersonas.decorateAndSet(dataProvider, new int[]{5, 10, 20, 50, 75, 100}, 10, true, new Font("Arial", Font.PLAIN, 12));
        
        paginatedTablePersonas.getDataTable().addMouseListener(new MouseAdapter() {
            @Override
            public void mouseClicked(java.awt.event.MouseEvent e) {
                int fila = paginatedTablePersonas.getDataTable().rowAtPoint(e.getPoint());
                if (fila > -1) {
                    if (e.getClickCount() == 2) {
                        seleccionarElement(fila);
                    }
                }
            }
        });
    }
    
    private void seleccionarElement(int fila) {
        if (fila >= 0) {
            Persona persona = paginatedTablePersonas.getTableModelGeneric().getListElements().get(fila);
            personasController.setPersona(persona);
            personasController.mostrarPersona();
        } else {
            JOptionPane.showMessageDialog(this, "Debe hacer clic en una fila para poder seleccionar un Elemento", "Mensaje de Error", JOptionPane.ERROR_MESSAGE);
        }
    }
    
    private void initComponents() {
        // Código generado por NetBeans
    }
    
    @PaginatedTableView(name = "listaPersonas")
    private com.eljavatar.swingutils.core.components.PaginatedTable<Persona> paginatedTablePersonas;
    
}

En este código he de recalcar 3 líneas. Las líneas 25 y 26 crean la implementación para cada uno de los tipos de tablas paginadas que expliqué al inicio de este tutorial. Basta sólo con comentar uno y descomentar el otro para cambiar de implementación.

En la línea 28 es donde creamos la paginación con el proveedor que hemos escogido y las otras propiedades que requerimos. Los parámetros que pasamos son los siguientes: El proveedor de datos, un array con los tamaños de página que le daremos al usuario para escoger, el tamaño de página inicial que verá el usuario, una variable que indica si mostraremos un filtro global, y finalmente la fuente de los componentes del paginador.

Ahora veamos el código del controlador:

import com.eljavatar.swingutils.core.annotations.PropertyController;
import com.eljavatar.swingutils.core.beansupdate.AbstractObserverController;
import com.eljavatar.swingutils.core.beansupdate.ObjectUpdate;
import com.eljavatar.swingutils.core.beansupdate.TipoUpdateEnum;
import com.eljavatar.swingutils.core.modelcomponents.LazyDataProvider;
import com.eljavatar.swingutils.core.modelcomponents.PaginationDataProvider;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import javax.swing.JOptionPane;

public class PersonasController extends AbstractObserverController<PersonasController, PersonasView> {

    private final PersonasView personasView;
    private final PersonasDao personasDao;
    private Persona persona;
    
    @PropertyController
    private List<Persona> listaPersonas;

    public PersonasController(PersonasView personasView) {
        this.personasView = personasView;
        this.personasDao = new PersonasDao();
        this.listaPersonas = new ArrayList<>();
        
        super.setListeners(PersonasController.this, this.personasView);
    }
    
    public void mostrarPersona() {
        String mensaje = "Código: " + persona.getCodigo() + "\n"
                + "Nombres: " + persona.getNombres()+ "\n"
                + "Apellidos: " + persona.getApellidos()+ "\n"
                + "Dirección: " + persona.getDireccion()+ "\n"
                + "Teléfono: " + persona.getTelefono();
        JOptionPane.showMessageDialog(personasView, mensaje);
    }
    
    public void listarPersonas() {
        this.listaPersonas = personasDao.generarDatos();
        super.changeData(new ObjectUpdate(TipoUpdateEnum.VIEW, Arrays.asList("listaPersonas")));
    }
    
    public PaginationDataProvider<Persona> createDataProvider() {
        return new PaginationDataProvider(listaPersonas);
    }
    
    public LazyDataProvider<Persona> createLazyDataProvider() {
        return new LazyDataProvider<Persona>() {
            @Override
            public List<Persona> getRows(int first, int last, Map<String, Object> filters) {
                Object[] response = getPersonasDao().getResultado(first, last, filters);
                this.setListData((List<Persona>) response[0]);
                this.setRowCount((int) response[1]);
                return getListData();
            }
        };
    }

    public List<Persona> getListaPersonas() {
        return listaPersonas;
    }

    public void setListaPersonas(List<Persona> listaPersonas) {
        this.listaPersonas = listaPersonas;
    }

    public PersonasDao getPersonasDao() {
        return personasDao;
    }

    public Persona getPersona() {
        return persona;
    }

    public void setPersona(Persona persona) {
        this.persona = persona;
    }
    
}

En este código podemos resaltar 2 métodos. En la línea 44 vemos el método que crea el proveedor de datos para tablas paginadas con carga completa; y en la línea 48 vemos el método que crea el proveedor de datos para tablas paginadas con carga dinámica.

A continuación vemos nuestra clase DAO, la cual simula la capa de servicios de datos, desde donde obtenemos los datos en métodos que usamos desde nuestro controlador para cada uno de los casos:

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

public class PersonasDao {

    private static List<Persona> listaDatos;

    static {
        listaDatos = new ArrayList<>();
        listaDatos.add(new Persona("US-NZEPSJ", "Armando", "Costa", "684 West Branch Rd. ", "35857229"));
        listaDatos.add(new Persona("US-PXCZSJ", "Emmy", "Spence", "Maineville, OH 45039", "85462552"));
        listaDatos.add(new Persona("US-AHFPZZ", "Edith", "Higgins", "55 Saxton St. ", "52524848"));
        listaDatos.add(new Persona("US-KRSEBV", "Isabelle", "Bonilla", "Joliet, IL 60435", "97957632"));
        listaDatos.add(new Persona("US-BTJUXD", "Elizabeth", "Haley", "74 Depot Street ", "92693956"));
        listaDatos.add(new Persona("US-RKHERS", "Giuliana", "Crawford", "Mobile, AL 36605", "34229289"));
        listaDatos.add(new Persona("US-LTYKWV", "Marilyn", "Osborne", "69 Bedford St. ", "74277594"));
        listaDatos.add(new Persona("US-PHXGVM", "Justin", "Rogers", "Macon, GA 31204", "85363778"));
        listaDatos.add(new Persona("US-XJVRXW", "Joselyn", "Curtis", "55 Princess St. ", "82264893"));
        listaDatos.add(new Persona("US-YRMVQP", "Dillon", "Savage", "Reynoldsburg, OH 43068", "49627366"));
        // Generamos una lista de 200 o más datos
    }

    public List<Persona> generarDatos() {
        return listaDatos.subList(0, 50);
    }

    public Object[] getResultado(int first, int last, Map<String, Object> filters) {
        List<Persona> listaFiltrada = null;
        int rowCount;

        if (filters.containsKey("codigo") && !filters.get("codigo").toString().isEmpty()) {
            listaFiltrada = listaDatos.stream().filter(persona -> persona.getCodigo().matches("(?i)(.*)" + filters.get("codigo").toString().trim() + "(.*)")).collect(Collectors.toList());
        }

        if (filters.containsKey("nombres") && !filters.get("nombres").toString().isEmpty()) {
            listaFiltrada = listaDatos.stream().filter(persona -> persona.getNombres().matches("(?i)(.*)" + filters.get("nombres").toString().trim() + "(.*)")).collect(Collectors.toList());
        }

        if (filters.containsKey("apellidos") && !filters.get("apellidos").toString().isEmpty()) {
            listaFiltrada = listaDatos.stream().filter(persona -> persona.getApellidos().matches("(?i)(.*)" + filters.get("apellidos").toString().trim() + "(.*)")).collect(Collectors.toList());
        }

        if (listaFiltrada == null) {
            if (last > listaDatos.size()) {
                last = listaDatos.size();
            }
            rowCount = listaDatos.size();
            listaFiltrada = listaDatos.subList(first, last);
        } else {
            if (last > listaFiltrada.size()) {
                last = listaFiltrada.size();
            }
            rowCount = listaFiltrada.size();
            listaFiltrada = listaFiltrada.subList(first, last);
        }
        
        return new Object[]{listaFiltrada, rowCount};
    }

}

Finalmente sólo nos resta ejecutar la clase PersonaView para ver el resultado, el cual es el siguiente:

JTable Paginado o Lazy y con Filtros - SwingUtils

Como podemos apreciar, crear JTable paginados en Java Swing es muy sencillo haciendo uso de la librería SwingUtils. Por otro lado, si tienes dudas sobre cómo usar éste componente, puedes escribirlas en la caja de comentarios.

2 comentarios:

  1. file:///home/pedroarevalo/Im%C3%A1genes/Captura%20de%20pantalla%20de%202018-05-25%2000-34-38.png

    que estoy haciendo mal??

    ResponderBorrar
    Respuestas
    1. Pedro, sube la imagen a un hosting de imágenes para poder visualizarla

      Borrar