JTable: Poner una cabecera lateral

De ChuWiki

Cuando metemos un JTable en un JScrollPane, aparece una cabecera de la tabla en la parte superior. A veces, puede interesarnos tener también una lateral. Java no permite hacer esto directamente, así que tendremos que hacer un poco de código.

Básicamente, tenemos que hacer que la primera columna del JTable:

  • Sea no editable.
  • No se pueda seleccionar.
  • Tenga el mismo aspecto que el JTableHeader (la cabecera superior).


No editable[editar]

Para que sea no editable, en nuestro TableModel debemos poner en el método isCellEditable() que devuelva false cuando se trata de la primera columna (columna 0). El siguiente trozo de código puede ser un ejemplo de cómo hacer esto

// Un modelo de datos que hace la primera columna (la de
// la cabecera lateral) no editable.
DefaultTableModel tm = new DefaultTableModel(10, 5) {
	@Override
	public boolean isCellEditable(int row, int column) {
		if (0 == column)
			return false;
		return super.isCellEditable(row, column);
	}
};

No seleccionable[editar]

Para que las celdas de la primera columna no sean seleccionables con el ratón, vamos a sobreescribir el método changeSelection() del JTable, de forma que si se intenta seleccionar una celda de la primera columna lo evitamos y si la celda es de otra columna, permitimos la selección llamando al método changeSelection() de la clase padre.

Para evitar la selección de las celdas de la primera columna, tenemos varias opciones:

  • Hacer directamente un return. De esta forma, si hacemos click sobre una de las celdas de la primera columna, no sucederá nada, seguirá seleccionada la fila y celda que estaba seleccionada antes de hacer el click.
  • Hacer que se seleccione la celda de la segunda columna que está en la misma fila en la que se ha hecho click. De esta forma, al hacer click sobre una celda de la primera columna, se seleccionará la fila correspondiente y ganará el foco la segunda celda de la columna. Para ello, llamamos al método changeSelection() de la clase padre, pero incrementando en uno el número de la columna. Esta es la opción que se ha puesto en el código de ejemplo.
// JTable al que se le pasa el modelo recien creado y se
// sobreescribe el metodo changeSelection para que no permita
// seleccionar la primera columna.
JTable t = new JTable(tm) {
	@Override
	public void changeSelection(int rowIndex, int columnIndex,
			boolean toggle, boolean extend) {
		if (columnIndex == 0)
                        // Podemos llamar a changeSelecion() incrementando la columna en 1
                        // o bien podríamos hacer directamente un return.
			super.changeSelection(rowIndex, columnIndex + 1, toggle, extend);
		else
			super.changeSelection(rowIndex, columnIndex, toggle, extend);
			}
		};


Mismo aspecto que el JTableHeader[editar]

Para conseguir que las celdas de la primera columna tengan la misma apariencia que la de las cabecera, pedimos al JTableHeader el render que tiene y se lo ponemos a las celdas de la primera columna. El código puede ser el siguiente:

// Se pone a la primera columna el render del JTableHeader
// superior.
t.getColumnModel().getColumn(0).setCellRenderer(
		t.getTableHeader().getDefaultRenderer());


Ejemplo completo[editar]

A continuación un ejemplo completo con todo esto y al final una foto de lo que obtenemos en pantalla.

package com.chuidiang.ejemplos;

import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.WindowConstants;
import javax.swing.table.DefaultTableModel;

/**
 * Ejemplo de cómo hacer una "cabecera" lateral en un JTable. Idea original de
 * Leunamal en
 * http://foro.chuidiang.com/java-j2se/tabla-de-referencias-cruzadas-en-java/
 * 
 * Básicamente consiste en hacer que la primera columna: - No sea editable. - No
 * sea seleccionable. - Tenga el mismo render que el JTableHeader superior.
 * 
 * @author chuidiang
 */
public class CabeceraLateral {
	/**
	 * Crea y visualiza una ventana con un JTable que tiene cabecera en la parte
	 * superior y en la columna izquierda
	 * 
	 * @param args
	 */
	public static void main(String[] args) {
		// Un modelo de datos que hace la primera columna (la de
		// la cabecera lateral) no editable.
		DefaultTableModel tm = new DefaultTableModel(10, 5) {
			@Override
			public boolean isCellEditable(int row, int column) {
				if (0 == column)
					return false;
				return super.isCellEditable(row, column);
			}
		};

		// Titulos para la cabecera superior. El primero es vacio,
		// puesto que corresponde
		tm.setColumnIdentifiers(new String[] { "", "A", "B", "C", "D" });

		// Valores para la primera columna, que es la cabecera lateral.
		for (int i = 0; i < 10; i++)
			tm.setValueAt(i + 1, i, 0);

		// JTable al que se le pasa el modelo recien creado y se
		// sobreescribe el metodo changeSelection para que no permita
		// seleccionar la primera columna.
		JTable t = new JTable(tm) {
			@Override
			public void changeSelection(int rowIndex, int columnIndex,
					boolean toggle, boolean extend) {
				if (columnIndex == 0)
					super.changeSelection(rowIndex, columnIndex + 1, toggle,
							extend);
				else
					super.changeSelection(rowIndex, columnIndex, toggle,
									extend);
			}
		};

		// Se pone a la primera columna el render del JTableHeader
		// superior.
		t.getColumnModel().getColumn(0).setCellRenderer(
				t.getTableHeader().getDefaultRenderer());
		
		// Creación y visualización de la ventana completa.
		JFrame v = new JFrame("Cabecera lateral");
		JScrollPane sp = new JScrollPane(t);
		v.getContentPane().add(sp);
		v.pack();
		v.setVisible(true);
		v.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
	}
}

y esto es lo que obtenemos al ejecutar el programa. Fíjate que la fila seleccionada (azul) no cambia el color de la primera columna.