miércoles, 18 de enero de 2012

JButton, JCheckBox, JComboBox en JTable

11 comentarios

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.