JTable RowFilter 是如何工作的?
How does JTable RowFilter work?
我正在尝试为 JTable 创建行过滤器以限制 table 中显示的行数。
RowFilter 代码很简单。它将模型行号转换为视图行号(如果 table 已排序),然后检查视图行号是否小于要在 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;
}
};
问题是模型行号并不总是转换为合理的视图行号,因此过滤器中包含了太多行。为了演示 运行 下面的代码:
1) Select 从组合框中输入“1”,您将得到如下输出:
Change the Filter to: 1
m0 : v0
m1 : v0
m2 : v0
m3 : v0
m4 : v0
此输出告诉我所有模型行都被转换为视图行 0。由于 0 小于过滤器值 1,所有行都包含在过滤器中(这是错误的)。
所以这里的问题是为什么 convertRowIndexToView(modelRow)
没有按预期工作?
2) 现在从组合框中输入 select“2”,您将得到如下输出:
Change the Filter to: 2
m0 : v0
m1 : v1
m2 : v2
m3 : v3
m4 : v4
如您所见,模型行现在映射到正确的视图行,因此只有 2 行包含在正确的过滤器中。
3) 现在从组合框中输入 select“3”,您将得到如下输出:
Change the Filter to: 3
m0 : v0
m1 : v1
m2 : v-1
m3 : v-1
m4 : v-1
在这种情况下,最后 3 个模型行被转换为 -1,我认为这意味着该行当前在 table 中不可见,这是正确的。所以在这种情况下,所有 5 行再次包含在过滤器中,这是不正确的,因为我们只想要前 3 行。
所以这里的问题是如何重置过滤器以便所有模型行都映射到原始视图行?
我尝试使用:
((TableRowSorter) table.getRowSorter()).setRowFilter(null);
((TableRowSorter) table.getRowSorter()).setRowFilter(filter);
在重置过滤器之前清除过滤器,但这给出了与步骤 1 相同的结果。也就是说,现在所有模型行都映射到视图行 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.. 行,当您从组合框中 select 1 时,您会注意到在这两种情况下模型索引 4 都转换为视图索引 4 并且这两个输出夹在不正确的模型周围以查看转换???
编辑:
根据 MadProgrammers 的建议,我尝试了:
//((TableRowSorter) table.getRowSorter()).setRowFilter(filter);
TableRowSorter sorter = new TableRowSorter();
table.setRowSorter( sorter );
sorter.setRowFilter( filter );
sorter.sort();
现在我在 table.
中一无所获
根据@MadProgrammer 的提示,我想出了以下解决方案。
不仅需要更换 RowSorter,还需要保留排序键,以便 sort() 方法将 table 重置回其当前排序状态:
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();
}
});
}
}
因此,经过一些认真的测试和调试,我将 DefaultRowSorter
和 TableRowSorter
的代码复制到您的 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]
有趣的部分在最后,在 Change the Filter to: 1
和 Change the Filter to: 2
之间。您可以看到 initializeFilteredMapping
已将超出范围的模型索引设置为 -1
,但是当我们更改为 Change the Filter to: 2
时,这些相同的索引仍然设置,更改过滤器并未重置他们。
这似乎是一种保持 table 响应的设计选择,他们可能从未想过有人可能会尝试从过滤器内部访问视图,因为您假设正在使用模型数据...
如何解决...?
您可以构建一个 "proxy" TableModel
,但这排除了 table 可能被排序的可能性。
你可以写一个 "proxy" TableModel
,其中 "knew" 关于 JTable
的排序状态(可能通过 RowSorter
)并且可以采取行动作为确定可见行数的过滤器,但随着模型开始冒险进入视图世界,这正在进入浑水...
另一种选择是更改 setFilter
方法的工作方式并重置 modelToView
和 viewToModel
变量,但它们应该是 private
好吧,我们可以使用 DefaultRowSorter
中可用的 createModelToView
、createViewToModel
和 setModelToViewFromViewToModel
方法...但它们 private
到...
似乎任何处理对这些变量进行严重修改的有用方法都是 private
...我的生活故事...(拿起你的手电筒和干草叉,我们正在进行开发狩猎)
下一个选择,全部自己写...多么美妙的想法,期望这违背了 OO 的基本原则...
A "work around"(我非常非常轻率地使用这个术语),将使用反射并调用我们需要的方法...
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);
}
}
现在,我很确定你和我一样知道,这是一个可怕的、可怕的想法,随时可能被打破。它也可能非常非常低效,因为您每次都将索引重置为双向查找。
所以,答案是,不要从过滤器访问视图。
一般来说,我倾向于在更换 RowFilter
时更换 RowSorter
,因为它避免了此类问题:P
我正在尝试为 JTable 创建行过滤器以限制 table 中显示的行数。
RowFilter 代码很简单。它将模型行号转换为视图行号(如果 table 已排序),然后检查视图行号是否小于要在 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;
}
};
问题是模型行号并不总是转换为合理的视图行号,因此过滤器中包含了太多行。为了演示 运行 下面的代码:
1) Select 从组合框中输入“1”,您将得到如下输出:
Change the Filter to: 1
m0 : v0
m1 : v0
m2 : v0
m3 : v0
m4 : v0
此输出告诉我所有模型行都被转换为视图行 0。由于 0 小于过滤器值 1,所有行都包含在过滤器中(这是错误的)。
所以这里的问题是为什么 convertRowIndexToView(modelRow)
没有按预期工作?
2) 现在从组合框中输入 select“2”,您将得到如下输出:
Change the Filter to: 2
m0 : v0
m1 : v1
m2 : v2
m3 : v3
m4 : v4
如您所见,模型行现在映射到正确的视图行,因此只有 2 行包含在正确的过滤器中。
3) 现在从组合框中输入 select“3”,您将得到如下输出:
Change the Filter to: 3
m0 : v0
m1 : v1
m2 : v-1
m3 : v-1
m4 : v-1
在这种情况下,最后 3 个模型行被转换为 -1,我认为这意味着该行当前在 table 中不可见,这是正确的。所以在这种情况下,所有 5 行再次包含在过滤器中,这是不正确的,因为我们只想要前 3 行。
所以这里的问题是如何重置过滤器以便所有模型行都映射到原始视图行?
我尝试使用:
((TableRowSorter) table.getRowSorter()).setRowFilter(null);
((TableRowSorter) table.getRowSorter()).setRowFilter(filter);
在重置过滤器之前清除过滤器,但这给出了与步骤 1 相同的结果。也就是说,现在所有模型行都映射到视图行 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.. 行,当您从组合框中 select 1 时,您会注意到在这两种情况下模型索引 4 都转换为视图索引 4 并且这两个输出夹在不正确的模型周围以查看转换???
编辑:
根据 MadProgrammers 的建议,我尝试了:
//((TableRowSorter) table.getRowSorter()).setRowFilter(filter);
TableRowSorter sorter = new TableRowSorter();
table.setRowSorter( sorter );
sorter.setRowFilter( filter );
sorter.sort();
现在我在 table.
中一无所获根据@MadProgrammer 的提示,我想出了以下解决方案。
不仅需要更换 RowSorter,还需要保留排序键,以便 sort() 方法将 table 重置回其当前排序状态:
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();
}
});
}
}
因此,经过一些认真的测试和调试,我将 DefaultRowSorter
和 TableRowSorter
的代码复制到您的 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]
有趣的部分在最后,在 Change the Filter to: 1
和 Change the Filter to: 2
之间。您可以看到 initializeFilteredMapping
已将超出范围的模型索引设置为 -1
,但是当我们更改为 Change the Filter to: 2
时,这些相同的索引仍然设置,更改过滤器并未重置他们。
这似乎是一种保持 table 响应的设计选择,他们可能从未想过有人可能会尝试从过滤器内部访问视图,因为您假设正在使用模型数据...
如何解决...?
您可以构建一个 "proxy" TableModel
,但这排除了 table 可能被排序的可能性。
你可以写一个 "proxy" TableModel
,其中 "knew" 关于 JTable
的排序状态(可能通过 RowSorter
)并且可以采取行动作为确定可见行数的过滤器,但随着模型开始冒险进入视图世界,这正在进入浑水...
另一种选择是更改 setFilter
方法的工作方式并重置 modelToView
和 viewToModel
变量,但它们应该是 private
好吧,我们可以使用 DefaultRowSorter
中可用的 createModelToView
、createViewToModel
和 setModelToViewFromViewToModel
方法...但它们 private
到...
似乎任何处理对这些变量进行严重修改的有用方法都是 private
...我的生活故事...(拿起你的手电筒和干草叉,我们正在进行开发狩猎)
下一个选择,全部自己写...多么美妙的想法,期望这违背了 OO 的基本原则...
A "work around"(我非常非常轻率地使用这个术语),将使用反射并调用我们需要的方法...
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);
}
}
现在,我很确定你和我一样知道,这是一个可怕的、可怕的想法,随时可能被打破。它也可能非常非常低效,因为您每次都将索引重置为双向查找。
所以,答案是,不要从过滤器访问视图。
一般来说,我倾向于在更换 RowFilter
时更换 RowSorter
,因为它避免了此类问题:P