My Java 8 app uses a JTable
inside a JScrollPane
. Currently the table has more than 10 columns and the data is added using DefaultTableModel
's addRow(someObjectArray)
. All cells in a column currently have the same type of data but columns 4+ can contain cells with "null" (0-3 always contain data != null!).
If the data in the first three columns isn't the same as the data in the previous row, the first three cells in the current row are written in a black, bold font, otherwise they use the normal, plain and dark grey font - it's simply a way to mark the beginning of new data:
Filling the table and scrolling vertically sets the bold font and plain font properly but scrolling right and then back to the first three columns, the bold font is messed up: It's sometimes set for every cell, sometimes for the wrong cells and sometimes it changes mid-text:
Scrolling down and up again resets the cells to what they should look like.
This is how the table is set up:
JTable myTable = new JTable() {
@Override
public Component prepareRenderer(TableCellRenderer renderer, int row, int column) {
Component c = super.prepareRenderer(renderer, row, column);
if (column<3) {
if(row==0) { //Always mark column 0-2 in row 0
c.setFont(c.getFont().deriveFont(Font.BOLD));
c.setForeground(Color.BLACK);
} else {
if(column==0) { //Check if column 0-2 should be marked and mark column 0 (or not)
Object prevColumnA = getValueAt(row-1, column); //String
Object currColumnA = getValueAt(row, column); //String
Object prevColumnB = getValueAt(row-1, column+1); //int
Object currColumnB = getValueAt(row, column+1); //int
Object prevColumnC = getValueAt(row-1, column+2); //String
Object currColumnC = getValueAt(row, column+2); //String
if(!prevColumnA.equals(currColumnA) || !prevColumnB.equals(currColumnB) || !prevColumnC.equals(currColumnC)) {
markRow = true;
c.setFont(c.getFont().deriveFont(Font.BOLD));
c.setForeground(Color.BLACK);
} else {
markRow = false;
c.setFont(c.getFont().deriveFont(Font.PLAIN));
c.setForeground(Color.DARK_GRAY);
}
} else { //Mark column 1-2 (or not)
if(markRow) {
c.setFont(c.getFont().deriveFont(Font.BOLD));
c.setForeground(Color.BLACK);
} else {
c.setFont(c.getFont().deriveFont(Font.PLAIN));
c.setForeground(Color.DARK_GRAY);
}
}
}
} else {
c.setFont(c.getFont().deriveFont(Font.PLAIN));
c.setForeground(Color.DARK_GRAY);
}
//System.out.println("row="+row+", column="+column+", markRow="+markRow);
return c;
}
};
How do I fix this?
Edit: MRE copied from here:
import java.awt.*;
import javax.swing.*;
import javax.swing.table.*;
public class SomeMRE extends JPanel {
boolean markRow = false;
public SomeMRE() {
JTable myTable = new JTable() {
@Override
public Component prepareRenderer(TableCellRenderer renderer, int row, int column) {
Component c = super.prepareRenderer(renderer, row, column);
if (column<3) {
if(row==0) { //Always mark column 0-2 in row 0
c.setFont(c.getFont().deriveFont(Font.BOLD));
c.setForeground(Color.BLACK);
} else {
if(column==0) { //Check if column 0-2 should be marked and mark column 0 (or not)
Object prevColumnA = getValueAt(row-1, column); //String
Object currColumnA = getValueAt(row, column); //String
Object prevColumnB = getValueAt(row-1, column+1); //int
Object currColumnB = getValueAt(row, column+1); //int
Object prevColumnC = getValueAt(row-1, column+2); //String
Object currColumnC = getValueAt(row, column+2); //String
if(!prevColumnA.equals(currColumnA) || !prevColumnB.equals(currColumnB) || !prevColumnC.equals(currColumnC)) {
markRow = true;
c.setFont(c.getFont().deriveFont(Font.BOLD));
c.setForeground(Color.BLACK);
} else {
markRow = false;
c.setFont(c.getFont().deriveFont(Font.PLAIN));
c.setForeground(Color.DARK_GRAY);
}
} else { //Mark column 1-2 (or not)
if(markRow) {
c.setFont(c.getFont().deriveFont(Font.BOLD));
c.setForeground(Color.BLACK);
} else {
c.setFont(c.getFont().deriveFont(Font.PLAIN));
c.setForeground(Color.DARK_GRAY);
}
}
}
} else {
c.setFont(c.getFont().deriveFont(Font.PLAIN));
c.setForeground(Color.DARK_GRAY);
}
//System.out.println("row="+row+", column="+column+", markRow="+markRow);
return c;
}
};
myTable.setModel(new DefaultTableModel(
new Object[][] {
{"ColumnA text 1", 123, "ColumnC text 1", 1, null, "bla", "bla", "bla", null},
{"ColumnA text 2", 234, "ColumnC text 2", 2, null, "bla", "bla", null, null},
{"ColumnA text 2", 234, "ColumnC text 2", 3, null, "bla", "bla", "bla", null},
{"ColumnA text 2", 234, "ColumnC text 2", 4, null, "bla", "bla", null, null},
{"ColumnA text 2", 234, "ColumnC text 2", 5, null, "bla", null, null, null},
{"ColumnA text 1", 123, "ColumnC text 1", 6, null, "bla", "bla", "bla", null},
{"ColumnA text 2", 234, "ColumnC text 2", 7, null, "bla", null, null, null},
{"ColumnA text 2", 234, "ColumnC text 2", 8, null, "bla", "bla", null, null},
{"ColumnA text 2", 234, "ColumnC text 2", 9, null, "bla", "bla", null, null}
},
new String[] {
"ColumnA", "ColumnB", "ColumnC", "ColumnD", "ColumnE", "ColumnF", "ColumnG", "ColumnH", "ColumnI"
}
));
setLayout( new BorderLayout() );
add(new JScrollPane(myTable));
}
private static void createAndShowGUI() {
JFrame frame = new JFrame("SomeMRE");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(new SomeMRE());
frame.pack();
frame.setVisible( true );
}
public static void main(String[] args) throws Exception {
java.awt.EventQueue.invokeLater( () -> createAndShowGUI() );
}
}
Best is to determine settings specifically for each cell individually in a standalone way, without using a variable (markRow
in your code fragment) which is determined at some specific cell coordinate (e.g. the first cell in a row).
The reason is that there is no determined way that prepareRenderer
is called. There is no guarantee that this method will first be called for the first cell in the first row, then for the second cell in the first row, and so on. Put differently, don't count on prepareRenderer
being called in a sequential fashion, or any specific order.
Also note, that the renderer component should be properly initialized for each cell. In the code fragment from the first version of your question, there was no else
part to your starting if(column<3)
which means that the component could have been arbitrarily initialized. If there is a decision tree to prepare a renderer, make sure that all paths prepare the renderer as is needed.