3

I have a JTable, that I want I want to be able to change the color of a single cell that is clicked on.

Here is a simplified version of my code:

public class TableFrame extends JFrame {

    public TableFrame() {
        JTable table = new JTable(8, 8);
        table.setGridColor(Color.BLACK);
        table.setDefaultRenderer(CustomCellRenderer.class, new CustomCellRenderer());
        getContentPane().add(table);
    }

    public class CustomCellRenderer 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 (hasFocus) {
                l.setBackground(Color.red);
                l.setText("Hello");
            }
            return l;
        }
    }
}

When I click a certain cell, I expect it to change color to red and add "Hello" to it. It changes the text, but for some weird reason, it changes the color of all the cells after it? And when I click an uncolored cell, it does the same, but not always in an organised way if that makes sense? Like, it won't color all the cells after it, but maybe some that are just above and leave others blank..

It's really weird and makes no sense whatsoever. What is happening??

user2397282
  • 3,798
  • 15
  • 48
  • 94
  • Do you want the changes to the cells to be permanent, or only on the **selected** cell. Remember that the cell renderer is shared between all cells (each cell does not have it's own), so you need to _reset_ it after a change. – RudolphEst Mar 01 '15 at 23:16
  • I want it to be permanent. Is that possible? – user2397282 Mar 01 '15 at 23:19
  • It is possible, but gets a little more complex. Since you have to update the model for the table (the data) as well as the view (the JTable and renderer components). It's not a trivial solution. – RudolphEst Mar 01 '15 at 23:21
  • I think that part might be okay for me. I will be storing each click as a row index and a colum index. So i guess in the cell renderer I can just check if a row/column combination already exists and color it accordingly? It's more the problem of why not just that cell is changing color, even though only that cell is 'inFocus'?? – user2397282 Mar 01 '15 at 23:23
  • Since you don't actually care about the focus, I've provided an answer based on whether the cell has been visited... you have to provide your own listener to track what has been visited. – RudolphEst Mar 01 '15 at 23:40

2 Answers2

3

Having dug around the DefaultTableCellRenderer class a bit, when you call setBackground on the JLabel component, which is backing the DefaultTableCellRenderer, it is storing the value you use...

public void setBackground(Color c) {
    super.setBackground(c);
    unselectedBackground = c;
}

When the cell is painted again, it's this value (unselectedBackground) which is been used to repaint the cell in "default" mode...

    if (isSelected) {
        //...
    } else {
        Color background = unselectedBackground != null
                                ? unselectedBackground
                                : table.getBackground();
        if (background == null || background instanceof javax.swing.plaf.UIResource) {
            Color alternateColor = DefaultLookup.getColor(this, ui, "Table.alternateRowColor");
            if (alternateColor != null && row % 2 != 0) {
                background = alternateColor;
            }
        }
        super.setForeground(unselectedForeground != null
                                ? unselectedForeground
                                : table.getForeground());
        super.setBackground(background);
    }

This means, the moment you use setBackground and pass it Color.RED, the DefaultTableCellRenderer assumes that this becomes the default color for ALL unselected cells.

The only choice you have is to reset the background color manually, for example...

public class CustomCellRenderer 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 (hasFocus) {
            l.setBackground(Color.red);
            l.setText("Hello");
        } else if (!isSelected) {
            l.setBackground(table.getBackground());
        }
        return l;
    }
}

Also, you should really be using something more like...

table.setDefaultRenderer(Object.class, new CustomCellRenderer());

to register the cell renderer, as it's the Class type returned by TableModel#getColumnClass which determines which cell renderer is used ;)

MadProgrammer
  • 343,457
  • 22
  • 230
  • 366
1

Since the OP only wants help with the rendered and not with the data... here goes (assuming there is a function called hasBeenClicked(row,column) method available to determine whether the cell has been visited yet.

public class CustomCellRenderer 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 (hasBeenClicked(row,column)) {
            l.setBackground(Color.red);
            l.setText("Hello");
        } else {
            // reset the label to white background
            l.setBackground(Color.white);
            l.setText("Hello");
        }
        return l;
    }
}

}

Also note that the registration of the renderer should be

  table.setDefaultRenderer(Object.class, new CustomCellRenderer());

Since we want all columns to have this renderer (renderers are registered against the class of the column in the model).

I tested with the below as the hasBeenClicked method.

public boolean hasBeenClicked(int row, int column){
    return (row%2==0 && column%2==0);
}

Just implement your own tracking of whether a cell has been clicked or not and you should be good to go. Remember that you should not use the renderer to track the clicks, use some kind of listener instead.

RudolphEst
  • 1,240
  • 13
  • 21