metodos datos agregar java swing jtable tablemodel abstracttablemodel

java - datos - Trabajar con varios modelos de tablas personalizadas evitando el código repetitivo



agregar datos a un jtable netbeans (2)

Estoy trabajando en un proyecto en el que tenemos varias clases de dominio para modelar datos comerciales. Esas clases son simples POJO y tengo que mostrar varias tablas usándolas. Por ejemplo, considere esta clase:

public class Customer { private Long id; private Date entryDate; private String name; private String address; private String phoneNumber; public Customer(Long id, Date entryDate, String name, String address, String phoneNumber) { this.id = id; this.entryDate = entryDate; this.nombre = name; this.domicilio = address; this.telefono = phoneNumber; } // Getters and setters here }

He creado mi propio modelo de tabla que se extiende desde AbstractTableModel para trabajar directamente con la clase Customer :

public class CustomerTableModel extends AbstractTableModel { private final List<String> columnNames; private final List<Customer> customers; public CustomerTableModel() { String[] header = new String[] { "Entry date", "Name", "Address", "Phone number" }; this.columnNames = Arrays.asList(header); this.customers = new ArrayList<>(); } @Override public Class<?> getColumnClass(int columnIndex) { switch (columnIndex) { case 0: return Date.class; case 1: return String.class; case 2: return String.class; case 3: return String.class; default: throw new ArrayIndexOutOfBoundsException(columnIndex); } } @Override public Object getValueAt(int rowIndex, int columnIndex) { Customer customer = getCustomer(rowIndex); switch (columnIndex) { case 0: return customer.getEntryDate(); case 1: return customer.getName(); case 2: return customer.getAddress(); case 3: return customer.getPhoneNumber(); default: throw new ArrayIndexOutOfBoundsException(columnIndex); } } @Override public boolean isCellEditable(int rowIndex, int columnIndex) { return true; } @Override public void setValueAt(Object aValue, int rowIndex, int columnIndex) { if (columnIndex < 0 || columnIndex >= getColumnCount()) { throw new ArrayIndexOutOfBoundsException(columnIndex); } else { Customer customer = getCustomer(rowIndex); switch (columnIndex) { case 0: customer.setEntryDate((Date)aValue); break; case 1: customer.setName((String)aValue); break; case 2: customer.setAddress((String)aValue); break; case 3: customer.setPhoneNumber((String)aValue); break; } fireTableCellUpdated(rowIndex, columnIndex); } } @Override public int getRowCount() { return this.customers.size(); } @Override public int getColumnCount() { return this.columnNames.size(); } @Override public String getColumnName(int columnIndex) { return this.columnNames.get(columnIndex); } public void setColumnNames(List<String> columnNames) { if (columnNames != null) { this.columnNames.clear(); this.columnNames.addAll(columnNames); fireTableStructureChanged(); } } public List<String> getColumnNames() { return Collections.unmodifiableList(this.columnNames); } public void addCustomer(Customer customer) { int rowIndex = this.customers.size(); this.customers.add(customer); fireTableRowsInserted(rowIndex, rowIndex); } public void addCustomers(List<Customer> customerList) { if (!customerList.isEmpty()) { int firstRow = this.customers.size(); this.customers.addAll(customerList); int lastRow = this.customers.size() - 1; fireTableRowsInserted(firstRow, lastRow); } } public void insertCustomer(Customer customer, int rowIndex) { this.customers.add(rowIndex, customer); fireTableRowsInserted(rowIndex, rowIndex); } public void deleteCustomer(int rowIndex) { if (this.customers.remove(this.customers.get(rowIndex))) { fireTableRowsDeleted(rowIndex, rowIndex); } } public Customer getCustomer(int rowIndex) { return this.customers.get(rowIndex); } public List<Customer> getCustomers() { return Collections.unmodifiableList(this.customers); } public void clearTableModelData() { if (!this.customers.isEmpty()) { int lastRow = customers.size() - 1; this.customers.clear(); fireTableRowsDeleted(0, lastRow); } } }

Hasta ahora todo está bien. Sin embargo, este enfoque tiene al menos dos problemas:

  1. Como tengo que implementar un modelo de tabla por clase, generaré muchos códigos repetitivos para esencialmente hacer tres cosas: definir un encabezado de tabla apropiado, agregar / eliminar objetos a / de una estructura subyacente (lista), anular tanto setValueAt() y getValueAt() métodos para trabajar con objetos definidos por el usuario.

  2. Digamos que tengo la misma lista de Clientes, pero tengo que presentar esto en dos tablas diferentes, con diferente encabezado o datos. Tendría que crear una subclase de mi modelo de tabla e invalidar lo que sea que deba ser anulado para cumplir con este requisito. No se siente elegante en absoluto.

Pregunta: ¿Hay alguna manera de deshacerse del código repetitivo que hace que mi modelo de mesa sea flexible y reutilizable?


Al igual que otros modelos de Swing (es decir, DefaultComboBoxModel , DefaultListModel ), podemos usar Generics para crear un modelo de tabla flexible y reutilizable, que también proporcione una API para trabajar con POJO definidos por el usuario.

Este modelo de mesa tendrá las siguientes características especiales:

  • Se extiende desde AbstractTableModel para aprovechar el manejo de eventos de modelo de tabla.
  • A diferencia de CustomerTableModel mostrado anteriormente, este modelo de tabla tiene que ser abstracto porque no debe anular getValueAt() método getValueAt() : simplemente porque no conocemos la clase o el tipo de datos que manejará este modelo de tabla, la tarea de anular el método mencionado se deja a las subclases.
  • Hereda la implementación de setValueAt() vacía de AbstractTableModel . Tiene sentido porque isCellEditable() también se hereda de esa clase y siempre devuelve false .
  • La implementación predeterminada de getColumnClass() también se hereda y siempre devuelve Object.class .

Estas características hacen que este modelo de mesa sea realmente fácil de implementar según nuestros requisitos:

  • Si necesitamos mostrar una tabla de solo lectura, entonces tenemos que anular los 2 métodos top: getValueAt() y getColumnClass() (este último es recomendado, pero no obligatorio).
  • Si nuestra tabla necesita ser editable, entonces tenemos que anular 4 métodos arriba: los dos mencionados anteriormente más isCellEditable() y setValueAt() .

Echemos un vistazo al código de nuestro modelo de tabla:

import java.util.ArrayList; import java.util.Collections; import java.util.List; import javax.swing.table.AbstractTableModel; /** * Abstract base class which extends from {@code AbstractTableModel} and * provides an API to work with user-defined POJO''s as table rows. Subclasses * extending from {@code DataObjectTableModel} must implement * {@code getValueAt(row, column)} method. * <p /> * By default cells are not editable. If those have to be editable then * subclasses must override both {@code isCellEditable(row, column)} and * {@code setValueAt(row, column)} methods. * <p /> * Finally, it is not mandatory but highly recommended to override * {@code getColumnClass(column)} method, in order to return the appropriate * column class: default implementation always returns {@code Object.class}. * * @param <T> The class handled by this TableModel. * @author dic19 */ public abstract class DataObjectTableModel<T> extends AbstractTableModel { private final List<String> columnNames; private final List<T> data; public DataObjectTableModel() { this.data = new ArrayList<>(); this.columnNames = new ArrayList<>(); } public DataObjectTableModel(List<String> columnIdentifiers) { this(); if (columnIdentifiers != null) { this.columnNames.addAll(columnIdentifiers); } } @Override public int getRowCount() { return this.data.size(); } @Override public int getColumnCount() { return this.columnNames.size(); } @Override public String getColumnName(int columnIndex) { return this.columnNames.get(columnIndex); } public void setColumnNames(List<String> columnNames) { if (columnNames != null) { this.columnNames.clear(); this.columnNames.addAll(columnNames); fireTableStructureChanged(); } } public List<String> getColumnNames() { return Collections.unmodifiableList(this.columnNames); } public void addDataObject(T dataObject) { int rowIndex = this.data.size(); this.data.add(dataObject); fireTableRowsInserted(rowIndex, rowIndex); } public void addDataObjects(List<T> dataObjects) { if (!dataObjects.isEmpty()) { int firstRow = data.size(); this.data.addAll(dataObjects); int lastRow = data.size() - 1; fireTableRowsInserted(firstRow, lastRow); } } public void insertDataObject(T dataObject, int rowIndex) { this.data.add(rowIndex, dataObject); fireTableRowsInserted(rowIndex, rowIndex); } public void deleteDataObject(int rowIndex) { if (this.data.remove(this.data.get(rowIndex))) { fireTableRowsDeleted(rowIndex, rowIndex); } } public void notifyDataObjectUpdated(T domainObject) { T[] elements = (T[])data.toArray(); for (int i = 0; i < elements.length; i++) { if(elements[i] == domainObject) { fireTableRowsUpdated(i, i); } } } public T getDataObject(int rowIndex) { return this.data.get(rowIndex); } public List<T> getDataObjects(int firstRow, int lastRow) { List<T> subList = this.data.subList(firstRow, lastRow); return Collections.unmodifiableList(subList); } public List<T> getDataObjects() { return Collections.unmodifiableList(this.data); } public void clearTableModelData() { if (!this.data.isEmpty()) { int lastRow = data.size() - 1; this.data.clear(); fireTableRowsDeleted(0, lastRow); } } }

Entonces, tomando este modelo de tabla y clase de Customer , una implementación completa se verá así:

String[] header = new String[] {"Entry date", "Name", "Address", "Phone number"}; DataObjectTableModel<Customer> model = new DataObjectTableModel<>(Arrays.asList(header)) { @Override public Class<?> getColumnClass(int columnIndex) { switch (columnIndex) { case 0: return Date.class; case 1: return String.class; case 2: return String.class; case 3: return String.class; default: throw new ArrayIndexOutOfBoundsException(columnIndex); } } @Override public Object getValueAt(int rowIndex, int columnIndex) { Customer customer = getDataObject(rowIndex); switch (columnIndex) { case 0: return customer.getEntryDate(); case 1: return customer.getName(); case 2: return customer.getAddress(); case 3: return customer.getPhoneNumber(); default: throw new ArrayIndexOutOfBoundsException(columnIndex); } } @Override public boolean isCellEditable(int rowIndex, int columnIndex) { return true; } @Override public void setValueAt(Object aValue, int rowIndex, int columnIndex) { if (columnIndex < 0 || columnIndex >= getColumnCount()) { throw new ArrayIndexOutOfBoundsException(columnIndex); } else { Customer customer = getDataObject(rowIndex); switch (columnIndex) { case 0: customer.setEntryDate((Date)aValue); break; case 1: customer.setName((String)aValue); break; case 2: customer.setAddress((String)aValue); break; case 3: customer.setPhoneNumber((String)aValue); break; } fireTableCellUpdated(rowIndex, columnIndex); } } };

Como podemos ver, en algunas líneas de código (LOC <50) tenemos una implementación completa.

¿Funciona con entidades JPA?

Lo hace mientras las entidades tengan getters y setters públicos. A diferencia de las implementaciones de JPA, este modelo de tabla no funciona con reflexión, por lo que tendremos que acceder a las propiedades del objeto utilizando la interfaz pública de la clase para implementar los métodos getValueAt() y setValueAt() .

¿Funciona con JDBC?

No, no. Tendríamos que envolver los conjuntos de resultados en clases de dominio y usar la interfaz de clase ofrecida como se mencionó anteriormente.

¿Funciona con las clases predeterminadas de Java?

Sí lo hace. Una vez más, usando la interfaz ofrecida por la clase. Por ejemplo, tomemos la clase java.io.File , podríamos tener la siguiente implementación del modelo de tabla:

String[] header = new String[] { "Name", "Full path", "Last modified", "Read", "Write", "Execute", "Hidden", "Directory" }; DataObjectTableModel<File> model = new DataObjectTableModel<File>(Arrays.asList(header)) { @Override public Class<?> getColumnClass(int columnIndex) { switch (columnIndex) { case 0: return String.class; case 1: return String.class; case 2: return Date.class; case 3: return Boolean.class; case 4: return Boolean.class; case 5: return Boolean.class; case 6: return Boolean.class; case 7: return Boolean.class; default: throw new ArrayIndexOutOfBoundsException(columnIndex); } } @Override public Object getValueAt(int rowIndex, int columnIndex) { File file = getDataObject(rowIndex); switch (columnIndex) { case 0: return file.getName(); case 1: return file.getAbsolutePath(); case 2: return new Date(file.lastModified()); case 3: return file.canRead(); case 4: return file.canWrite(); case 5: return file.canExecute(); case 6: return file.isHidden(); case 7: return file.isDirectory(); default: throw new ArrayIndexOutOfBoundsException(columnIndex); } } };


En la misma línea que la respuesta de dic19, puede usar el modelo de tabla de filas que también usa genéricos y proporciona muchos métodos comunes que le permiten actualizar dinámicamente el modelo de tabla .

También necesitará implementar un par de métodos ya que el modelo también es abstracto. El código JButtonTableModel.java muestra cómo puedes hacer esto.

Además, si quieres ser realmente sofisticado, puedes mirar el Bean Table Model (enlace que se encuentra en el blog anterior), que extiende el RowTableModel . Este modelo usa la reflexión para construir TableModel, por lo que no tiene que implementar un modelo personalizado.