How do I correctly use custom renderers to paint specific cells in a JTable?
I have a JTable component in my GUI which displays psuedocode
of an algorithm. I want to highlight the current line of execution by changing the background of a particular cell and then changing the cell beneath and so on.
Right now my code changes the backgrounds on all cells in my JTable as pictured below:
The code I am using to archive this current state is as below:
class CustomRenderer extends DefaultTableCellRenderer
{
@Override
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column)
{
JLabel d = (JLabel) super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
if((row == 0) && (column == 0))
d.setBackground(new java.awt.Color(255, 72, 72));
return d;
}
}
I then call jTable2.setDefaultRenderer(String.class, new CustomRenderer());
in my constructor.
I assume that:
- This method is being called on every String type table cell.
- That this would only change the colour of the cell at position (0,0)
How do I fix my code so that only cell (0,0) is coloured?
This is not an answer (*), just too long for a comment on both answers: both are correct in that the else block is the important thingy to ensure that the default color is used for cell that are not supposed to be highlighted. They err slightly in how to reach that, both to the same overall effect: they miss any special coloring, like f.i. due to selection, focus, editable, dnd ...
They reach that "miss" by different means with slightly different effects
setBackground(Color.WHITE);
set's a fixed color which may or may not be the default "normal" table background
setBackground(null);
set's no color which leads to showing the "normal" background color - due to internal tricksery of the DefaultTableCellRenderer isOpaque implementation :-)
The basic reason for the problem (also known the infamous color memory, TM) is an unusually bad implementation of the default renderer which leaves it essentially un-extendable:
/**
* Overrides <code>JComponent.setBackground</code> to assign
* the unselected-background color to the specified color.
*
* JW: The side-effect is documented and looks innocent enough :-)
*/
public void setBackground(Color c) {
super.setBackground(c);
unselectedBackground = c;
}
// using that side-effect when configuring the colors in getTableCellRendererComp
// is what leads to the horrendeous problems
// in the following lines of the else (not selected, that is normal background color)
Color background = unselectedBackground != null
? unselectedBackground : table.getBackground();
super.setBackground(background);
Seeing that, the way out (other than using SwingX and its flexible, clean, powerful, consistent .. :-) renderer support is @Hovercraft's but inverse: first do the custom coloring (or null if none intended) then call super:
@Override
public Component getTableCellRendererComponent(JTable table,
Object value, boolean isSelected, boolean hasFocus, int row,
int column) {
if (myHighlightCondition) {
setBackground(Color.RED);
} else {
setBackground(null);
}
super.getTableCellRendererComponent(table, value, isSelected, hasFocus,
row, column);
return this;
}
(*) After all, this comment led to an answer, forgot that it's fixable on the custom renderer level :-)
BTW: Catching the "first" call to the renderer is highly brittle, there is no guaratee on which cell that will happen, might well be the last row of the last column.
You forgot your else part of your if block, the code that paints the background to the default if it is not the important row:
if (row == 0 && column == 0) {
d.setBackground(new java.awt.Color(255, 72, 72));
} else {
d.setBackground(null);
}
My SSCCE
import java.awt.Component;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.*;
import javax.swing.table.DefaultTableCellRenderer;
public class TestJTable {
private static int highlightedRow = 0;
private static void createAndShowGui() {
String[] columnNames = {"Program"};
Object[][] rowData = {{"Row 1"}, {"Row 2"}, {"Row 3"}, {"Row 4"},
{"Row 1"}, {"Row 2"}, {"Row 3"}, {"Row 4"},
{"Row 1"}, {"Row 2"}, {"Row 3"}, {"Row 4"}};
final JTable myTable = new JTable(rowData , columnNames );
myTable.setDefaultRenderer(Object.class, new DefaultTableCellRenderer()
{
@Override
public Component getTableCellRendererComponent(JTable table,
Object value, boolean isSelected, boolean hasFocus, int row,
int column) {
Component c = super.getTableCellRendererComponent(table, value, isSelected, hasFocus,
row, column);
if (row == highlightedRow && column == 0) {
c.setBackground(new java.awt.Color(255, 72, 72));
} else {
c.setBackground(null);
}
return c;
}
});
JFrame frame = new JFrame("TestJTable");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(new JScrollPane(myTable));
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
new Timer(1000, new ActionListener() {
@Override
public void actionPerformed(ActionEvent arg0) {
highlightedRow++;
int rowCount = myTable.getRowCount();
highlightedRow %= rowCount;
myTable.repaint();
}
}).start();
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
createAndShowGui();
}
});
}
}
Add an else clause to your if
:
if ((row == 0) && (column == 0)) {
d.setBackground(new java.awt.Color(255, 72, 72));
}
else {
d.setBackground(Color.WHITE);
}
Remember that the same renderer instance is used to paint all the cells.