Cómo utilizar la Clase Table

Con la clase JTable, se pueden mostrar tablas de datos, y opcionalmente permitir que el usuario los edite. JTable no contiene ni almacena datos; simplemente es una vista de nuestros datos. Aquí podemos ver una tabla típica mostrada en un ScrollPane:

El resto de esta página explica como realizar algunas de las tareas más comunes relacionadas con las tablas. Aquí están los tópicos cubiertos por está página:

Crear una Tabla Sencilla

SimpleTableDemo

Intenta esto:
  1. Compila y ejecuta la aplicación. el fichero fuente es SimpleTableDemo.java.
  2. Pulsa sobre la celda que contiene "Snowboarding".
    Se selecciona toda la primera fila, indicando que has seleccionado los datos de Mary Campione. Una especial iluminación indica que la celda "Snowboarding" es editable. Generalmente, se empieza a editar una celda de texto haciendo doble-click en ella.
  3. Posiciona el cursor sobre "First Name". Ahora pulsa el botón del ratón y arrástrala hacia la derecha.
    Como puedes ver, los usuarios pueden modificar las columnas en las tablas.
  4. Posiciona el cursor justo a la derecha de una columna de cabecera. Ahora pulsa el botón del ratón y arrastralo a derecha o izquierda.
    La columna cambia su tamaño, y las demás columnas se ajustan para rellenar el espacio sobrante.
  5. Redimensiona la ventana que contiene la tabla para que sea tan grande como para contener la tabla completa.
    Todas las celdas de la tabla se agrandan, expandiéndose para llenar el espacio extra.

Aquí está el código que implementa la tabla en SimpleTableDemo.java:
Object[][] data = {
    {"Mary", "Campione", 
     "Snowboarding", new Integer(5), new Boolean(false)},
    {"Alison", "Huml", 
     "Rowing", new Integer(3), new Boolean(true)},
    {"Kathy", "Walrath",
     "Chasing toddlers", new Integer(2), new Boolean(false)},
    {"Mark", "Andrews",
     "Speed reading", new Integer(20), new Boolean(true)},
    {"Angela", "Lih",
     "Teaching high school", new Integer(4), new Boolean(false)}
};

String[] columnNames = {"First Name", 
                        "Last Name",
                        "Sport",
                        "# of Years",
                        "Vegetarian"};

final JTable table = new JTable(data, columnNames);
El ejemplo SimpleTableDemo utiliza uno de los constructores de JTable que aceptan datos directamente:
  • JTable(Object[][] rowData, Object[] columnNames)
  • JTable(Vector rowData, Vector columnNames)
La ventaja de utilizar uno de estos constructores es que es sencillo. Sin embargo, estos constructores también tienen desventajas:
  • Automáticamente hacen todas las celdas editables.
  • Tratan igual a todos los tipos de datos. Por ejemplo, si una columna tiene datos Boolean, los datos pueden mostrarse como un CheckBox en la tabla. Sin embargo, si especificamos los datos como un argumento array o vector del constructor de JTable, nuestro dato Boolean se mostrará como un string. Se pueden ver estas diferencias en las dos últimas columnas de los ejemplos ateriores.
  • Requieren que pongamos todos los datos de la tabla en un array o vector, lo que es inapropiado para algunos datos. Por ejemplo, si estamos ejemplarizando un conjunto de objetos desde una base de datos, podríamos quere pedir los objetos directamente por sus valores, en vez de copiar todos los valores en un array o un vector.
Si queremos evitar estas restricciones, necesitamos implementar nuestro propio modelo de tabla como se describe en Crear un Modelo de Tabla.

Añadir una Tabla a un Contenedor

Es fácil poner una tabla en un ScrollPane. Sólo necesitamos escribir una o dos líneas de código:
JScrollPane scrollPane = new JScrollPane(table);
table.setPreferredScrollableViewportSize(new Dimension(500, 70));
El ScrollPane obtiene automáticamente las cabeceras de la tabla, que muestra los nombres de las columnas, y los pone en la parte superior de la tabla. Incluso cuando el usuario se desplaza hacia abajo, los nombres de columnas permanecen visibles en la parte superior del área de visión. El ScrollPane también intenta hacer que su área de visión sea del mismo tamaño que el tamaño preferido de la tabla. El código anterior selecciona el tamaño preferido de la tabla con el método setPreferredScrollableViewportSize.

Nota: Antes de Swing 1.0.2, el ScrollPane no podía obtener las cabeceras de la tabla a menos que se utilizara el método JTable.createScrollPaneForTable para crearlo. Aquí tenemos unos ejemplos de código recomendado, antes y después de Swing 1.0.2:
//1.0.1 code (causes deprecation warning in 1.0.2 and later releases):
scrollPane = JTable.createScrollPaneForTable(table);

//Recommended code (causes missing column names in 1.0.1):
scrollPane = new JScrollPane(table);

Si estamos utilizando una tabla sin un ScrollPane, debemos obtener los componentes de cabecera de la tabla y situarlos nosotros mismos. Por ejemplo:
container.setLayout(new BorderLayout());
container.add(table.getTableHeader(), BorderLayout.NORTH);
container.add(table, BorderLayout.CENTER);

Seleccionar y Cambiar la Anchura de las Columnas

Por defecto, todas las columnas de una tabla empiezan con la misma anchura, y las columnas rellenan automáticamente la anchura completa de la tabla. Cuando la tabla se ensancha o se estrecha (lo que podría suceder cuando el usuario redimensiona la ventana que la contiene) la anchura de todas las columnas cambia apropiadamente.

Cuando el usuario redimensiona una columna, arrastando su borde derecho, todas las demás deben cambiar su tamaño. Por defecto, el tamaño de la tabla permace igual, y todas las columnas a la derecha del punto de arrastre acomodan su tamaño al espacio añadido o eliminado desde la columna situada a la izquierda del punto de arrastre.

Las siguientes figuras ilustran el comportamiento de redimensionado por defecto.


Inicialmente, las columnas tienen la misma anchura.


Cuando el usuario redimensiona una columna, alguna de las otras columnas debe ajustar su tamaño para que el tamaño de la tabla permanezca igual.


Cuando se redimensiona toda la tabla, todas las columnas se redimensionan.

Para personalizar la anchura inicial de las columnas, podemos llamar al método setPreferredWidth con cada una de las columnas de la tabla. Este selecciona tanto las anchuras preferidas de las clumnas como sus anchuras relativas aproximadamente. Por ejemplo, si añadimos el siguiente código a SimpleTableDemo haremos que la tercera columna se mayor que las otras:

TableColumn column = null;
for (int i = 0; i < 5; i++) {
    column = table.getColumnModel().getColumn(i);
    if (i == 2) {
        column.setPreferredWidth(100); //sport column is bigger
    } else {
        column.setPreferredWidth(50);
    }
}

Nota: el método setPreferredWidth fue primero introducido en Swing 1.1 beta 2. Para versiones anterior debemos utilizar setMinWidth, asegurándonos de llamarlo sobre cada columna, (de otro modo, las columnas que no lo hagamos serán muy finas).
Como muestra el código anterior, cada columna de la tabla está representada por un objeto TableColumn. Junto a setPreferredWidth, TableColumn también suministra métodos para obtener la anchura mínima, máxima y actual de una columna. Para un ejemplo de selección de anchura de celdas basada en la cantidad de espacio necesario para mostrar el contenido de las celdas, puedes ver el método initColumnSizes en TableRenderDemo.java, que se explica en Mayor personalización del Visionado y del manejo de Eventos.

Cuando el usuario redimensiona explícitamente las columnas, los nuevos tamaños no sólo se convierten en la anchura actual de la columna, sino que también se convierten en la anchura preferida. Si embargo, cuando las columnas se redimensionan como resultado de un cambio de anchura de la tabla, las anchuras preferidas de las columnas no cambian.

Podemos cambiar el comportamiento de redimensionado de una tabla llamando al método setAutoResizeMode. El argumento de este método debe ser uno de estos valores (definidos como constantes en JTable):

AUTO_RESIZE_SUBSEQUENT_COLUMNS
Por defecto. Además de redimensionar la columna a la izquierda del punto de arrastre, ajusta los tamaños de todas las columnas situadas a la derecha del punto de arrastre.
AUTO_RESIZE_NEXT_COLUMN
Ajusta sólo las columnas inmediatas a la izquierda y derecha del punto de arrastre.
AUTO_RESIZE_OFF
Ajusta el tamaño de la tabla.

Nota: Antes de la versión Swing 1.1 Beta, el modo por defecto era AUTO_RESIZE_ALL_COLUMNS. Sin embargo, este modo no es intuitivo por eso se cambió el modo por defecto a: AUTO_RESIZE_SUBSEQUENT_COLUMNS.

Detectar Selecciones de Usuario

El siguiente fragmento de código muestra cómo detectar cuando el usuario selecciona una fila de la tabla. Por defecto, una tabla permite que el usuario selecciona varias filas -- no columnas o celdas individuales -- y las filas seleccionadas deben ser contiguas. Utilizando el método setSelectionMode, el siguiente código especifica que sólo se puede seleccionar una fila cada vez. Puedes encontrar el programa completo en SimpleTableSelectionDemo.java.
table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
...
ListSelectionModel rowSM = table.getSelectionModel();
rowSM.addListSelectionListener(new ListSelectionListener() {
    public void valueChanged(ListSelectionEvent e) {
        ListSelectionModel lsm = (ListSelectionModel)e.getSource();
        if (lsm.isSelectionEmpty()) {
            ...//no rows are selected
        } else {
            int selectedRow = lsm.getMinSelectionIndex();
            ...//selectedRow is selected
        }
    }
});
SimpleTableSelectionDemo también tiene código (no incluido en el fragmento anterior) que cambia la orientación de la selección de la tabla. Cambiando un par de valores booleanos, podemos permitir que la tabla acepte selecciones de columnas o de celdas individuales en vez de seleccionar filas.

Para más información y ejemplos de implementación de selecciones, puedes ver Escribir un Oyente de List Selection.

Crear un Modelo de tabla

Como se ha visto, toda tabla obtiene sus datos desde un objeto que implemente el interface TableModel.

El constructor de JTable usado por SimpleTableDemo crea su modelo de tabla con este código:

new AbstractTableModel() {
    public String getColumnName(int col) { 
        return columnNames[col].toString(); 
    }
    public int getRowCount() { return rowData.length; }
    public int getColumnCount() { return columnNames.length; }
    public Object getValueAt(int row, int col) { 
        return rowData[row][col]; 
    }
    public boolean isCellEditable(int row, int col) { return true; }
    public void setValueAt(Object value, int row, int col) {
        rowData[row][col] = value;
	fireTableCellUpdated(row, col);
    }
}
Cómo muestra el código anterior, implementar un modelo de tabla puede ser sencillo. Generalmente, se implementa en una subclase de la clase AbstractTableModel.

Nuestro modelo podría contener sus datos en un array, un vector o un hashtable, o podría obtener los datos desde una fuente externa como una base de datos. Incluso podría generar los datos en tiempo de ejecución.

Aquí está de nuevo una imagen de una tabla implementada por TableDemo, que tiene un modelo de tabla personalizado:

Esta tabla es diferente de la de SimpleTableDemo en estas cosas:

  • El modelo de tabla de SimpleTableDemo, habiendo sido creado automáticamente por JTable, no es suficentemente inteligente para saber que la columna '# of Years' contiene números (que generalmente deben alinearse a la derecha). Tampoco sabe que la columna 'Vegetarian' contiene un valor booleano, que pueden ser represantados por checkboxes. El modelo de datos personalizado de TableDemo, aunque sencillo, puede determinar los tipos de datos, ayudando a JTable a mostrar los datos en el mejor formato.
  • En SimpleTableDemo, todas las celdas son editables. En TableDemo, hemos implementado el modelo de tabla personalizado, para que no permita editar la columna 'name' pero, sin embargo, si se pueden editar las otras columnas.

Aquí está el código de TableDemo.java que es diferente del código de SimpleTableDemo.java. Las partes en negrita indican el código que hace que este modelo de tabla sea diferente del modelo de tabla definido automáticamente por SimpleTableDemo.

public TableDemo() {
    ...
    MyTableModel myModel = new MyTableModel();
    JTable table = new JTable(myModel);
    table.setPreferredScrollableViewportSize(new Dimension(500, 70));

    //Create the scroll pane and add the table to it. 
    JScrollPane scrollPane = new JScrollPane(table);

    //Add the scroll pane to this window.
    setContentPane(scrollPane);
    ...
}

class MyTableModel extends AbstractTableModel {
    final String[] columnNames = ...//same as before...
    final Object[][] data = ...//same as before...

    public int getColumnCount() {
        return columnNames.length;
    }
    
    public int getRowCount() {
        return data.length;
    }

    public String getColumnName(int col) {
        return columnNames[col];
    }

    public Object getValueAt(int row, int col) {
        return data[row][col];
    }

    public Class getColumnClass(int c) {
        return getValueAt(0, c).getClass();
    }

    /*
     * Don't need to implement this method unless your table's
     * editable.
     */
    public boolean isCellEditable(int row, int col) {
        //Note that the data/cell address is constant,
        //no matter where the cell appears onscreen.
        if (col < 2) { 
            return false;
        } else {
            return true;
        }
    }

    /*
     * Don't need to implement this method unless your table's
     * data can change.
     */
    public void setValueAt(Object value, int row, int col) {
        ...//debugging code not shown...
        ...//ugly class cast code for Integers not shown...
        data[row][col] = value;
        ...//debugging code not shown...
    }
    ...

Detectar Cambios de Datos

Una tabla y su modelo detectan automáticamente si el usuario edita los datos de la tabla. Sin embargo, si los datos cambian por otra razón, debemos realizar unos pasos especiales para indicar a la tabla y a su modelo el cambio de los datos. Si no implementamos un modelo de tabla, como en SimpleTableDemo, también debemos realizar los pasos especiadl para ver cuando el usuario edita los datos de la tabla.

Para disparar un evento table-model, el modelo llama al método fireTableRowsInserted, que está definido por la clase AbstractTableModel. Otros métodos fireXxxx que define la clase AbstractTableModel para ayudarnos a lanzar eventos table-model son fireTableCellUpdated, fireTableChanged, fireTableDataChanged, fireTableRowsDeleted, fireTableRowsInserted, fireTableRowsUpdated, y fireTableStructureChanged.

Si tenemos una clase como SimpleTableDemo que es una tabla o un modelo de tabla, pero necesita reaccionar a los cambios en un modelo de tabla, necesitamos hacer algo especial para detectar cuando el usuario edita los datos de la tabla. Específicamente, necesitamos registrar un oyente de table-model. Añadiendo el código en negrita del siguiente fragmento hace que SimpleTableDemo reaccione ante los cambios de datos.

public class SimpleTableDemo ... implements TableModelListener {
    ...
    public SimpleTableDemo() {
        ...
	model = table.getModel();
        model.addTableModelListener(this);
        ...
    }

    public void tableChanged(TableModelEvent e) {
        ...
        int row = e.getFirstRow();
        int column = e.getColumn();
        String columnName = model.getColumnName(column);
        Object data = model.getValueAt(row, column);

        ...// Do something with the data...
    }
    ...
}

Conceptos: Editores de Celdas e Intérpretes

Antes de seguir adelante, necesitamos comprender como las tablas dibujan sus celdas. Podríamos esperar que cada celda de la tabla fuera un componente. Sin embargo, por razones de rendimiento, las tablas Swing no están implementadas de esta forma.

En su lugar, se utiliza un sencillo intérprete de celdas para dibujar todas las celdas de una columna. Frecuentemente este intérprete es compartido entre todas las columnas que contienen el mismo tipo de datos. Podemos pensar en el intérprete como un sello de caucho que las tablas utilizan para estampar los datos formateados apropiadamente en cada celda. Cuando el usuario empieza a editar los datos de una celta, un editor de celdas toma el control sobre la edición de la celda.

Por ejemplo, todas las celdas de la columna '# of Years' de TableDemo contienen datos numéricos -- específicamente un objeto Integer. Por defecto, el intérprete para una columna numérica utiliza un sencillo ejemplar de JLabel para dibujar los números apropiados, alineados a la derecha, en las celdas de la columna. Si el usuario empieza a editar una de las celdas, el editor por defecto utiliza un JTextField alineado a la derecha para controlar la edición.

Para elegir el intérprete que muestra la celdas de una columna, una tabla primero determina si hemos especificado un ínterprete para esa columna particular. Si no lo hacemos, la tabla llama al método getColumnClass del modelo de tabla, que obtiene el tipo de datos de las celdas de la columna. Luego, la tabla compara el tipo de datos de la columna con una lista de tipos de datos para los que hay registrados unos intérpretes. Esta lista es inicializada por la tabla, pero podemos añadirle elementos o cambiarla. Actualmente, las tablas ponen los siguientes tipos en la lista:

  • Boolean -- interpretado con un checkbox.
  • Number -- interpretado con una etiqueta alineada a la derecha.
  • ImageIcon -- interpretado por una etiqueta centrada.
  • Object -- interpretado por una etiqueta que muestra el valor String del objeto.

Las tablas eligen sus editores de celdas utilizando un algoritmo similar.

Recuerda que si dejamos que una tabla cree su propio modelo, utiliza Object como el tipo de cada columna. TableDemo.java muestra como especificar los tipos de datos de las columnas con más precisión.

Las siguientes secciones explican cómo pesonalizar el visionado y edición de celdas especificando intérpretes y editores de celdas para columnas o tipos de datos.

Validar el Texto Introducido por el Usuario

En los ejemplos de tablas que hemos visto hasta ahora, el usuario podía introducir cualquier texto en la columna '# of Years'. SimpleTableDemo no comprueba el valor de los datos. El ejemplo TableDemo está ligeramente mejorado en que cuando el usuario hace la edición, el código comprueba si la entrada puede ser pasada a un entero. Sin embargo, TableDemo debe usar un código más ambicioso para convertir el string devuelto por el editor de celdas por defecto en un Integer. Si no hace la conversión, el tipo real de los datos podría cambiar de Integer a String.

Lo que realmente queremos hacer es comprobar la entrada del usuario mientras la está tecleando, y hacer que el editor de celdas devuelva un Integer en lugar de un string. Podemos conseguir una o estas dos tareas utilizando un campo de texto personalizado para controlar la edición de la celda.

Un campo de texto personalizado, puede chequear la entrada del usuario mientras la está tecleando o después de que haya indicado el final (pulsado la tecla return, por ejemplo). Llamamos a estos tipos de validación, chequeo de pulsación y chequeo de acción, respectivamente.

El siguiente código, tomado de TableEditDemo.java, configura un campo de texto con chequeo de pulsación. Las líneas en negrita hacen que el campo de texto sea el editor para todas las celdas que contengan datos del tipo Integer.

final WholeNumberField integerField = new WholeNumberField(0, 5);
integerField.setHorizontalAlignment(WholeNumberField.LEFT);

DefaultCellEditor integerEditor = 
    new DefaultCellEditor(integerField) {
        //Override DefaultCellEditor's getCellEditorValue method
        //to return an Integer, not a String:
        public Object getCellEditorValue() {
            return new Integer(integerField.getValue());
        }
    };
table.setDefaultEditor(Integer.class, integerEditor);
La clase WholeNumberField utilizada arriba es una subclase de JTextField personalizada que permite al usuario introducir sólo dos dígitos. El método getValue devuelve el valor int del contenido de WholeNumberField Puedes ver Cómo usar TextField para más información sobre WholeNumberField. Esta página también proporciona un textfield con validación de propósito general, llamado DecimalField, que podemos personalizar para validar cualquier formato de número que le especifiquemos.

Usar un ComboBox como un Editor

Aquí hay un ejemplo de configuración de un ComboBox como editor de celda. Las líneas en negrita del código configuran el ComboBox para editar una columna, en vez de para un tipo de dato específico.
TableColumn sportColumn = table.getColumnModel().getColumn(2);
...
JComboBox comboBox = new JComboBox();
comboBox.addItem("Snowboarding");
comboBox.addItem("Rowing");
comboBox.addItem("Chasing toddlers");
comboBox.addItem("Speed reading");
comboBox.addItem("Teaching high school");
comboBox.addItem("None");
sportColumn.setCellEditor(new DefaultCellEditor(comboBox));
Aquí hay una imagen el editor ComboBox en uso:

El editor ComboBox está implementado en TableRenderDemo.java, que se explica en más detalle en Mayor Personalización de Visionado y de Manejo de Eventos.

Especificar otros Editores

Como se vió en la sección anterior, podemos seleccionar un editor para una columna utilizando el método setCellEditor de Tablecolum, o para un tipo de datos específico usando el método setDefaultEditor de JTable. Para ambos métodos, podemos especificar un argumento que implemente el interface TableCellEditor. Afortunadamente, la clase DefaultCellEditor implementa este interface y proporciona constructores para permitir especificar y editar componentes que sean JTextField, JCheckBox, o JComboBox. Normalmente no tenemos que especificar explícitamente un checkbox como un editor, ya que las columnas con datos Boolean automáticamente usan un editor y un intérprete CheckBox.

¿Y si queremos especificar un editor que no sea un textfield, checkbox, o combobox? Bien, como DefaultCellEditor no soporta otros tipos de componentes, debemos trabajar un poco más. Necesitamos crear una subclase del editor de componente deseado, y esta subclase debe implementar el interface TableCellEditor. Luego configuramos el componente como un editor para un tipo de dato o una columna, utilizando los métodos setDefaultEditor o setCellEditor, respectivamente.

Aquí hay una imagend e una tabla con un diálogo que sirve, indirectamente, como editor de celda. Cuando el usuario empieza a editar una celda en el columna, 'Favorite Color', un botón, (el verdadero editor de celda) aparece y trae el diálogo, con el que el usuario puede elegir un color diferente.


Podemos encontrar el código fuente en TableDialogEditDemo.java. el rejemplo también necesita WholeNumberField.java.

Mayor Personalización de Visionado y de Manejo de Eventos

Ya hemos visto como especificar editores de celdas. También podemos especificar intérpretes para celdas y para cabeceras de columnas. Los intérpretes personalizados permiten mostrar datos de formas personalizadas y especificar texto de ayuda (tooltips) para que lo muestre la tabla.

Aunque los intérpretes determinan cómo se muestran las celdas o cabeceras de columnas, no pueden manejar eventos. Para detectar los eventos que tienen lugar dentro de una tabla, deberíamos elegir la técnica apropiada para ordenar el evento en el que estamos interesados. Para una celda que esta siendo editada, el editor debería procesar eventos. Para detectar selecciones y deselecciones de fila/columna/celda, se utiliza un oyente de selection como se describe en Detectar Selecciones del Usuario. Para editar pulsaciones del ratón en una cabecera de columna, podemos registrar un oyente de mouse en la cabecera de la tabla. (Puedes ver un ejemplo en TableSorter.java). Para detectar otros eventos, podemos registrar el oyente apropiado sobre el objeto JTable.

Crear un intérprete personalizado puede ser tan sencillo como crear una subclse de un componente existente y luego implementar el único método del interface TableCellRenderer. En la figura anterior, el intérprete de color utilizado para la celda "Favorite Color" es una subclase de JLabel. Podemos encontrar el código del intérprete en la clase interna de TableDialogEditDemo.java. Aquí está el código que registra el ejemplar de ColorRenderer como el intérprete por defecto para todos los datos Color:

table.setDefaultRenderer(Color.class, new ColorRenderer(true));
Podemos especificar un intérprete especifico de celda, si queremos. Para hacer esto, nocesitamos definir una subclase de JTable que sobreescriba el método getCellRenderer. Por ejemplo, el siguiente código hace que la primera celda de la primera columna de la tabla use un intérprete personalizado:
TableCellRenderer weirdRenderer = new WeirdRenderer();
table = new JTable(...) {
    public TableCellRenderer getCellRenderer(int row, int column) {
	if ((row == 0) && (column == 0)) {
	    return weirdRenderer;
	}
	// else...
	return super.getCellRenderer(row, column);
    }
};
Para añadir tool-tips a las celdas o columnas de cabecera, necesitamos obtener y crear el intérprete de celda o cabecera y luego utilizar el método setToolTipText del componente del intérprete. TableRenderDemo.java añade tool-tips tanto a las celdas como a la cabecera de la columna 'Sport' con el siguiente código:
//Set up tool tips for the sport cells.
DefaultTableCellRenderer renderer =
        new DefaultTableCellRenderer();
renderer.setToolTipText("Click for combo box");
sportColumn.setCellRenderer(renderer);

//Set up tool tip for the sport column header.
TableCellRenderer headerRenderer = sportColumn.getHeaderRenderer();
if (headerRenderer instanceof DefaultTableCellRenderer) {
    ((DefaultTableCellRenderer)headerRenderer).setToolTipText(
             "Click the sport to see a list of choices");
} 
Una interesante característica de TableRenderDemo es cómo determina el tamaño de sus columnas. Por cada columna, TableRenderDemo obtiene los componentes utilizados para intérpretar las celdas y la cabecera de columna. Luego pregunta a los componentes cuánto espacio necesitan. Finalmente, utiliza esa información para seleccionar al anchura de la columna.
TableColumn column = null;
Component comp = null;
int headerWidth = 0;
int cellWidth = 0;
Object[] longValues = model.longValues;

for (int i = 0; i < 5; i++) {
    column = table.getColumnModel().getColumn(i);

    comp = column.getHeaderRenderer().
                     getTableCellRendererComponent(
                         null, column.getHeaderValue(), 
                         false, false, 0, 0);
    headerWidth = comp.getPreferredSize().width;

    comp = table.getDefaultRenderer(model.getColumnClass(i)).
                     getTableCellRendererComponent(
                         table, longValues[i],
                         false, false, 0, i);
    cellWidth = comp.getPreferredSize().width;
    ...//debugging code not shown...
    column.setPreferredWidth(Math.max(headerWidth, cellWidth));
}

...//In the model:
public final Object[] longValues = {"Angela", "Andrews", 
                                    "Teaching high school",
                                    new Integer(20), Boolean.TRUE};

Ordenación y otras Manipulaciones de Datos

Una forma de realizar una manipulación de datos como la ordenación es utilizar uno o más modelos de tablas especializados (manipuladores de datos), además del modelo que proporciona los datos (el modelo de datos). Los manipuladores de datos deberían situarse entre la tabla y el modelo de datos, como muestra la siguiente figura:

Podemos utilizar las clases TableMap y TableSorter cuando implementemos nuestro manipulador de datos. TableMap implementa TableModel y sirve como una superclase para manipuladores de datos. TableSorter es una subclase de TableMap que ordena los datos proporcionados por otro modelo de tabla. También podemos cambiar estas clases, utilizándolas como un base para escribir nuestros propios manipuladores, o utilizarlas tal como son para proporcionar la funcionalidad de ordenación.

Para implementar la ordenación con TableSort, necesitamos sólo tres líneas de código. El siguiente listado muestra las diferencias entre TableDemo y su primo ordenado, TableSorterDemo.

TableSorter sorter = new TableSorter(myModel); //ADDED THIS
//JTable table = new JTable(myModel);          //OLD
JTable table = new JTable(sorter);             //NEW
sorter.addMouseListenerToHeaderInTable(table); //ADDED THIS
El método addMouseListenerToHeaderInTable añade un oyente de mouse que detecta pulsaciones sobre las cabeceras de columna. Cuando el oyente detecta un click, ordena las filas básandose en la columna pulsada. Por ejemplo, cuando pulsamos sobre "Last Name", las filas son reordenadas para que la fila con "Andrews" se convierta la primera. Cuando volvemos a pulsar de nuevo la cabecera de columna, las filas se ordenan en orden inverso.

El API Table

Las tablas de esta sección sólo cubren una parte de este API. Para más información puedes ver el API de JTable y para distintas clases y paquetes table package. El API para usar tablas se divide en estas categorías:

Clases e Interfaces Relacionados con las Tablas
Clase/Interface Propósito
JTable El componente que presenta la tabla al usuario.
JTableHeader El componente que presenta los nombres de columnas al usuario. Por defecto, la tabla genera este componente automáticamente.
TableModel, AbstractTableModel Respectivamente, el interface que un modelo de tabla debe implementar y la superclase usual para implementaciones de modelos de tabla.
TableCellRenderer, DefaultTableCellRenderer Respectivamente, el interface que un intérprete de celda debe implementar y la implementación más usual.
TableCellEditor, DefaultCellEditor Respectivamente, el interface que debe implementar un editor de celda, y la implementación más usual.
TableColumnModel, DefaultTableColumnModel Respectivamente, el interface que debe implementar un modelo de columna, y su implementación usual. Normalmente no tenemos que tratar directamente con el modelo de columna a menos que necesitemos obtener el modelo de selección de columna u obtener un índice de columna o un objeto.
TableColumn Controla todos los atributos de una columna de la tabla, incluyendo, redimensionado, anchuras mínima, máxima, preferida y actual; y editor/intérprete opcional específico de la columna.
DefaultTableModel Un modelo de tabla basado en Vector utilizado por JTable cuando construimos una tabla sin especificar modelo de datos ni datos.

Crear y Configurar una Tabla
Método/Constructor de JTable Propósito
JTable(TableModel)
JTable(TableModel, TableColumnModel)
JTable(TableModel, TableColumnModel, ListSelectionModel)
JTable()
JTable(int, int)
JTable(Object[][], Object[])
JTable(Vector, Vector)
Crea una tabla. El argumento opcional TableModel especifica el modelo que proporciona los datos de la tabla. Los argumentos opcionales TableColumnModel y ListSelectionModel permiten especificar el modelo de columna y el modelo de selección. Como una alternativa para especificar un modelo de tabla, podemos suministrar datos y nombres de columnas utilizando un array o un Vector. Otra opción es no especificar datos, opcionalmente especificar el número de filas y columnas (ambos enteros) que hayan en la tabla.
void setPreferredScrollableViewportSize(Dimension) Selecciona el tamaño de la parte visible de la tabla cuando se está viendo dentro de un ScrollPane.
JTableHeader getTableHeader(Dimension) Obtiene el componente que muestra los nombres de columnas.

Manipular Columnas
Método Propósito
TableColumnModel getColumnModel()
(en JTable)
Obtiene el modelo de columna de la tabla.
TableColumn getColumn(int)
Enumeration getColumns()

(en TableColumnModel)
Obtiene uno o todos los objetos TableColumn de la tabla.
void setMinWidth(int)
void setPreferredWidth(int)
void setMaxWidth(int)
(en TableColumn)
Seleciona la anchura mínima, preferida o máxima de la columna.
int getMinWidth(int)
int getPreferredWidth()
int getMaxWidth()
int getWidth()
(en TableColumn)
Obtiene la anchura mínima, preferida, máxima o actual de la columna.

Usar Editores e Intérpretes
Métodos Propósito
void setDefaultRenderer(Class, TableCellRenderer)
void setDefaultEditor(Class, TableCellEditor)
(en JTable)
Selecciona el intérprete o editor usado, por defecto, para todas las celdas en todas las columnas que devuelvan el tipo de objetos especificado.
void setCellRenderer(TableCellRenderer)
void setCellEditor(TableCellEditor)
(en TableColumn)
Selecciona el intérprete o editor usado para todas las celdas de esta columna.
TableCellRenderer getHeaderRenderer()
(en TableColumn)
Obtiene el intérprete de cabecera para esta columna, que podemos personalizar.

Implementar Selección
Método de JTable Propósito
void setSelectionMode(int) Selecciona los intervalos de selección permitidos en la tabla. Los valores válidos están definidos en ListSelectionModel como SINGLE_SELECTION, SINGLE_INTERVAL_SELECTION, y MULTIPLE_INTERVAL_SELECTION (por defecto).
void setSelectionModel(ListSelectionModel)
ListSelectionModel getSelectionModel()
Selecciona u obtiene el modelo usado para controlar las selecciones de filas.
void setRowSelectionAllowed(boolean)
void setColumnSelectionAllowed(boolean)
void setCellSelectionEnabled(boolean)
Selecciona la orientación de selección de la tabla. El argumento booleano especifica si está permitido el tipo de selección particular. Por defecto, las selección de filas está permitida, y la de columna y celda no.

Ejemplos que usan Tablas

Esta tabla lista ejemplos que usan JTable y dónde poder encontrarlos.

Ejemplos Dónde se Describe Notas
SimpleTableDemo.java Esta página Una tabla básica con un modelo no personalizado. No incluye código para: especificar anchuras de columna o detectar edición del usuario.
SimpleTable-
SelectionDemo.java
Esta página Añade selección sencilla y detección de selección a SimpleTableDemo. Modificando las constantes ALLOW_COLUMN_SELECTION y ALLOW_ROW_SELECTION del programa, podemos experimentar con distintas alternativas para permitir que sólo las filas sean seleccionadas.
TableDemo.java Esta página Una tabla básica con modelo personalizado.
TableEditDemo.java, WholeNumberField.java Esta página Modifica TableDemo para usar un editor personalizado (una variante de textfiled) para todos los datos Integer.
TableRenderDemo.java Esta página Modifica TableDemo para usar un editor personalizado (un combobox) para todos los datos de la columna 'Sport'. También redimensiona inteligentemente los tamaños de las columnas y utiliza un intérprete para mostrar tool-tips para la celdas y la cabecera de la columna 'Sport'.
TableDialogEditDemo.java, WholeNumberField.java Esta página Modifica TableEditDemo para tener un intérprete y un editor de cela que muestre colores y nos permita elegir uno, utilizando un diálogo selector de colores.
TableSorterDemo.java, TableSorter.java, TableMap.java Esta página Ordena los datos interponiendo un manipulador de datos entre el modelo de datos y la tabla. Detecta las puslaciones del usuario sobre cabeceras de columnas.
ListSelectionDemo.java Escribir un Oyente de List Selection Muestra cómo utilizar todos los modos de selección, usando un oyente de list selection que se comparte entre una tabla y una lista.


Ozito