java - JTable RowFilter 是如何工作的?

标签 java swing jtable rowfilter

我尝试为 JTable 创建行过滤器以限制表中显示的行数。

RowFilter 代码很简单。它将模型行号转换为 View 行号(如果表格已排序),然后检查 View 行号是否小于表格中要显示的行数:

RowFilter<TableModel, Integer> filter = new RowFilter<TableModel, Integer>()
{
    @Override
    public boolean include(RowFilter.Entry<? extends TableModel, ? extends Integer> entry)
    {
        int modelRow = entry.getIdentifier();
        int viewRow = table.convertRowIndexToView(modelRow);

        return viewRow < numberOfRows;
    }

};

问题是模型行号并不总是转换为合理的 View 行号,因此过滤器中包含了太多行。为了演示运行下面的代码:

1) 从组合框中选择“1”,您将得到如下输出:

Change the Filter to: 1
m0 : v0
m1 : v0
m2 : v0
m3 : v0
m4 : v0

此输出告诉我所有模型行都被转换为 View 行 0。由于 0 小于过滤器值 1,所有行都包含在过滤器中(这是错误的)。

所以这里的问题是为什么 convertRowIndexToView(modelRow) 没有按预期工作?

2) 现在从组合框中选择“2”,您将得到如下输出:

Change the Filter to: 2
m0 : v0
m1 : v1
m2 : v2
m3 : v3
m4 : v4

如您所见,模型行现在映射到正确的 View 行,因此只有 2 行包含在正确的过滤器中。

3) 现在从组合框中选择“3”,您将得到如下输出:

Change the Filter to: 3
m0 : v0
m1 : v1
m2 : v-1
m3 : v-1
m4 : v-1

在这种情况下,最后 3 个模型行被转换为 -1,我认为这意味着该行当前在表中不可见,这是正确的。所以在这种情况下,所有 5 行再次包含在过滤器中,这是不正确的,因为我们只需要前 3 行。

所以这里的问题是如何重置过滤器,以便所有模型行都映射到原始 View 行?

我尝试使用:

((TableRowSorter) table.getRowSorter()).setRowFilter(null);
((TableRowSorter) table.getRowSorter()).setRowFilter(filter);

在重置过滤器之前清除过滤器,但这会产生与步骤 1 相同的结果。也就是说,现在所有模型行都映射到 View 行 0(因此仍然显示所有 5 行)。

测试代码如下:

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.table.*;

public class FilterSSCCE extends JPanel
{
    private JTable table;

    public FilterSSCCE()
    {
        setLayout( new BorderLayout() );

        JComboBox<Integer> comboBox = new JComboBox<Integer>();
        comboBox.addItem( new Integer(1) );
        comboBox.addItem( new Integer(2) );
        comboBox.addItem( new Integer(3) );
        comboBox.addItem( new Integer(4) );
        comboBox.addItem( new Integer(5) );
        comboBox.setSelectedIndex(4);

        comboBox.addActionListener( new ActionListener()
        {
            @Override
            public void actionPerformed(ActionEvent e)
            {
                //System.out.println( table.convertRowIndexToView(4) );
                Integer value = (Integer)comboBox.getSelectedItem();
                newFilter( value );
                //System.out.println( table.convertRowIndexToView(4) );
            }
        });
        add(comboBox, BorderLayout.NORTH);

        table = new JTable(5, 1);

        for (int i = 0; i < table.getRowCount(); i++)
            table.setValueAt(String.valueOf(i+1), i, 0);

        table.setAutoCreateRowSorter(true);
        JScrollPane scrollPane = new JScrollPane(table);
        add(scrollPane, BorderLayout.CENTER);
    }

    private void newFilter(int numberOfRows)
    {
        System.out.println("Change the Filter to: " + numberOfRows);

        RowFilter<TableModel, Integer> filter = new RowFilter<TableModel, Integer>()
        {
            @Override
            public boolean include(RowFilter.Entry<? extends TableModel, ? extends Integer> entry)
            {
                int modelRow = entry.getIdentifier();
                int viewRow = table.convertRowIndexToView(modelRow);

                System.out.println("m" + modelRow + " : v" + viewRow);

                return viewRow < numberOfRows;
            }

        };

        ((TableRowSorter) table.getRowSorter()).setRowFilter(filter);
    }

    private static void createAndShowGUI()
    {
        JPanel panel = new JPanel();

        JFrame frame = new JFrame("FilterSSCCE");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.add(new FilterSSCCE());
        frame.setLocationByPlatform( true );
        frame.pack();
        frame.setVisible( true );
    }

    public static void main(String[] args)
    {
        EventQueue.invokeLater(new Runnable()
        {
            public void run()
            {
                createAndShowGUI();
            }
        });
    }
}

知道如何创建行过滤器来显示前“n”行吗?

哦,是的,还有一点令人沮丧。如果您取消注释 actionPeformed() 方法中的两个 System.out.. 行,当您从组合框中选择 1 时,您会注意到在这两种情况下模型索引 4 都转换为 View 索引 4 并且这两个输出夹在中间查看转化的模型不正确???

编辑:

根据我尝试过的 MadProgrammers 建议:

//((TableRowSorter) table.getRowSorter()).setRowFilter(filter);
TableRowSorter sorter = new TableRowSorter();
table.setRowSorter( sorter );
sorter.setRowFilter( filter );
sorter.sort();

现在我在表中一无所获。

最佳答案

因此,经过一些认真的测试和调试,我将 DefaultRowSorterTableRowSorter 的代码复制到您的 FilterSSCCE 中,并添加了一些输出监控modelToView 字段,DefaultRowSorter#convertRowIndexToView 使用它在模型和 View 索引之间进行映射...

Change the Filter to: 1
createModelToView = [0, 0, 0, 0, 0]
m0 : v0
m1 : v0
m2 : v0
m3 : v0
m4 : v0
initializeFilteredMapping.1 = [0, 1, 2, 3, 4]
initializeFilteredMapping.2 = [0, 1, 2, 3, 4]
Change the Filter to: 5
m0 : v0
m1 : v1
m2 : v2
m3 : v3
m4 : v4
initializeFilteredMapping.1 = [0, 1, 2, 3, 4]
initializeFilteredMapping.2 = [0, 1, 2, 3, 4]
Change the Filter to: 1
m0 : v0
m1 : v1
m2 : v2
m3 : v3
m4 : v4
initializeFilteredMapping.1 = [0, -1, -1, -1, -1]
initializeFilteredMapping.2 = [0, -1, -1, -1, -1]
Change the Filter to: 2
m0 : v0
m1 : v-1
m2 : v-1
m3 : v-1
m4 : v-1
initializeFilteredMapping.1 = [0, 1, 2, 3, 4]
initializeFilteredMapping.2 = [0, 1, 2, 3, 4]

有趣的部分在最后,在 Change the Filter to: 1Change the Filter to: 2 之间。可以看到initializeFilteredMapping已经将超出范围的模型索引设置为-1,但是当我们更改为Change the Filter to: 2,那些相同的索引仍然设置,更改过滤器并没有重置它们。

这似乎是一种保持表格响应的设计选择,他们可能从未想过有人会尝试从过滤器内访问 View ,因为您假设正在使用模型数据...

如何绕过它...?

您可以构建一个“代理”TableModel,但这排除了对表进行排序的可能性。

您可以编写一个“代理”TableModel,它“知道”JTable 的排序状态(可能通过 RowSorter)并且它可以作为过滤器来确定可见的行数,但随着模型开始冒险进入 View 世界,这正在进入浑水......

另一种选择是改变 setFilter 方法的工作方式并重置 modelToViewviewToModel 变量,但它们是 private,因为它们应该是,好吧,我们可以使用 createModelToViewcreateViewToModelsetModelToViewFromViewToModel 方法在 DefaultRowSorter ...但它们私有(private) ...

似乎任何处理对这些变量进行严重修改的有用方法都是private...我的生活故事...(拿起你的手电筒和干草叉,我们继续开发者狩猎)

下一个选择,全部自己写......多么美妙的想法,期望这违背了 OO 的基本原则......

“解决方法”(我非常非常轻地使用了这个术语)是使用反射并只调用我们需要的方法...

public class TestRowSorter<M extends TableModel> extends TableRowSorter<M> {

    public TestRowSorter() {
    }

    public TestRowSorter(M model) {
        super(model);
    }

    public Method findMethod(String name, Class... lstTypes) {

        return findMethod(getClass(), name, lstTypes);

    }

    public Method findMethod(Class parent, String name, Class... lstTypes) {

        Method method = null;
        try {
            method = parent.getDeclaredMethod(name, lstTypes);
        } catch (NoSuchMethodException noSuchMethodException) {
            try {
                method = parent.getMethod(name, lstTypes);
            } catch (NoSuchMethodException nsm) {
                if (parent.getSuperclass() != null) {
                    method = findMethod(parent.getSuperclass(), name, lstTypes);
                }
            }
        }

        return method;

    }

    @Override
    public void setRowFilter(RowFilter<? super M, ? super Integer> filter) {

        try {
            Method method = findMethod("createModelToView", int.class);
            method.setAccessible(true);
            method.invoke(this, getModelWrapper().getRowCount());

            method = findMethod("createViewToModel", int.class);
            method.setAccessible(true);
            method.invoke(this, getModelWrapper().getRowCount());

            method = findMethod("setModelToViewFromViewToModel", boolean.class);
            method.setAccessible(true);
            method.invoke(this, true);
        } catch (SecurityException | IllegalAccessException | IllegalArgumentException | InvocationTargetException exp) {
            exp.printStackTrace();
        }

        super.setRowFilter(filter);
    }

}

现在,我很确定你和我一样知道,这是一个可怕的、可怕的想法,随时都可能被打破。它也可能非常非常低效,因为您每次都将索引重置为双向查找。

所以,答案是,不要从过滤器访问 View 。

一般来说,当我更换 RowFilter 时,我倾向于更换 RowSorter,因为它避免了这类问题:P

关于java - JTable RowFilter 是如何工作的?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/30448255/

相关文章:

java - 将 JTable 行从 mysql 填充到 Java 中的 JTextFields

java - 我可以测试使用 Mockito 自省(introspection)的代码吗?

java - 无法调整 GridBagLayout 中 JTextField 和 JScrollPane 的大小

java - 在获得焦点时开始在 JTable 中的单元格中进行编辑

java - Swing PaintComponent 无法正常工作

Java - 移动 JPanel 的位置

java - 有没有办法为 JTable 创建渐变背景?

java - 我无法多态地访问具体类的方法

java - 使用java当系统空闲一段时间后自动注销

java - Jersey - 来自 HTTP header 的编码(marshal)对象