Martin Melka
Martin Melka

Reputation: 7789

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.

Upvotes: 3

Views: 5918

Answers (4)

MadProgrammer
MadProgrammer

Reputation: 347194

Personally, I would avoid any solution that required you to extend a JTable. The main reason is it makes your code less portable and tightly couples your renderer to a particular implementation of JTable. It might suit your needs, but, personally, I'm always trying to see a bigger picture/potential/reuse.

Remember that a cell renderer is bound to a table column. It's not meant to be able to provide custom rendering on a cell by cell basis, per se. What you can do though, is provided rendering logic for each cell based a on series of conditions within the renderer.

I would probably put together some kind condition/format engine, which you could use to pass the cell value and coordinates to, which would then make decisions based on the needs of the particular implementation of the engine.

class ColorField extends DefaultTableCellRenderer {

    (...))

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

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

        // CellFormat is an object you create to hold the various aspects
        // of the cell format
        // You could also pass in the selected and focused flags if you
        // needed them...
        // getFormatEngine is a method you would write to provide
        // to gain access to the format engine
        CellFormat format = getFormatEngine().getFormatFor(value, row, column);
        setBackground(format.getBackground());
        setForeground(format.getForeground());
        setFont(format.getFont());
        // Any other properties you may wish to effect...

        return this;
    }
}

The FormatEngine would be the bases for making decisions about how best to format the given cell. It should be capable of providing a "default" format as well

This means you could devise a "default" renderer for particular cell types, but have the ability to supply custom rendering based on your needs without the need for multiple if statements or extending the base render

This is also the bases for a conditional formatting engine, where you could apply a series of conditions against a given value and return an appropriate format based on the results

Upvotes: 3

Mateva
Mateva

Reputation: 812

I believe the correct way to implement this is via ColorHighlighter. This is said in the documentation. This is a related issue in SO.

Note: if the table allows sorting it is essential to implement a corresponding ComponentAdapter, with its dapter.convertRowIndexToModel

Upvotes: 0

Lee Meador
Lee Meador

Reputation: 12985

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.

Upvotes: 3

fjf2002
fjf2002

Reputation: 882

You need some color information for each cell in a column. This information could reside in the values:

class CellValue {
    Color getBackgroundColor() { ... }
    String getCellContent() { ... }
}

Your getTableCellRendererComponent() method could then look like this:

CellValue cellValue = (CellValue) value;
Color bgColor = cellValue.getBackgroundColor();
String text = cellValue.getCellContent();

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

l.setBackground(bgColor);

...

Of course you have to adapt your TableModel to store CellValue instances.

Upvotes: 0

Related Questions