Search code examples
javaswingjtabletablecellrenderer

Apply a TableCellRenderer on a single cell


I am trying to be able to color separate cells in a JTable, but I got only so far to apply a TableCellRenderer on a whole column, which then obviously malfunctions. I have a custom JTable:

public class JColorTable extends JTable{
  (...)
  public void setCellColor(int col, int row, Color newColor) {
    getColumnModel().getColumn(col).setCellRenderer(new ColorField(col, row, newColor, background));
    repaint();
  }
}

ColorField looks like this:

class ColorField extends DefaultTableCellRenderer {

    (...))

    @Override
    public Component getTableCellRendererComponent(JTable table, Object value,
            boolean isSelected, boolean hasFocus, int row, int column) {

        JLabel l = (JLabel) super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);

        if (row == newRow && column == newCol) {
            l.setBackground(Color.red);
        } else {
            l.setBackground(defaultColor);
        }

        return l;
    }
}

This works like a charm when I have a single colored cell in a column, but when I try to color another cell in that column, the previous gets deleted (due to the condition in ColorField not applying for the previous column).

Is there a way to only apply ColorField to a single cell, rather than the whole column? If so, how? I did not find anything suitable, I'm afraid.


Solution

  • JTable has a method getCellRenderer() that you can override. It is called when a cell needs rendering and returns one based on the row and column.

    Your JTable would need to keep some record of which renderer to use for each cell (by row and column). A 2D array would do it or a Map with the key being an X,Y value.

    Add a method to set the renderer on a particular cell (by row and column) and there you go.

    class MyTable extends JTable {
    
        // all the other JTable stuff goes here too ...
    
        public TableCellRenderer getCellRenderer(int row, int column) {
            TableCellRenderer myRenderer = getCustomRenderer(row, column);
            if (myRenderer != null) {
                return myRenderer;
            }
            // else...
            return super.getCellRenderer(row, column);
        }
    
        private Map<Integer, Map<Integer, TableCellRenderer>> rendererMap = new ...;
    
        public void setCustomRenderer(int row, int column, TableCellRenderer renderer) {
            Map<Integer, TableCellRenderer> m1 = rendererMap.get(row);
            if (m1 == null) {
                m1 = new ...;
                rendererMap.put(row, m1);
            }
            m1.put(column, renderer);
        }
    
        public TableCellRenderer getCustomRenderer(int row, int column) {
            Map<Integer, TableCellRenderer> m1 = rendererMap.get(row);
            if (m1 == null) {
                return null;
            }
            return m1.get(column);
        }
    }
    

    The default version of getTableCellRenderer uses the renderer set on the column if there is one, if not it uses the renderer based on the class of the cell contents. Default cell contents is Object in many cases. It depends on the TableModel used.