Search code examples
javaswingcolorsjtabletablecellrenderer

Changing Cell Color without changing color in other cells (Jtable)


So say that we have a JTable with 31 columns and 10 rows. And I want to change the color of the 2 Column 4 row to red. And after I do that change another cell color without loosing the color of my previous cell.

I have tried the following without success:

public class CellR extends DefaultTableCellRenderer {

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

        setForeground(Color.white);
        if(row == TestHotel.v.getRow()  && column == TestHotel.v.getCol()){
            // Only for specific cell
            // c.setFont(/* special font*/);
            // you may want to address isSelected here too
            setForeground(Color.BLACK);
            setBackground(Color.RED);
         } 
         return this;
}

If I call the renderer the first time it is working... But if I then want to change another cell color I am loosing the first one.


Solution

  • The cell renderers in tables and lists are used like a "stamp". One component is used for painting all the cells. Also see Concepts: Editors and Renderers. If you want to retain the information about the "highlighted" cells, you somehow have to store them.

    An example (extended based on the comments) :

    import java.awt.BorderLayout;
    import java.awt.Color;
    import java.awt.Component;
    import java.awt.Point;
    import java.awt.event.ActionEvent;
    import java.awt.event.ActionListener;
    import java.util.HashMap;
    import java.util.Map;
    import java.util.Random;
    
    import javax.swing.JButton;
    import javax.swing.JFrame;
    import javax.swing.JScrollPane;
    import javax.swing.JTable;
    import javax.swing.SwingUtilities;
    import javax.swing.table.DefaultTableCellRenderer;
    import javax.swing.table.TableColumn;
    import javax.swing.table.TableColumnModel;
    
    public class CellRendererTest
    {
        public static void main(String[] args)
        {
            SwingUtilities.invokeLater(new Runnable()
            {
                @Override
                public void run()
                {
                    createAndShowGUI();
                }
            });
        }
    
        private static void createAndShowGUI()
        {
            JFrame f = new JFrame();
            f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    
            String[] columnNames = {
                "First Name", "Last Name", "Sport" };
            Object[][] data = {
                {"Kathy", "Smith", "Snowboarding" },
                {"John", "Doe", "Rowing" },
                {"Sue", "Black", "Knitting"},
                {"Jane", "White", "Speed reading"},
                {"Joe", "Brown", "Pool"}
            };
            final JTable table = new JTable(data, columnNames);
    
            final ColoringCellRenderer cellRenderer = new ColoringCellRenderer(); 
            TableColumnModel columnModel = table.getColumnModel();
            int cc = columnModel.getColumnCount();
            for (int c=0; c<cc; c++)
            {
                TableColumn column = columnModel.getColumn(c);
                column.setCellRenderer(cellRenderer);
            }
            JScrollPane scrollPane = new JScrollPane(table);
            f.getContentPane().setLayout(new BorderLayout());
            f.getContentPane().add(scrollPane, BorderLayout.CENTER);
    
            JButton addRandomColorButton = new JButton("Add random color");
            addRandomColorButton.addActionListener(new ActionListener()
            {
                private Random random = new Random(0);
                @Override
                public void actionPerformed(ActionEvent e)
                {
                    int rows = table.getRowCount();
                    int cols = table.getColumnCount();
                    int row = random.nextInt(rows);
                    int col = random.nextInt(cols);
                    int r = random.nextInt(255);
                    int g = random.nextInt(255);
                    int b = random.nextInt(255);
                    cellRenderer.setCellColor(row, col, new Color(r,g,b));
                    table.repaint();
                }
            });
            f.getContentPane().add(addRandomColorButton, BorderLayout.SOUTH);
    
            f.pack();
            f.setLocationRelativeTo(null);
            f.setVisible(true);
        }
    }
    
    
    class ColoringCellRenderer extends DefaultTableCellRenderer
    {
        private final Map<Point, Color> cellColors = new HashMap<Point, Color>();
    
        void setCellColor(int r, int c, Color color)
        {
            if (color == null)
            {
                cellColors.remove(new Point(r,c));
            }
            else
            {
                cellColors.put(new Point(r,c), color);
            }
        }
    
        private Color getCellColor(int r, int c)
        {
            Color color = cellColors.get(new Point(r,c));
            if (color == null)
            {
                return Color.WHITE;
            }
            return color;
        }
    
        @Override
        public Component getTableCellRendererComponent(JTable table, Object value,
            boolean isSelected, boolean hasFocus, int row, int column)
        {
            super.getTableCellRendererComponent(
                table, value, isSelected, hasFocus, row, column);
            Color color = getCellColor(row, column);
            setBackground(color);
            return this;
        }
    }
    

    EDIT: The remaining part was from the original answer, using only a single cell color. The new one above is more complete (and more powerful, because it can emulate the single-color renderer), but I'll leave this here for completeness

    This could be achieved with a renderer like this one:

    class ColoringCellRenderer extends DefaultTableCellRenderer
    {
        private final Set<Point> highlightedCells = new HashSet<Point>();
    
        void setHighlighted(int r, int c, boolean highlighted)
        {
            if (highlighted)
            {
                highlightedCells.add(new Point(r,c));
            }
            else
            {
                highlightedCells.remove(new Point(r,c));
            }
        }
    
        private boolean isHighlighted(int r, int c)
        {
            return highlightedCells.contains(new Point(r,c));
        }
    
        public Component getTableCellRendererComponent(JTable table, Object value,
            boolean isSelected, boolean hasFocus, int row, int column)
        {
    
            if (isHighlighted(row,  column))
            {
                setForeground(Color.BLACK);
                setBackground(Color.RED);
            }
            else
            {
                setForeground(Color.BLACK);
                setBackground(Color.WHITE);
            }
            return this;
        }
    }
    

    You can then create an instance of this renderer, and add or remove cells to be highlighted:

    ColoringCellRenderer r = new ColoringCellRenderer();
    // Assign renderer to table...
    ...
    
    // Later, highlight cells:
    r.setHighlighted(4,2,true);
    r.setHighlighted(6,1,true);
    r.setHighlighted(1,5,false);
    ...
    

    If you want different colors for the cells, you could replace the Set with a Map that maps a particular Point (representing the row/column of the cell) to a Color object.