JTable 中的单元格在水平滚动后改变外观

Cells in JTable change appearance after scrolling horizontally

我的 Java 8 应用程序在 JScrollPane 中使用 JTable。目前 table 有超过 10 列,数据是使用 DefaultTableModeladdRow(someObjectArray) 添加的。一列中的所有单元格当前都具有相同类型的数据,但列 4+ 可以包含具有 "null" 的单元格(0-3 始终包含数据!= null!)。

如果前三列中的数据与上一行中的数据不相同,则当前行中的前三个单元格以黑色粗体字体书写,否则使用普通的普通字体和深灰色字体 - 这只是一种标记新数据开始的方法:

填充 table 并垂直滚动正确设置粗体和普通字体,但向右滚动然后返回前三列,粗体字体被弄乱了:有时为每个单元格设置,有时对于错误的单元格,有时它会更改中间文本:

再次向下和向上滚动会将单元格重置为它们应有的样子。

这是 table 的设置方式:

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

我该如何解决这个问题?

编辑: MRE 复制自

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

首先,不是 MRE。您陈述的问题是:

but scrolling right and then back to the first three columns, the bold font is messed up:

好吧,您发布的代码不进行水平滚动,因此您无法测试您发布的代码以确保它演示了您陈述的问题。

在测试粗体条件之前,您是否看过我关于删除 "markRow" 变量并设置默认呈现的原始评论?

当我尝试上述建议时,我能够显着简化代码:

JTable myTable = new JTable()
{
    @Override
    public Component prepareRenderer(TableCellRenderer renderer, int row, int column)
    {
        Component c = super.prepareRenderer(renderer, row, column);
        c.setForeground(Color.DARK_GRAY);

        if (column < 3)
        {
            if(row == 0) { //Always mark columns 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)
                TableModel model = getModel();
                Object prevColumn0 = model.getValueAt(row-1, 0); //String
                Object currColumn0 = model.getValueAt(row, 0); //String
                Object prevColumn1 = model.getValueAt(row-1, 1); //int
                Object currColumn1 = model.getValueAt(row, 1); //int
                Object prevColumn2 = model.getValueAt(row-1, 2); //String
                Object currColumn2 = model.getValueAt(row, 2); //String

                if (!prevColumn0.equals(currColumn0)
                ||  !prevColumn1.equals(currColumn1)
                ||  !prevColumn2.equals(currColumn2))
                {
                    c.setFont(c.getFont().deriveFont(Font.BOLD));
                    c.setForeground(Color.BLACK);
                }
            }
        }

        return c;
    }
};

From my understand it iterates over the cells that are currently displayed and then sets the renderer for each.

不要假设渲染顺序。每个单元格独立于其他单元格呈现。这就是为什么您不想依赖从上一个正在渲染的单元格设置的变量。

编辑:

更好的 getColumnClass(...) 实现看起来像:

@Override
public Class getColumnClass(int column)
{
    switch (column)
    {
        case 2: return Date.class;
        default: return String.class;
    }
}

在为 POJO 创建自定义 TableModel 时,您将使用上述方法。有关创建自定义模型的分步方法,请参阅 Row Table Model

对于从数据库获取数据时可能使用的更通用的实现,您可以使用:

@Override
public Class getColumnClass(int column)
{
    for (int row = 0; row < getRowCount(); row++)
    {
        Object o = getValueAt(row, column);

        if (o != null)
        {
            return o.getClass();
        }
    }

    return Object.class;
}

最好以独立的方式单独确定每个单元格的设置,而不使用在某个特定单元格坐标(例如第一个单元格中的第一个单元格)确定的变量(代码片段中的markRow)行)。

原因是prepareRenderer的调用方式没有确定。无法保证首先为第一行中的第一个单元格调用此方法,然后为第一行中的第二个单元格调用,依此类推。换句话说,不要指望 prepareRenderer 会按顺序或任何特定顺序被调用。

另请注意,应为每个单元正确初始化渲染器组件。在 the first version of your question 的代码片段中,起始 if(column<3) 中没有 else 部分,这意味着该组件可能已被任意初始化。如果有准备渲染器的决策树,请确保所有路径都按需要准备渲染器。