Search code examples
javaswingcolorsjtabletablecellrenderer

Java. How to paint a specific cell in a JTable?


Ok I'm in this situation... I have Renderer in my class but have no idea how to use it to make certain cell's background red. It's a room renting app and I have Jtable as calendar so I want paint cells which are rent red. So It should somehow take specific column and row and make that cell red. My renderer down bellow but as I said no Idea how to use it since Im new to java. Real question how do I pass that column and row, I have problem with that. Cell rendered worked with some other code but that wasnt what I need.

ublic class TableColour extends javax.swing.table.DefaultTableCellRenderer {
@Override
public java.awt.Component getTableCellRendererComponent(javax.swing.JTable table, java.lang.Object value, boolean isSelected, boolean hasFocus, int row, int column) {
    java.awt.Component cellComponent = super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
    cellComponent.setBackground(java.awt.Color.RED);
    return cellComponent;
}

}


Solution

  • Alright oh wow I might have some trouble figuring this out. But maybe somehow. You said you dont know how my code looks, well I have some basic renderer. One thing to have in mind I have 2 dimensional array ReservedOne which holds row index and column index of taken room also room number date, time until which its reserved. So now Im a bit confused when looking at your example how to use my array to set colors. I hope I wont have mental breakdown

    Your TableModel should be modeling this data, this is very important, as it allows the rest of the API to revolve around it

    Real question how do I pass that column and row, I have problem with that. Cell rendered worked with some other code but that wasnt what I need.

    This is why it's important to have the TableModel wrap the data, as the table API will pass the row and column information to the TableCellRenderer, but it will also pass the cell value!

    public class RoomTableModel extends AbstractTableModel {
    
        private Room[][] reservations;
    
        public RoomTableModel(Room[][] reservations) {
            this.reservations = reservations;
        }
    
        @Override
        public int getRowCount() {
            return reservations.length;
        }
    
        @Override
        public int getColumnCount() {
            return reservations[0].length;
        }
    
        @Override
        public Object getValueAt(int rowIndex, int columnIndex) {
            return reservations[rowIndex][columnIndex];
        }
    
        @Override
        public Class<?> getColumnClass(int columnIndex) {
            return Room.class;
        }
    
        @Override
        public void setValueAt(Object aValue, int rowIndex, int columnIndex) {
            if (aValue instanceof Room) {
                Room room = (Room) aValue;
                reservations[rowIndex][columnIndex] = room;
                fireTableCellUpdated(rowIndex, columnIndex);
            }
        }
    
    }
    

    This means we can now setup the cell renderer to display the information we need

    public static class RoomTableCellRenderer extends DefaultTableCellRenderer {

        private static Color BOOKED_COLOR = Color.RED;
    
        @Override
        public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
            if (value instanceof Room && value != null) {
                if (isSelected) {
                    setBackground(table.getSelectionBackground());
                    setForeground(table.getSelectionForeground());
                } else {
                    setBackground(table.getBackground());
                    setForeground(table.getForeground());
                }
                // Update the details based on the room properties
            } else { //if (value == null) {
                setBackground(BOOKED_COLOR);
                setText(null);
            }
            return this;
        }
    
    }
    

    Don't forget, if you want the table to use your renderer, you need to register it...

    table.setDefaultRenderer(Room.class, new RoomTableCellRenderer());
    

    Updated...

    Based on the available data been stored in 2D String array (you really don't like me).

    This is a little dirty. In reality, the data should be setup in such away as it could be passed to the TableModel and let it take care of the details. You're also going to have be careful in how you update the array, as updates won't be reflected by the table until you can force it to refresh ... and that won't be pretty.

    public class LocalDateTableCellRenderer extends DefaultTableCellRenderer {
    
        protected static DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd");
        private String[][] bookings;
    
        public LocalDateTableCellRenderer(String[][] bookings) {
            this.bookings = bookings;
        }
    
    
        @Override
        public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
            super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
            setBackground(isSelected ? table.getSelectionBackground() : table.getBackground());
            setForeground(isSelected ? table.getSelectionForeground() : table.getForeground());
            if (value instanceof LocalDate) {
                LocalDate date = (LocalDate) value;
                if (hasBookingFor(date)) {
                    setForeground(Color.WHITE);
                    setBackground(Color.RED);
                }
                setText(formatter.format(date));
            } else {
                setText(null);
            }
            return this;
        }
    
        protected boolean hasBookingFor(LocalDate date) {
            for (String[] data : bookings) {
                int day = Integer.parseInt(data[2]);
                int month = Integer.parseInt(data[3]);
                int year = 2017; // Because :P
    
                LocalDate booking = LocalDate.of(year, month, day);
                if (booking.isEqual(date)) {
                    return true;
                }
            }
            return false;
        }
    
    }
    

    Basically, this allows you to pass the booking information to the TableCellRenderer, as I've stated, this is NOT how you really should be doing this, but it would require a significant restructure of your code to make it work properly.

    Now, I create a TableModel which basically takes a year and month value and returns a LocalDate for each cell (or null if the cell is out of the month range)

    public class CalendarModel extends AbstractTableModel {
    
        public static String[] COLUMN_NAMES = {"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"};
    
        private int rows = 0;
    
        private LocalDate startOfCalendar;
        private LocalDate firstDayOfMonth;
        private LocalDate lastDayOfMonth;
    
        public CalendarModel(int year, Month month) {
            firstDayOfMonth = LocalDate.of(year, month, 1);
    
            startOfCalendar = firstDayOfMonth.minusDays(firstDayOfMonth.getDayOfWeek().getValue());
            lastDayOfMonth = firstDayOfMonth.with(TemporalAdjusters.lastDayOfMonth());
    
            System.out.println(startOfCalendar.getDayOfWeek());
            System.out.println(firstDayOfMonth);
            System.out.println(lastDayOfMonth);
    
            Duration between = Duration.between(startOfCalendar.atStartOfDay(), lastDayOfMonth.atStartOfDay());
            long days = between.toDays();
            rows = (int) Math.round(days / 7d) + 1;
        }
    
        @Override
        public int getRowCount() {
            return rows;
        }
    
        @Override
        public int getColumnCount() {
            return 7;
        }
    
        @Override
        public Class<?> getColumnClass(int columnIndex) {
            return LocalDate.class;
        }
    
        @Override
        public String getColumnName(int column) {
            return COLUMN_NAMES[column];
        }
    
        @Override
        public Object getValueAt(int rowIndex, int columnIndex) {
    
            LocalDate date = null;
    
            if (startOfCalendar != null) {
                int day = (rowIndex * 7) + columnIndex;
                date = startOfCalendar.plusDays(day);
    
                if (date.isBefore(firstDayOfMonth) || date.isAfter(lastDayOfMonth)) {
                    date = null;
                }
            }
    
            return date;
    
        }
    
    }
    

    This means that the TableCellRenderer is been passed either a null value of LocalDate value, with this information, you then need to search your array for any possible bookings for the specified date.

    This will scale horribly, which is why I've avoided doing and kept trying to get you to change how you manage your data, but here it is

    And finally a really rough example...

    This example doesn't really care for all the information you'll be managing, it will only care about the month and day information

    Example

    import java.awt.Color;
    import java.awt.Component;
    import java.awt.EventQueue;
    import java.time.Duration;
    import java.time.LocalDate;
    import java.time.Month;
    import java.time.format.DateTimeFormatter;
    import java.time.temporal.TemporalAdjusters;
    import javax.swing.JFrame;
    import javax.swing.JScrollPane;
    import javax.swing.JTable;
    import javax.swing.UIManager;
    import javax.swing.UnsupportedLookAndFeelException;
    import javax.swing.table.AbstractTableModel;
    import javax.swing.table.DefaultTableCellRenderer;
    import javax.swing.table.TableModel;
    
    public class Test {
    
        public static void main(String[] args) {
            new Test();
        }
    
        public Test() {
            EventQueue.invokeLater(new Runnable() {
                @Override
                public void run() {
                    try {
                        UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
                    } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
                        ex.printStackTrace();
                    }
    
                    String[][] bookings = new String[7][6];
                    bookings[0][2] = "5";
                    bookings[0][3] = "4";
                    bookings[1][2] = "10";
                    bookings[1][3] = "4";
                    bookings[2][2] = "15";
                    bookings[2][3] = "4";
                    bookings[3][2] = "20";
                    bookings[3][3] = "4";
                    bookings[4][2] = "25";
                    bookings[4][3] = "4";
                    bookings[5][2] = "30";
                    bookings[5][3] = "4";
                    bookings[6][2] = "5";
                    bookings[6][3] = "5";
    
                    TableModel model = new CalendarModel(2017, Month.APRIL);
                    JTable table = new JTable(model);
                    table.setDefaultRenderer(LocalDate.class, new LocalDateTableCellRenderer(bookings));
    
                    JFrame frame = new JFrame("Testing");
                    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                    frame.add(new JScrollPane(table));
                    frame.pack();
                    frame.setLocationRelativeTo(null);
                    frame.setVisible(true);
                }
            });
        }
    
    }