简体   繁体   English

JTable RowFilter如何工作?

[英]How does JTable RowFilter work?

I'm try to create a Row filter for a JTable to limit the number of rows displayed in the table. 我尝试为JTable创建一个Row过滤器,以限制表中显示的行数。

The RowFilter code is simple. RowFilter代码很简单。 It converts the model row number to the view row number (in case the table is sorted) and then checks if the view row number is less that the number of lines to be displayed in the table: 它将模型行号转换为视图行号(如果表已排序),然后检查视图行号是否小于要在表中显示的行数:

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

};

The problem is that the model row number is not always converted to a reasonable view row number so too many rows are being included in the filter. 问题是模型行号并不总是转换为合理的视图行号,因此过滤器中包含的行太多。 To demonstrate run the code below: 要演示运行以下代码:

1) Select "1" from the combo box and you will get output like: 1)从组合框中选择“1”,您将获得如下输出:

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

This output is telling me that all model rows are being converted to view row 0. Since 0 is less than the filter value of 1, all rows are included in the filter (which is wrong). 此输出告诉我所有模型行都被转换为视图行0.由于0小于过滤器值1,所有行都包含在过滤器中(这是错误的)。

So the question here is why is the convertRowIndexToView(modelRow) not working as expected? 所以这里的问题是为什么convertRowIndexToView(modelRow)没有按预期工作?

2) Now select "2" from the combo box and you will get output like: 2)现在从组合框中选择“2”,您将获得如下输出:

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

As you can see the model rows are now mapping to the proper view row, so only 2 row are included in the filter which is correct. 如您所见,模型行现在映射到正确的视图行,因此过滤器中只包含2行,这是正确的。

3) Now select "3" from the combo box and you will get output like: 3)现在从组合框中选择“3”,您将获得如下输出:

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

In this case the last 3 model rows are converted to -1, which I assume means the row is not currently visible in the table, which is correct. 在这种情况下,最后3个模型行转换为-1,我假设这意味着该行当前不可见,这是正确的。 So in this case all 5 rows are again included in the filter which is incorrect since we only want the first 3. 所以在这种情况下,所有5行都被包含在过滤器中,这是不正确的,因为我们只想要前3行。

So the question here is how to reset the filter so all model rows are mapped to the original view row? 那么这里的问题是如何重置过滤器,以便所有模型行都映射到原始视图行?

I tried to use: 我试着用:

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

to clear the filter, before resetting the filter but this gave the same results as step 1. That is, now all the model rows map to view row 0 (so all 5 rows are still displayed). 在重置过滤器之前清除过滤器但是这给出了与步骤1相同的结果。也就是说,现在所有模型行都映射到视图行0(因此仍然显示所有5行)。

Here is the test code: 这是测试代码:

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

Any idea how to create a row filter to display the first "n" rows? 知道如何创建行过滤器来显示第一个“n”行吗?

Oh yeah, one more frustrating point. 哦,是的,还有一个令人沮丧的观点。 If you uncomment the two System.out.. lines in the actionPeformed() method, when you select 1 from the combo box you will notice that in both cases the model index 4 is converted to view index 4 and these two outputs are sandwiched around the incorrect model to view conversions??? 如果取消注释actionPeformed()方法中的两个System.out ..行,当您从组合框中选择1时,您会注意到在两种情况下模型索引4都转换为视图索引4并且这两个输出被夹在中间查看转换的错误模型???

Edit: 编辑:

Based on MadProgrammers suggestion I tried: 基于MadProgrammers的建议,我试过:

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

Now I get nothing in the table. 现在我没有得到任何表格。

So, after some serious testing and debugging, I copied the code for DefaultRowSorter and TableRowSorter into your FilterSSCCE and added some output monitoring the modelToView field, which is used by DefaultRowSorter#convertRowIndexToView to map between the model and view indicies... 因此,经过一些严格的测试和调试后,我将DefaultRowSorterTableRowSorter的代码复制到您的FilterSSCCE并添加了一些监视modelToView字段的输出, DefaultRowSorter#convertRowIndexToView使用该字段在模型和视图标记之间进行映射...

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]

The interesting part is here at end, between Change the Filter to: 1 and Change the Filter to: 2 . 有趣的部分是在最后,在Change the Filter to: 1Change the Filter to: 2 You can see that initializeFilteredMapping has set the model indices that are out of range to -1 , but when we change to Change the Filter to: 2 , those same indices are still set, changing the filter has NOT reset them. 您可以看到initializeFilteredMapping已将超出范围的模型索引设置为-1 ,但是当我们更改为Change the Filter to: 2 ,仍然设置了相同的索引,更改过滤器没有重置它们。

This seems to be a design choice to keep the table responsive and they probably never thought some one might try and access the view from within the filter, as you're suppose to be using the model data... 这似乎是保持表响应的设计选择,他们可能从未想过某人可能尝试从过滤器中访问视图,因为您假设使用模型数据...

How to get around it...? 如何解决它......?

You could build a "proxy" TableModel , but that precludes the possibility that the table might be sorted. 您可以构建一个“代理” TableModel ,但这排除了表可能被排序的可能性。

You could write a "proxy" TableModel which "knew" about the sorted state of the JTable (probably via the RowSorter ) and which could act as the filter to determine the visible row count, but this is crossing into murky water as the model is starting to venture into the world of the view... 您可以编写一个“代理” TableModel ,它“知道” JTable的排序状态(可能通过RowSorter ),它可以作为过滤器来确定可见的行数,但是由于模型是开始冒险进入视野世界......

Another choice would be to change the way that the setFilter method works and reset the modelToView and viewToModel variables, but they are private , as they should be, okay, we could use the createModelToView , createViewToModel and setModelToViewFromViewToModel methods available in the DefaultRowSorter ... but they are private to... 另一种选择是改变setFilter方法的工作方式并重置modelToViewviewToModel变量,但它们是private ,因为它们应该是,好吧,我们可以使用DefaultRowSorter可用的createModelToViewcreateViewToModelsetModelToViewFromViewToModel方法......但他们是private的......

It would seem just about any useful method which deals with serious modification to these variables are private ...story of my life...(get your torches and pitchforks, we're going on a dev-hunt) 似乎任何有用的方法都可以处理对这些变量进行严格修改的private ......我的生活故事......(得到你的火炬和干草叉,我们正在开展一场狩猎活动)

Next choice, write it ALL yourself...what a wonderful idea, expect that goes against the basic principles of OO... 下一个选择,自己写一下......多么奇妙的想法,期望违背OO的基本原则......

A "work around" (and I use the term very, very lightly), would be to use reflection and just call the methods we need... “解决方法”(我使用非常非常轻松的术语)将使用反射并调用我们需要的方法...

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

}

Now, I'm pretty sure you know, as I do, this is a horrible, horrible idea which could break at any time. 现在,我很确定你知道,就像我一样,这是一个可怕的,可怕的想法,任何时候都可能破裂。 It's probably also very, very inefficient, as you're resetting the indexes to the bi-directional look up each time. 它可能也非常非常低效,因为您每次都要将索引重置为双向查找。

So, the answer, don't access the view from the filter. 所以,答案是,不要从过滤器访问视图。

Generally speaking, I tend to replace the RowSorter when ever I replace the RowFilter as it avoids these kind of issues :P 一般来说,当我更换RowFilter时,我倾向于替换RowSorter ,因为它避免了这些问题:P

Using the tips from @MadProgrammer I came up with the following solution. 使用@MadProgrammer的提示我想出了以下解决方案。

Not only does the RowSorter need to be replaced, you also need to keep the sort keys so the sort() method resets the table back to its current sort state: 不仅需要替换RowSorter,还需要保留排序键,以便sort()方法将表重置为当前的排序状态:

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)
            {
                Integer value = (Integer)comboBox.getSelectedItem();
                newFilter( value );
            }
        });
        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)
    {
        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;
            }

        };

        TableRowSorter oldSorter = (TableRowSorter)table.getRowSorter();
        TableRowSorter<TableModel> sorter = new TableRowSorter<TableModel>(table.getModel());
        table.setRowSorter( sorter );
        sorter.setRowFilter( filter );
        sorter.setSortKeys( oldSorter.getSortKeys() );
        sorter.sort();
    }

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM