Ejemplo de uso de TableModelListener

De ChuWiki

Cuando cambia el valor de alguna de las celdas de un JTable, este avisa del cambio a quien esté interesado. Esto puede ser útil para realizar ciertas tareas, como guardar esos valores cambiados en base de datos o, como en el ejemplo que vemos a continuación, actualizar el valor de otras celdas.

En el siguiente ejemplo se crea una tabla con tres filas y tres columnas con números. Se pretende que la última columna muestre la suma de las dos anteriores y que la última fila muestre la suma de las dos filas superiores. La última celda, en la posición 3,3, tendrá la suma de las dos celdas superiores, que coincidirá con las dos celdas a su izquierda. Más o menos lo de la siguiente figura

Las celdas amarillas no son editables y muestran la suma bien de la fila, bien de la columna.

El código para este ejemplo es el siguiente, explicado en los propios comentarios.


package com.chuidiang.ejemplos;

import java.awt.Color;
import java.awt.Component;

import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.event.TableModelEvent;
import javax.swing.event.TableModelListener;
import javax.swing.table.DefaultTableCellRenderer;
import javax.swing.table.DefaultTableModel;
import javax.swing.table.TableModel;

/**
 * Ejemplo de uso del TableModelListener.
 * Una tabla de 3 filas y 3 columnas con numeros, de forma que la ultima columna es la suma
 * de las dos columnas anteriores y la ultima fila es la suma de las dos filas anteriores.
 * Solo son editables las celdas que no son de la ultima fila ni de la ultima columna.
 * Se usa TableModelListener para actualizar las sumas cuando el usuario modifica el valor de
 * alguna de las celdas.
 * 
 * @author Chuidiang
 */
public class EjemploTableModelListener {

    public static void main(String[] args) {
        new EjemploTableModelListener();
    }

    public EjemploTableModelListener() {
        JTable tabla = creaVentanaConTabla();
        anadeListenerAlModelo(tabla);
    }

    /**
     * Se añade el listener al modelo y se llama a actualizaSumas(), que es el método 
     * encargado de actualizar las sumas de las celdas no editables.
     */
    private void anadeListenerAlModelo(JTable tabla) {
        tabla.getModel().addTableModelListener(new TableModelListener() {

            @Override
            public void tableChanged(TableModelEvent evento) {
                actualizaSumas(evento);
            }
        });
    }

    /**
     * Cuando cambie el valor de alguna celda, el JTable avisará a los listener y
     * nuestro listener llamará a este método.
     */
    protected void actualizaSumas(TableModelEvent evento) {
        // Solo se trata el evento UPDATE, correspondiente al cambio de valor
        // de una celda.
        if (evento.getType() == TableModelEvent.UPDATE) {

            // Se obtiene el modelo de la tabla y la fila/columna que han cambiado.
            TableModel modelo = ((TableModel) (evento.getSource()));
            int fila = evento.getFirstRow();
            int columna = evento.getColumn();

            // Los cambios en la ultima fila y columna se ignoran.
            // Este return es necesario porque cuando nuestro codigo modifique
            // los valores de las sumas en esta fila y columna, saltara nuevamente
            // el evento, metiendonos en un bucle recursivo de llamadas a este
            // metodo.
            if (fila == 2 || columna == 2) {
                return;
            }

            try {
                // Se actualiza la suma en la ultima columna de la fila que ha
                // cambiado.
                double valorPrimeraColumna = ((Number) modelo.getValueAt(fila,
                        0)).doubleValue();
                double valorSegundaColumna = ((Number) modelo.getValueAt(fila,
                        1)).doubleValue();
                modelo.setValueAt(valorPrimeraColumna + valorSegundaColumna,
                        fila, 2);

                // Se actualiza la suma en la ultima fila de la columna que ha
                // cambiado.
                double valorPrimeraFila = ((Number) modelo.getValueAt(0,
                        columna)).doubleValue();
                double valorSegundaFila = ((Number) modelo.getValueAt(1,
                        columna)).doubleValue();
                modelo.setValueAt(valorPrimeraFila + valorSegundaFila, 2,
                        columna);

                // Se actualiza la ultima fila y columna, que tiene la suma total
                // de todas las celdas.
                double valorPrimeraFilaUltimaColumna = ((Number) modelo
                        .getValueAt(0, 2)).doubleValue();
                double valorSegundaFilaUltimaColumna = ((Number) modelo
                        .getValueAt(1, 2)).doubleValue();
                modelo.setValueAt(valorPrimeraFilaUltimaColumna
                        + valorSegundaFilaUltimaColumna, 2, 2);
                
            } catch (NullPointerException e) {
                // La celda que ha cambiado esta vacia.
            }
        }

    }

    private JTable creaVentanaConTabla() {
        JFrame ventana = new JFrame();
        JTable tabla = new JTable(new MiModelo());
        tabla.setDefaultRenderer(Double.class, new MiRender());
        JScrollPane scroll = new JScrollPane(tabla);
        ventana.getContentPane().add(scroll);
        ventana.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        ventana.pack();
        ventana.setVisible(true);
        return tabla;
    }

    /**
     * Nuestro modelo de datos.
     * @author Chuidiang
     */
    class MiModelo extends DefaultTableModel {
        
        /**
         * Se carga con tres filas y tres columnas con numeros.
         */
        public MiModelo() {
            super(new Object[][] { { 1, 1, 2 }, { 1, 1, 2 }, { 2, 2, 4 } },
                    new String[] { "A", "B", "C" });
        }

        /**
         * El contenido de las celdas sera Double.
         */
        @Override
        public Class<?> getColumnClass(int arg0) {
            return Double.class;
        }

        /**
         * Se hacen no editables la ultima fila y columna.
         * Es decir, es editable si no es la fila 2 ni la columna 2.
         */
        @Override
        public boolean isCellEditable(int fila, int columna) {
            return fila < 2 && columna < 2;
        }
    }

    /**
     * Un Render para mostrar de un color distinto las celdas no editables.
     * 
     * @author Chuidiang
     */
    class MiRender extends DefaultTableCellRenderer {
        @Override
        public Component getTableCellRendererComponent(JTable table,
                Object value, boolean isSelected, boolean hasFocus, int row,
                int column) {
            
            // Se llama al metodo de la clase padre para que ponga bien los
            // colores de celda con/sin foco, celda de/seleccionada, dibuje el 
            // valor de la celda, etc.
            super.getTableCellRendererComponent(table, value, isSelected,
                    hasFocus, row, column);
            
            // Y nosotros solo cambiamos el color de fondo.
            if (row == 2 || column == 2) {
                // Si es ultima fila o columna, es decir, celda no editable,
                // color amarillo.
                setBackground(Color.YELLOW);
            } else {
                // Si es editable, color blanco.
                setBackground(Color.WHITE);
            }
            
            // DefaultTableCellRenderer hereda de JLabel y es este el JLabel que
            // devolvemos.
            return this;
        }
    }
}