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

坚强是说给别人听的谎言 提交于 2019-12-13 03:27:50

问题


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;
}

}


回答1:


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

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);
            }
        });
    }

}



回答2:


You have to set the cell renderer for every column of your JTable. I hope this example can help you:

import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.SwingUtilities;
import javax.swing.table.DefaultTableModel;

public class JTableTest
{

  public static void main( String[] args )
  {
    SwingUtilities.invokeLater( new Runnable()
    {
      @Override
      public void run()
      {
        buildUI();
      }
    } );
  }

  public static void buildUI()
  {
    final int w = 500;
    final int h = 200;
    Object colNames[] =
    {
      "COL1", "COL2", "COL3"
    };
    Object[][] data =
    {
    };
    DefaultTableModel dtm = new DefaultTableModel( data, colNames );
    dtm.addRow( new Object[]
    {
      "a", "b", "c"
    } );
    dtm.addRow( new Object[]
    {
      "d", "e", "f"
    } );
    dtm.addRow( new Object[]
    {
      "g", "h", "i"
    } );
    dtm.addRow( new Object[]
    {
      "l", "m", "n"
    } );
    final JTable t = new JTable( dtm );
    final TableColour tce = new TableColour();
    t.getColumnModel().getColumn( 0 ).setCellRenderer( tce );
    t.getColumnModel().getColumn( 1 ).setCellRenderer( tce );
    t.getColumnModel().getColumn( 2 ).setCellRenderer( tce );
    final JFrame f = new JFrame();
    f.setBounds( 0, 0, w, h );
    JScrollPane sp = new JScrollPane( t );
    f.getContentPane().add( sp );
    f.setVisible( true );

  }
}

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;
  }

}


来源:https://stackoverflow.com/questions/43349420/java-how-to-paint-a-specific-cell-in-a-jtable

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!