Search code examples
javajtablebackground-colortablecellrenderer

Mixed color rendering in a JTable


I hava a JTable into which I've placed a set of numbers representing gear ratios. There are some repetitions in the data and so I would like a different colored background for them; however, each  set of duplicates must have a different color.  Duplicates may consist of two, three or four cells.  The  list of duplicates (an ArrayList)  includes the row and column in which each exist, and the RGB color (background and foreground) assigned to that set of duplicates.  It is defined like so:

    private Double ratio;       
    private int row;
    private int col;
    private int backgroundRed;
    private int backgroundGreen;
    private int backgroundBlue;
    private int foregroundRed;
    private int foregroundGreen;
    private int foregroundBlue;

The list is sent to a cell renderer, so it would seem easy enough—all it has to do is read the list and render the colors.  The trouble I'm having is that I can't seem to get the renderer to reset to the default black and white needed for those ratios that are not duplicated.

Here is what the table should look like.  All of the non-duplicated cells are rendered in a white background with black font.

enter image description here

And here is the output from what I have developed thus far:

enter image description here

Row 0 in the output is almost correct in that it displays the first 3 columns in black and white since they are not duplicated, and the remaining cells in that row are different colors.  The trouble starts in row 1, col 0 and col 1 where these two cells should also be in black and white but are in the same colors as row 0 col 8.  This tells me that it’s not resetting to the default (i.e., black on white).  This occurs again in row 3 (Col 5, 6 & 7), row 4 (Col 6, 7 & 8), row 5 (Col 0, 1 &2), and all of rows 6 and 8.

Here is the code for  the renderer.  You’ll notice that I tried two different comparisons (IF statements): one that compares the ratio values and the other that identifies the row and column of each set of duplicates.  Both of these produce similar results.  So my question  is, how can I reset the renderer to a default color for those cells that are not duplicated?

public class NumberCellRenderer extends DefaultTableCellRenderer
{
    private static final long serialVersionUID = 1L;
    private JLabel label;
    private DecimalFormat numberFormat = new DecimalFormat("#0.000"); 
    private List<DuplicateValues> duplicateValues = new ArrayList<>();
    private String text;

    public NumberCellRenderer (List<DuplicateValues> duplicateValues)
    {
        this.duplicateValues = duplicateValues;
    }
    
    @Override
    public JLabel getTableCellRendererComponent(JTable jTable, Object value, boolean isSelected, boolean hasFocus, int row, int column)
    {
        Component c = super.getTableCellRendererComponent(jTable, value, isSelected, hasFocus, row, column);
        if (c instanceof JLabel && value instanceof Number)
        {
            label = (JLabel) c;
            label.setHorizontalAlignment(JLabel.CENTER);
            Number num = (Number) value;
            text = numberFormat.format(num);
            label.setText(text);
        }
        for(int i=0; i<duplicateValues.size(); i++)
        {
//          if(duplicateValues.get(i).getRatio() == Double.parseDouble(value.toString()))
            if(row == duplicateValues.get(i).getRow() && column == duplicateValues.get(i).getCol())
            {
                label.setBackground(new Color(duplicateValues.get(i).getBackgroundRed(), duplicateValues.get(i).getBackgroundGreen(), duplicateValues.get(i).getBackgroundBlue()));
                label.setForeground(new Color(duplicateValues.get(i).getForegroundRed(), duplicateValues.get(i).getForegroundGreen(), duplicateValues.get(i).getForegroundBlue()));
            }
            else
            {
                label.setBackground(getBackground());
                label.setForeground(getForeground());
            }
        }
    return label;
    }
    return label;
    }
}

I've looked at other solutions but most want to render an entire column or row the same color. This app requires that the same colors be dispersed throughout the table.


Solution

  • Let’s walk through this loop…

    for(int i=0; i<duplicateValues.size(); i++)
    {
        if(row == duplicateValues.get(i).getRow() && column == duplicateValues.get(i).getCol())
        {
    

    When the above if test is true, the code applies custom colors to the label:

            label.setBackground(new Color(duplicateValues.get(i).getBackgroundRed(), duplicateValues.get(i).getBackgroundGreen(), duplicateValues.get(i).getBackgroundBlue()));
            label.setForeground(new Color(duplicateValues.get(i).getForegroundRed(), duplicateValues.get(i).getForegroundGreen(), duplicateValues.get(i).getForegroundBlue()));
    

    And that’s perfect… as long as the program doesn’t go changing the label’s colors to something else. However, because this loop keeps going (in most cases), the next iteration of the loop will not set these same colors. Instead, it will enter the else part:

        }
        else
        {
            label.setBackground(getBackground());
            label.setForeground(getForeground());
        }
    

    And now the code which set the custom colors based on the duplicate values has been undone. Your custom colors are lost.

    The solution is to call label.setForeground and label.setBackground only once, after the loop has finished:

    Color foreground;
    Color background;
    
    if (isSelected) {
        foreground = table.getSelectionForeground();
        background = table.getSelectionBackground();
    } else {
        foreground = table.getForeground();
        background = table.getBackground();
    
        for (DuplicateValues d : duplicateValues) {
            if (row == d.getRow() && column == d.getCol()) {
                foreground = new Color(
                    d.getForegroundRed(),
                    d.getForegroundGreen(),
                    d.getForegroundBlue());
                background = new Color(
                    d.getBackgroundRed(),
                    d.getBackgroundGreen(),
                    d.getBackgroundBlue());
    
                // We found a match.  No need to keep checking!
                break;
            }
        }
    }
    
    label.setForeground(foreground);
    label.setBackground(background);