滚动嵌套 table in Java 时重绘问题 9+
Repainting problem when scrolling nested table in Java 9+
我有一个使用嵌套 JTables 的 Swing UI 元素。也就是说,外部 table returns JTable 对象的 TableCellRenderer 作为要为每个单元格呈现的 Component。此代码在 Java 8 下运行良好。在 Java 9 或 Java 10 下使用完全相同的代码会导致内部 table 在滚动 [=26] 时无法正确重绘=].当 window 调整大小时,table 正确重绘。
编辑:
我做了更多的挖掘,并将问题追溯到 BasicTableUI.java 中的 1862:1877 行——这些行是在 Java 中添加的 9. 如果我自己制作没有这些行的表 UI,问题消失了。从调试来看,问题似乎是在我的情况下 rMin 和 rMax 相等并且设置 rMax = rMax - 1 使得 rMax < rMin 这显然是不好的。添加的代码似乎用于处理打印 table 的问题。不确定为什么嵌套 tables 会造成这种中断。
参见下面的 SSCE:
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);
}
}
make my own TableUI without these lines, the problem disappears.
或者更简单,您可以使用 JViewport#SIMPLE_SCROLL_MODE:
JScrollPane scrollPane = new JScrollPane(mainTable);
scrollPane.getVerticalScrollBar().setUnitIncrement(10);
scrollPane.getViewport().setScrollMode(JViewport.SIMPLE_SCROLL_MODE);
我知道它的老问题,但我遇到了同样的问题,也许遇到同样问题的其他人会觉得这有帮助。
这是一个 JDK 多次报告的错误(因此有很多重复),但大部分信息都可以在以下票证中找到:
https://bugs.openjdk.java.net/browse/JDK-8202702
已将这些行添加到 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
要么
https://github.com/openjdk/jdk/commit/bb9fed1008dee377725dc669401c389da685f618#diff-ae77528c5d554a9870371e7075801adc4ecd8f992ff484804f164b692b388858)
JDK12 以后固定。不幸的是,它没有向后移植到 JDK 11,所以如果你被迫使用这个 LTS 版本,你可以像@MattWallace 那样使用自己的 TableUI 或者你可以覆盖你的Table 用
扩展 JTable
@Override
public int getSelectedRow()
{
final int i = super.getSelectedRow();
return i == -1 ? -2 : i;
}
这可能会带来 BasicTableUI 评论中提到的问题:
// 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
但如果您可以接受,这也是一种解决方法。其他解决方法,如将 Table 包装在 JViewPort 或 JScrollPane 中,我不推荐,因为我遇到了表格的大小问题,由于长文本,表格的行高可能会发生变化。
我有一个使用嵌套 JTables 的 Swing UI 元素。也就是说,外部 table returns JTable 对象的 TableCellRenderer 作为要为每个单元格呈现的 Component。此代码在 Java 8 下运行良好。在 Java 9 或 Java 10 下使用完全相同的代码会导致内部 table 在滚动 [=26] 时无法正确重绘=].当 window 调整大小时,table 正确重绘。
编辑: 我做了更多的挖掘,并将问题追溯到 BasicTableUI.java 中的 1862:1877 行——这些行是在 Java 中添加的 9. 如果我自己制作没有这些行的表 UI,问题消失了。从调试来看,问题似乎是在我的情况下 rMin 和 rMax 相等并且设置 rMax = rMax - 1 使得 rMax < rMin 这显然是不好的。添加的代码似乎用于处理打印 table 的问题。不确定为什么嵌套 tables 会造成这种中断。
参见下面的 SSCE:
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);
}
}
make my own TableUI without these lines, the problem disappears.
或者更简单,您可以使用 JViewport#SIMPLE_SCROLL_MODE:
JScrollPane scrollPane = new JScrollPane(mainTable);
scrollPane.getVerticalScrollBar().setUnitIncrement(10);
scrollPane.getViewport().setScrollMode(JViewport.SIMPLE_SCROLL_MODE);
我知道它的老问题,但我遇到了同样的问题,也许遇到同样问题的其他人会觉得这有帮助。
这是一个 JDK 多次报告的错误(因此有很多重复),但大部分信息都可以在以下票证中找到:
https://bugs.openjdk.java.net/browse/JDK-8202702
已将这些行添加到 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 要么 https://github.com/openjdk/jdk/commit/bb9fed1008dee377725dc669401c389da685f618#diff-ae77528c5d554a9870371e7075801adc4ecd8f992ff484804f164b692b388858)
JDK12 以后固定。不幸的是,它没有向后移植到 JDK 11,所以如果你被迫使用这个 LTS 版本,你可以像@MattWallace 那样使用自己的 TableUI 或者你可以覆盖你的Table 用
扩展 JTable@Override
public int getSelectedRow()
{
final int i = super.getSelectedRow();
return i == -1 ? -2 : i;
}
这可能会带来 BasicTableUI 评论中提到的问题:
// 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
但如果您可以接受,这也是一种解决方法。其他解决方法,如将 Table 包装在 JViewPort 或 JScrollPane 中,我不推荐,因为我遇到了表格的大小问题,由于长文本,表格的行高可能会发生变化。