How does JTable RowFilter work?

I am trying to create a Row filter for JTable to limit the number of rows displayed in a table.

RowFilter code is simple. It converts the line number of the model to the line number of the view (in the case of sorting the table), and then checks if the number of lines of the view is less, 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 line number of the model is not always converted to a reasonable viewing line number, so too many lines are included in the filter. To demonstrate running the code below:

1) Select "1" in the combo box and you will get the result as follows:

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

This conclusion tells me that all lines of the model are converted to view line 0. Since 0 is less than filter 1, all lines are included in the filter (which is wrong).

So the question is, why is convertRowIndexToView(modelRow) not working as expected?

2) Now select "2" from the combo box and you will get the result, for example:

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

As you can see, now the model lines are now displayed in the correct line of the view, so only two lines are included in the filter.

3) Now select β€œ3” in the combo box and you will get the result, for example:

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

In this case, the last 3 rows of the model are converted to -1, which, I believe, means that the row is not currently visible in the table, which is correct. Thus, in this case, all 5 lines are again included in the filter, which is incorrect, since we want only the first 3.

So the question is, how to reset the filter so that all model lines are displayed in the original view line?

I tried using:

 ((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 in step 1. That is, now all the lines of the model display the line of line 0 (so that all 5 lines are still displayed).

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 on creating a line filter to display the first "n" lines?

Oh yes, another frustrating moment. If you uncomment the two lines of System.out .. in the actionPeformed () method, when you select 1 from the combo box, you will notice that in both cases the model 4 index is converted to index 4, and these two outputs are clamped around the wrong model to view conversions.

Edit:

Based on the suggestion of MadProgrammers, I tried:

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

Now I am not getting anything in the table.

+7
java swing jtable rowfilter
source share
2 answers

Using advice from @MadProgrammer, I came up with the following solution.

It is necessary to replace not only RowSorter, but also save the sort keys so that the sort () method dumps the table back to the current sort state:

 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(); } }); } } 
+3
source share

So, after serious testing and debugging, I copied the code for DefaultRowSorter and TableRowSorter to your FilterSSCCE and added some output controlling the modelToView field, which is used by DefaultRowSorter#convertRowIndexToView for matching between model and 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] 

The interesting part here is at the end, between Change the Filter to: 1 and Change the Filter to: 2 . You can see that initializeFilteredMapping set the model indices out of range to -1 , but when we go to Change the Filter to: 2 , the same indices are still set, changing the filter has NOT reset them.

This seems to be a design choice to maintain the responsiveness of the table, and they probably never thought that someone could try and access the view from the filter, since you are assuming to use model data ...

How to get around this ??

You can create a "proxy" TableModel , but this eliminates the ability to sort the table.

You can write a β€œproxy” TableModel that β€œknew” about the sorted state of JTable (possibly through RowSorter ) and which could act as a filter to determine the visible number of rows, but this goes into murky water as the model begins to risk into the world of views. ..

Another option would be to change the way the setFilter method setFilter and the reset variables modelToView and viewToModel , but they are private , as it should be, well, we could use createModelToView , createViewToModel and setModelToViewFromViewToModel , available in DefaultRowSorter ... but they are private to ...

It would seem that some useful method that concerns a serious modification of these variables is private ... the story of my life ... (get torches and pitchforks, we will go in search)

Next choice, write it EVERYTHING ... what a great idea, expect this to run counter to the basic principles of OO ...

β€œWork” (and I use this term very, very easy), will 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 sure you know, like me, this is a terrible, terrible idea that can break at any time. This is probably also very, very inefficient, as you drop indexes on bidirectional search each time.

So, the answer is, do not refer to the view from the filter.

Generally speaking, I try to replace RowSorter when I ever replace RowFilter as it avoids such problems: P

+5
source share

All Articles