I have a Swing UI element that uses nested JTables. That is, the TableCellRenderer of the outer table returns JTable objects as the Component to render for each cells. This code works great under Java 8. Using the exact same code under Java 9 or Java 10 causes the inner table to not be repainted properly when scrolling the table. The table IS repainted properly when the window is resized.
Edit: I did some more digging and traced the problem to lines 1862:1877 in BasicTableUI.java - these lines were added in Java 9. If I make my own TableUI without these lines, the problem disappears. From debugging, it appears that the issue is that in my case rMin and rMax are equal and setting rMax = rMax - 1 makes rMax < rMin which is obviously is bad. The added code seems to be there to deal with issues printing a table. Not sure why nested tables makes this break.
See the SSCE below:
package testing.test_painting;
import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.plaf.basic.BasicTableHeaderUI;
import javax.swing.table.AbstractTableModel;
import javax.swing.table.JTableHeader;
import javax.swing.table.TableCellRenderer;
import javax.swing.table.TableColumn;
import javax.swing.table.TableColumnModel;
import javax.swing.table.TableModel;
import java.awt.Component;
import java.awt.Dimension;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
class InnerTable extends JTable {
public InnerTable(TableModel dm) {
super(dm);
}
}
class InnerTableModel extends AbstractTableModel {
@Override
public int getRowCount() {
return 500;
}
@Override
public int getColumnCount() {
return 10;
}
@Override
public Object getValueAt(int rowIndex, int columnIndex) {
return rowIndex + "," + columnIndex;
}
}
class OuterTableModel extends AbstractTableModel {
private final List<JTable> termTables;
public OuterTableModel() {
this.termTables = Arrays.asList(new InnerTable(new InnerTableModel()));
}
@Override
public int getRowCount() {
return 1;
}
@Override
public int getColumnCount() {
return termTables.size();
}
@Override
public Object getValueAt(int rowIndex, int columnIndex) {
return termTables.get(columnIndex);
}
public JTable getTermTable(int modelColumn) {
return termTables.get(modelColumn);
}
}
class OuterTable extends JTable {
private final List<TableRenderer> renderers;
private class TableRenderer implements TableCellRenderer {
private final OuterTableModel tableModel;
public TableRenderer(OuterTableModel tableModel) {
this.tableModel = tableModel;
}
@Override
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
int modelColumn = convertColumnIndexToModel(column);
JTable termTable = tableModel.getTermTable(modelColumn);
termTable.setVisible(true);
return termTable;
}
}
private final OuterTableModel tableModel;
public OuterTable(OuterTableModel tableModel) {
super(tableModel);
renderers = new ArrayList<>(tableModel.getColumnCount());
for (int i = 0; i < tableModel.getColumnCount(); i++) {
renderers.add(new TableRenderer(tableModel));
}
this.tableModel = tableModel;
setCellDimensions();
}
@Override
public void setTableHeader(JTableHeader tableHeader) {
tableHeader.setUI(new BasicTableHeaderUI());
super.setTableHeader(tableHeader);
}
private void setCellDimensions() {
Dimension preferredSize = tableModel.getTermTable(0).getPreferredSize();
if (getRowHeight() != preferredSize.height) {
setRowHeight(preferredSize.height);
}
TableColumnModel columnModel = getColumnModel();
for (int i = 0; i < columnModel.getColumnCount(); i++) {
TableColumn column = columnModel.getColumn(i);
column.setMinWidth(preferredSize.width);
column.setMaxWidth(preferredSize.width);
column.setPreferredWidth(preferredSize.width);
column.setWidth(preferredSize.width);
}
setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
}
@Override
public TableCellRenderer getCellRenderer(int row, int column) {
return renderers.get(column);
}
}
public class TestNestedTable {
public static void main(String[] args) {
OuterTableModel mainTableModel = new OuterTableModel();
OuterTable mainTable = new OuterTable(mainTableModel);
JFrame jFrame = new JFrame();
JScrollPane scrollPane = new JScrollPane(mainTable);
scrollPane.getVerticalScrollBar().setUnitIncrement(10);
jFrame.getContentPane().add(scrollPane);
jFrame.pack();
jFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
jFrame.setVisible(true);
}
}
I know its and old question, but I had the same issue and maybe someone else with the same problem finds this helpful.
This is an JDK bug reported several times (so a lot of duplicates) but most information can be found in the following ticket:
https://bugs.openjdk.java.net/browse/JDK-8202702
There has been added these lines to the BasicTableUI:
// Do not decrement rMax if rMax becomes
// less than or equal to rMin
// else cells will not be painted
if (rMax - rMin > 1) {
rMax = rMax - 1;
}
(s. http://hg.openjdk.java.net/jdk/client/rev/3ba3d39b91c7 or https://github.com/openjdk/jdk/commit/bb9fed1008dee377725dc669401c389da685f618#diff-ae77528c5d554a9870371e7075801adc4ecd8f992ff484804f164b692b388858)
It is fixed for JDK 12 onward. Unfortunaltey, it is not backported to JDK 11, so if you are forced to use this LTS version one can either do it like @MattWallace and use own TableUI or you can overwrite your Table extending JTable with
@Override
public int getSelectedRow()
{
final int i = super.getSelectedRow();
return i == -1 ? -2 : i;
}
This might bring the problems back mentioned in BasicTableUI comments:
// We did rMax-1 to paint the same number of rows that are drawn on console
// otherwise 1 extra row is printed per page than that are displayed
// when there is no scrollPane and we do printing of table
// but not when rmax is already pointing to index of last row
// and if there is any selected rows
But if you can live with that, this can be also a workaround. Other workarounds like wrapping the Table in a JViewPort or a JScrollPane I cannot recommend as I got size issues with tables which can have changing row heights due to long texts.