El componente JTable es uno de los mas complejos que nos ofrece el paquete Swing en cuanto a diseño de interfaces se refiere, precisamente por su complejidad también permite una personalización casi absoluta del componente.
La clase JTable sirve para mostrar tablas de datos, permitiendo opcionalmente al usuario editar los datos. Los JTable no contienen ni almacenan datos, simplemente es una manera de mostrarlos.
Los JScrollPane son los componentes que habitualmente sirven como contenedor para los JTable, este automáticamente coloca la cabecera de la tabla en la parte superior de la vista de manera que los nombres de las columnas permanecen visibles cuando hacemos “scroll”.
El encargado de decirnos como se muestra un valor en cada celda es el TableCellRenderer, esta interfaz define el método requerido por un objeto que querría ser dibujado en las celdas de un JTable.
Así mismo, el encargado de decirnos como se comporta cada celda es el TableCellEditor, una interfaz que define el método que cualquier objeto que querría ser editor de valores de un JTable necesita implementar.
Otro de los componentes mas importantes es el TableModel, esta interfaz especifica los métodos que el JTable usará para interrogar a un modelo de datos tabulado. Un JTable puede configurarse para mostrar cuanlquier modelo de datos que implemente esta interfaz con solo un par de líneas de código:
MyTableModel model = new MyTableModel(columnNames, data); // Establecemos el modelo JTable table = new JTable(model);
En nuestro ejemplo de hoy, explicamos como mostrar componentes complejos como JCheckBox, JComboBox y JButton en el interior de nuestras tablas.
En primer lugar definiremos nuestro TableModel, en este estableceremos como queremos que nuestra tabla se comporte.
package tablaCompleja.componentes; import javax.swing.table.AbstractTableModel; public class MyTableModel extends AbstractTableModel { /** Nombre de las columnas. */ private String[] columnNames; /** Datos. */ private Object[][] data; /** * Constructor. * @param columnNames Nombres de las columnas * @param data Datos de la tabla */ public MyTableModel(String[] columnNames, Object[][] data) { this.columnNames = columnNames; this.data = data; } @Override public String getColumnName(int column) { // Nombre de las columnas para la cabecera return columnNames[column]; } @Override public int getRowCount() { // Devuelve el número de filas return data != null ? data.length : 0; } @Override public int getColumnCount() { // Devuelve el número de columnas return columnNames.length; } /** * Nos devolverá la clase que contiene cada columna, * es necesario para trabajar correctamente con los componentes * que mostraremos en la tabla. */ @Override public Class getColumnClass(int columnIndex) { Class clazz = Object.class; Object aux = getValueAt(0, columnIndex); if (aux != null) { clazz = aux.getClass(); } return clazz; } @Override public Object getValueAt(int rowIndex, int columnIndex) { // Devuelve el valor que se debe mostrar en la tabla en cada celda return data[rowIndex][columnIndex]; } @Override public void setValueAt(Object aValue, int rowIndex, int columnIndex) { // Si queremos que la tabla sea editable deberemos establecer estos valores data[rowIndex][columnIndex] = aValue; fireTableCellUpdated(rowIndex, columnIndex); } @Override public boolean isCellEditable(int rowIndex, int columnIndex) { // Permitimos editar todas las celdas de la tabla return true; } /** * Nos servira para limpiar la información de una fila * @param row */ public void reset(int row) { for (int i = 0; i < data[row].length - 1; i++) { // Para las columnas con String if (getColumnClass(i) == String.class) { setValueAt("", row, i); } else if(getColumnClass(i) == Boolean.class) { setValueAt(false, row, i); } } } }Vamos a crear un JTable personalizado que heredará del JTable original, en esta clase vamos a ver como especificar que renderers y editors usaremos para cada columna dependiendo del tipo de objeto que contenga.
package tablaCompleja; import javax.swing.DefaultCellEditor; import javax.swing.JButton; import javax.swing.JCheckBox; import javax.swing.JComboBox; import javax.swing.JTable; import tablaCompleja.componentes.ButtonCellEditor; import tablaCompleja.componentes.ButtonCellRenderer; import tablaCompleja.componentes.MyTableModel; public class MyTable extends JTable { /** * Constructor. */ public MyTable() { this.setRowHeight(30); JComboBox states = new JComboBox(new String[] {"España", "Argentina", "EEUU"} ); String [] columnNames = new String[]{ "Nombre", "email", "Fumador", "Nacionalidad", "" }; Object [][] data = new Object[][]{ {"Angel", "angelarcosheredia@gmail.com", false, "Click para elegir", new JButton("Reset")}, {"Juan", "juan@gmail.com", false, "Click para elegir", new JButton("Reset")}, {"Ana", "ana@hotmail.com", false, "Click para elegir", new JButton("Reset")} }; MyTableModel model = new MyTableModel(columnNames, data); // Establecemos el modelo this.setModel(model); // Establecemos el renderer y editor que usaremos para el boton this.setDefaultRenderer(JButton.class, new ButtonCellRenderer()); this.setDefaultEditor(JButton.class, new ButtonCellEditor()); // Editores para cada tipo de objeto, estos nos permitirán darles el comportamiento adecuado this.getColumn("Nacionalidad").setCellEditor(new DefaultCellEditor(states)); this.setDefaultEditor(JCheckBox.class, new DefaultCellEditor(new JCheckBox())); } }Como vemos en el código anterior los valores boolean se muestran automáticamente como un JCheckBox, y el DefaultCellEditor que es el editor por defecto que usa un JTable también se encargará de utilizar un JComboBox apropiadamente si se lo especificamos. Para el renderizar y darle comportamiento a un JButton si tendremos que implementar nuestro propio renderer y editor. Aquí os dejo el código que, como podéis ver, no es nada complicado.
package tablaCompleja.componentes; import java.awt.Component; import javax.swing.JButton; import javax.swing.JTable; import javax.swing.table.TableCellRenderer; public class ButtonCellRenderer implements TableCellRenderer { @Override public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) { // Devolvemos el botón tal cual if (value instanceof JButton) { return (JButton) value; } return null; } }
package tablaCompleja.componentes; import java.awt.Component; import java.awt.event.ActionEvent; import javax.swing.AbstractAction; import javax.swing.AbstractCellEditor; import javax.swing.JButton; import javax.swing.JTable; import javax.swing.table.TableCellEditor; public class ButtonCellEditor extends AbstractCellEditor implements TableCellEditor { /** Componente que estamos editando. */ private Component currentValue; @Override public Component getTableCellEditorComponent(final JTable table, Object value, boolean isSelected, final int row, int column) { JButton button = null; if (value instanceof JButton) { button = (JButton) value; // Action que permite "limpiar" los valores de una fila button.setAction(new AbstractAction("Reset") { @Override public void actionPerformed(ActionEvent e) { ((MyTableModel) table.getModel()).reset(row); } }); } currentValue = button; return button; } @Override public Object getCellEditorValue() { return currentValue; } }Y ahora ya solo nos queda el fragmento de código para echar a andar nuestro ejemplo:
package tablaCompleja.componentes; import java.awt.Component; import java.awt.event.ActionEvent; import javax.swing.AbstractAction; import javax.swing.AbstractCellEditor; import javax.swing.JButton; import javax.swing.JTable; import javax.swing.table.TableCellEditor; public class ButtonCellEditor extends AbstractCellEditor implements TableCellEditor { /** Componente que estamos editando. */ private Component currentValue; @Override public Component getTableCellEditorComponent(final JTable table, Object value, boolean isSelected, final int row, int column) { JButton button = null; if (value instanceof JButton) { button = (JButton) value; // Action que permite "limpiar" los valores de una fila button.setAction(new AbstractAction("Reset") { @Override public void actionPerformed(ActionEvent e) { ((MyTableModel) table.getModel()).reset(row); } }); } currentValue = button; return button; } @Override public Object getCellEditorValue() { return currentValue; } }
Espero haberos aclarado un poco más como funciona un JTable y que todo esto os haya sido útil de alguna manera.
Un saludo y hasta pronto.