I'm try to create a Row filter for a JTable to limit the number of rows displayed in the table.
The RowFilter code is simple. 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:
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).
So the question here is why is the convertRowIndexToView(modelRow)
not working as expected?
2) Now select "2" from the combo box and you will get output like:
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.
3) Now select "3" from the combo box and you will get output like:
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. So in this case all 5 rows are again included in the filter which is incorrect since we only want the first 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).
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?
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???
Edit:
Based on MadProgrammers suggestion I tried:
//((TableRowSorter) table.getRowSorter()).setRowFilter(filter);
TableRowSorter sorter = new TableRowSorter();
table.setRowSorter( sorter );
sorter.setRowFilter( filter );
sorter.sort();
Now I get nothing in the table.
TableRowSorter
, applying that to theJTable
setting therowFilter
to that sorter and then callingsort
on theTableRowSorter
... :P – Outpour...previously filtered data set
yes and I guess the question is why? I guess most filters are based on the actual data in the TableModel, so this in not a problem as the mapping of the indexes will be redone after the filtering?I ended up creating...
- didn't work for me. Guess I did something else wrong? – ToughmindedTableRowSorter<TableModel> sorter = new TableRowSorter<>(table.getModel());
;) – OutpourDefaultRowSorter
andTableRowSorter
code into some test code, I monitored the changes to themodelToView
, which is used by theDefaultRowSorter#convertRowIndexToView
, it's using the cached data from the previous filter, meaning that the rows the were previously filtered out are-1
. It would seem that you're not suppose to be looking at the view when doing the filtering... – OutpourIt would seem that you're not suppose to be looking at the view when doing the filtering
- that would make sense. Using your suggestion, I came up with a solution to do the filtering and maintain the sort order. – Toughminded