【问题标题】:Cells in JTable change appearance after scrolling horizontallyJTable 中的单元格在水平滚动后改变外观
【发布时间】:2020-02-24 22:32:27
【问题描述】:

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

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

填充表格并垂直滚动正确设置粗体字体和纯字体但向右滚动然后返回前三列,粗体字体被搞砸了:有时为每个单元格设置,有时为错误的单元格设置,有时它改变了中间文本:

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

这就是表格的设置方式:

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

我该如何解决这个问题?

编辑:here复制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() );
    }
}

【问题讨论】:

  • (1-) 过去您曾被要求在每个问题上发布minimal reproducible example。你没有,所以我们无能为力。我猜问题出在你的 markRow 变量上。我不认为变量是必要的。基本逻辑应该是设置默认渲染,然后只有在满足粗体条件时才更改渲染。
  • 我在这里第二个 camickr...minimal reproducible example 是解决此类问题所需要的。 camickr 的猜测也很中肯。例如,当 column>=3 时,您没有正确配置渲染器。但这就是我们没有minimal reproducible example:猜测。
  • @TT。那我该如何“正确配置”渲染器呢?我添加了一个else 案例并将字体设置为Font.PlainColor.DARK_GRAY,这确实会改变其他列的颜色,但问题并没有解决。
  • 我在您的代码中看到的 else 不是 if( column &lt; 3 ) AFAICT 的 else 部分。另外,我怀疑您使用markRow 标记特定行是正确的。最好专门为每个单元格确定设置,不要指望 markRow 对另一个单元格是正确的。
  • @TT。我在我的代码中为if (column&lt;3) 添加了一个“else”来测试它,但我还没有编辑 MRE 以将它添加到那里。我必须检查前 3 列的内容,我只在查看第一列时才这样做。据我了解,它遍历当前显示的单元格,然后为每个单元格设置渲染器。我不想对每个单元格进行整个大检查 if(column==0),所以我需要一些可以轻松检查同一行的第 1 列和第 2 列的东西,这是我的 markRow 布尔值。

标签: java swing jtable tablecellrenderer


【解决方案1】:

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

但是向右滚动然后回到前三列,粗体字乱了:

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

您是否看过我关于在测试粗体条件之前删除“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;
    }
};

据我了解,它会遍历当前显示的单元格,然后为每个单元格设置渲染器。

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

编辑:

更好的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;
}

【讨论】:

  • 我不知道如何设置默认渲染(除非使用prepareRenderer,我已经这样做了)。我已经设法在 TT. 的帮助下修复了代码,只是等待他写一个答案,所以我可以接受它。顺便说一句,无需每次都获取模型,直接调用getValueAt 也可以。您也不要使用您的代码设置不符合条件的单元格的外观。 From my understand 的确切含义是:“据我所知”。
  • 无需获取模型,直接调用 getValueAt 也可以 - 仅当用户从不重新排序列时。同样,我给出的答案促进了良好的长期编码实践,以防止在您甚至不知道自己可能有问题时出现问题。基于提供的 MRE,最好从模型中获取数据。 也不要用你的代码设置不符合条件的单元格的外观 - 你是对的。 “粗体”属性压倒了黑色/灰色,所以我认为没有必要。重新添加了该行。
  • (1) 你让我摆脱markRow,就是这样,没有解释为什么或如何更改代码。 TT 通过提供解释和回答我的问题帮助我修复了我的代码。 (2) 我没有接受你的回答,因为你在我已经在别人的帮助下找到了我的问题的解决方案之后发布了它(与这次相同)。在您看来,这可能是最好的答案,但它甚至没有按我需要的方式工作(查看我昨天发布的评论)。 (3) 上次我已经告诉过你,我不支持重新排序列,并且永远不会支持这个应用程序 - 这没有改变。
  • 另外,请注意我没有重置字体,因为 prepareRenderer() 方法会这样做。实际上 prepareRenderer 也应该重置前景和背景,但这是一个众所周知的错误。
  • 查看我昨天发布的评论 - 再说一次,我们不是在这里为您提供完整的解决方案,只是为您指明正确的方向。在提供的一小段代码中,我给您的建议(针对该特定问题的陈述问题)有效,因为所有列都包含数据。它并非旨在解决所有未来的问题。答案的要点是向您展示如何在“行”而不是“列”级别进行渲染,这就是问题所在。我不能教你使用 JTable 的所有方面都是几行代码。
【解决方案2】:

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

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

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

【讨论】:

    猜你喜欢
    • 2012-07-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-07-30
    • 1970-01-01
    • 1970-01-01
    • 2011-01-28
    相关资源
    最近更新 更多