miércoles, 18 de enero de 2012

JButton, JCheckBox, JComboBox en JTable


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.

11 comentarios:

Anónimo dijo...

deja imagenes para saber como queda....

Anónimo dijo...

buen codigo solo te falto el metodo main

pero todo jalo perfecto

Anónimo dijo...

buen aporte gracias

Lich dijo...

Por eso odio a java, 999999 lineas de código para hacer una maricada.

Unknown dijo...

Por fin! Muchas gracias!!!

Anónimo dijo...

Muchas Gracias me sirvio de Mucho

David dijo...

pues a mi no me funciona... alguien me puede echar una mano?

Unknown dijo...

@David, sin dar datos sobre porque no te funciona dudo mucho que nadie te pueda ayudar

Unknown dijo...

Agregar un JComboBox en solo 1 metodo para cualquier tabla:
public void insertarComboBox(JTable tabla){ //PARAMETRO: la tabla a modificar
Object[] items = {"item 1","item 2"," ..... "}; //items del combobox

cbxModelo = new DefaultComboBoxModel(items); //agrego items al modelo
cbxItems = new JComboBox(cbxModelo); //le agrego el modelo al combobox
cbxItems.setEditable(true); //le digo que pueda ser editable

columna = tabla.getColumnModel().getColumn(1); //defino cual sera la columna a modificar
editorDeCeldas = new DefaultCellEditor(cbxItems); //le paso al editor de celdas el combobox
columna.setCellEditor(editorDeCeldas); //le digo a la columna que se modifique metiendo los items

}

Elías Gaspar dijo...

Para el que publicó cómo agregar un JComboBox en solo 1 método para cualquier tabla, muchas gracias!, no quería agregar justamente un JComboBox pero me sirvió de base para otro componente que necesitaba añadir :D

Unknown dijo...

al que publicó lo del jCombobox.. que es columna? D:
podrian ponerlo un poco más claro?

Publicar un comentario