[英]Row filter doesn't work as expected on cell update events
Working in a shared table model example I realized that if we attach a row filter to a table's row sorter this filter doesn't have any effect on cell update events. 在共享表模型示例中工作我意识到如果我们将行过滤器附加到表的行分类器,则此过滤器对单元更新事件没有任何影响。 According to RowSorter API : 根据RowSorter API :
Concrete implementations of
RowSorter
need to reference a model such asTableModel
orListModel
.RowSorter
具体实现需要引用诸如TableModel
或ListModel
类的模型。 The view classes, such asJTable
andJList
, will also have a reference to the model. 视图类(例如JTable
和JList
)也将具有对模型的引用。 To avoid ordering dependencies,RowSorter
implementations should not install a listener on the model. 为避免排序依赖项,RowSorter
实现不应在模型上安装侦听器。 Instead the view class will call into theRowSorter
when the model changes. 相反,视图类将在模型更改时调用RowSorter
。 For example, if a row is updated in aTableModel
JTable
invokesrowsUpdated
. 例如,如果在TableModel
更新了一行,则JTable
调用rowsUpdated
。 When the model changes, the view may call into any of the following methods:modelStructureChanged
,allRowsChanged
,rowsInserted
,rowsDeleted
androwsUpdated
. 当模型更改时,视图可能会调用以下任何方法:modelStructureChanged
,allRowsChanged
,rowsInserted
,rowsDeleted
和rowsUpdated
。
So as I understand this paragraph, a cell update is a particular case of row update and as such rowsUpdated
should be called and row filtered accordingly. 因此,据我理解这一段,单元格更新是行更新的rowsUpdated
应该调用rowsUpdated
并相应地对行进行过滤。
To illustrate what I'm saying, please consider this simple filter: 为了说明我在说什么,请考虑这个简单的过滤器:
private void applyFilter() {
DefaultRowSorter sorter = (DefaultRowSorter)table.getRowSorter();
sorter.setRowFilter(new RowFilter() {
@Override
public boolean include(RowFilter.Entry entry) {
Boolean value = (Boolean)entry.getValue(2);
return value == null || value;
}
});
}
Here the third column is expected to be a Boolean
and entry
(row) has to be included if the cell value is either null
or true
. 这里第三列应该是一个Boolean
,如果单元格值为null
或true
,则必须包含entry
(行)。 If I edit a cell placed at third column and set its value to false
then I'd expect this row just "disappear" from the view. 如果我编辑放置在第三列的单元格并将其值设置为false
那么我希望此行只是从视图中“消失”。 However, to accomplish this I have to set a new filter again because it doesn't seem to work "automatically". 但是,为了实现这一点,我必须再次设置一个新的过滤器,因为它似乎不能“自动”工作。
Attaching a TableModelListener
to the model as follows, I can see the update event on cell edits: 将TableModelListener
附加到模型,如下所示,我可以在单元格编辑上看到更新事件:
model.addTableModelListener(new TableModelListener() {
@Override
public void tableChanged(TableModelEvent e) {
if (e.getType() == TableModelEvent.UPDATE) {
int row = e.getLastRow();
int column = e.getColumn();
Object value = ((TableModel)e.getSource()).getValueAt(row, column);
String text = String.format("Update event. Row: %1s Column: %2s Value: %3s", row, column, value);
System.out.println(text);
}
}
});
As I've said, if I reset the filter using this TableModelListener
then it works as expected: 正如我所说,如果我使用这个TableModelListener
重置过滤器,那么它按预期工作:
model.addTableModelListener(new TableModelListener() {
@Override
public void tableChanged(TableModelEvent e) {
if (e.getType() == TableModelEvent.UPDATE) {
applyFilter();
}
}
});
Question: is this a bug/implementation issue? 问题:这是一个错误/实施问题吗? Or I'm misunderstanding the API? 或者我误解了API?
Here is a complete MCVE illustrating the problem. 这是一个完整的MCVE来说明问题。
import java.awt.BorderLayout;
import javax.swing.BorderFactory;
import javax.swing.DefaultRowSorter;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.RowFilter;
import javax.swing.SwingUtilities;
import javax.swing.event.TableModelEvent;
import javax.swing.event.TableModelListener;
import javax.swing.table.DefaultTableModel;
import javax.swing.table.TableModel;
public class Demo {
private JTable table;
private void createAndShowGUI() {
DefaultTableModel model = new DefaultTableModel(5, 3) {
@Override
public boolean isCellEditable(int row, int column) {
return column == 2;
}
@Override
public Class<?> getColumnClass(int columnIndex) {
return columnIndex == 2 ? Boolean.class : super.getColumnClass(columnIndex);
}
};
model.addTableModelListener(new TableModelListener() {
@Override
public void tableChanged(TableModelEvent e) {
if (e.getType() == TableModelEvent.UPDATE) {
int row = e.getLastRow();
int column = e.getColumn();
Object value = ((TableModel)e.getSource()).getValueAt(row, column);
String text = String.format("Update event. Row: %1s Column: %2s Value: %3s", row, column, value);
System.out.println(text);
// applyFilter(); un-comment this line to make it work
}
}
});
table = new JTable(model);
table.setAutoCreateRowSorter(true);
applyFilter();
JPanel content = new JPanel(new BorderLayout());
content.setBorder(BorderFactory.createEmptyBorder(8,8,8,8));
content.add(new JScrollPane(table));
JFrame frame = new JFrame("Demo");
frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
frame.add(content);
frame.pack();
frame.setLocationByPlatform(true);
frame.setVisible(true);
}
private void applyFilter() {
DefaultRowSorter sorter = (DefaultRowSorter)table.getRowSorter();
sorter.setRowFilter(new RowFilter() {
@Override
public boolean include(RowFilter.Entry entry) {
Boolean value = (Boolean)entry.getValue(2);
return value == null || value;
}
});
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
new Demo().createAndShowGUI();
}
});
}
}
Well, after doing some research and reading the API, bugs reports and Oracle forum I've found some interesting things. 好吧,在做了一些研究和阅读API,bug报告和Oracle论坛后,我发现了一些有趣的东西。
The first thing I found is we have to set sortsOnUpdate
property to true
in order to enable the notification chain when rowsUpdated(...) is called. 我发现的第一件事是我们必须将sortsOnUpdate
属性设置为true
,以便在调用rowsUpdated(...)时启用通知链。 Otherwise no RowSorterEvent will be fired and the view (our JTable) won't be aware that something happened and won't repaint accordingly. 否则不会触发RowSorterEvent ,并且视图 (我们的JTable)将不会意识到发生了什么事情并且不会相应地重新绘制。 So making this little change: 所以做这个小小的改变:
DefaultRowSorter sorter = (DefaultRowSorter)table.getRowSorter();
sorter.setRowFilter(new RowFilter() {
@Override
public boolean include(RowFilter.Entry entry) {
Boolean value = (Boolean)entry.getValue(2);
return value == null || value;
}
});
sorter.setSortsOnUpdates(true);
We won't have to re-apply the filter on a table model update. 我们不必在表模型更新上重新应用过滤器。 But... 但...
While JTable implements RowSorterListener
interface, subscribes itself to the row sorter as a listener and process RowSorterEvents
. 当JTable实现RowSorterListener
接口时,将其自身订阅为行分类器作为侦听器并处理RowSorterEvents
。 there's a bug repainting the table. 重新粉刷桌子有一个错误。 The odd behavior is well described in these posts: 奇怪的行为在这些帖子中有详细描述:
In a nutshell: 简而言之:
When a RowSorterEvent.TYPE.SORTED
event is processed by JTable, it repaints only the area related to the involded rows but not the rest of the table, which remains as it was. 当JTable处理RowSorterEvent.TYPE.SORTED
事件时,它仅重新绘制与所涉及的行相关的区域,而不重新绘制与表的其余部分相关的区域,该区域保持不变。 Let's say we edit the first row and it should be filtered now. 假设我们编辑第一行,现在应该对其进行过滤。 Then the rest of the rows should be shifted up one row to top but it turns out they are not: only the first row will be correctly repainted to show the second row but the rest of the table still the same. 然后其余的行应该向上移动一行到顶部但事实证明它们不是:只有第一行才能正确重新绘制以显示第二行但表的其余部分仍然相同。 This is in fact a bug because in this particular case the whole table needs to be repainted. 这实际上是一个错误,因为在这种特殊情况下,整个表需要重新绘制。 See core bug # 6791934 查看核心bug#6791934
As a workaround we could either attach a new RowSorterListener
to the RowSorter
or override JTable's sorterChanged(...) as follows in order to force a whole repaint on our table (IMHO the second method is preferred). 作为一种解决方法,我们可以将新的RowSorterListener
附加到RowSorter
或覆盖JTable的sorterChanged(...) ,如下所示,以强制在我们的表上进行整个重绘(恕我直言,第二种方法是首选)。
DefaultRowSorter sorter = (DefaultRowSorter)table.getRowSorter();
...
sorter.addRowSorterListener(new RowSorterListener() {
@Override
public void sorterChanged(RowSorterEvent e) {
if (e.getType() == RowSorterEvent.Type.SORTED) {
// We need to call both revalidate() and repaint()
table.revalidate();
table.repaint();
}
}
});
Or 要么
JTable table = new JTable(tableModel) {
@Override
public void sorterChanged(RowSorterEvent e) {
super.sorterChanged(e);
if (e.getType() == RowSorterEvent.Type.SORTED) {
resizeAndRepaint(); // this protected method calls both revalidate() and repaint()
}
}
};
The JXTable component that is part of SwingX library and extends from JTable doesn't have this problem because SwingLabs team has overwrote sorterChanged(...)
mthod as follows to hack around this bug: 作为SwingX库的一部分并从JTable扩展的JXTable组件没有这个问题,因为SwingLabs团队已经覆盖了sorterChanged(...)
方法,如下所示来解决这个问题:
//----> start hack around core issue 6791934:
// table not updated correctly after updating model
// while having a sorter with filter.
/**
* Overridden to hack around core bug
* http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6791934
*
*/
@Override
public void sorterChanged(RowSorterEvent e) {
super.sorterChanged(e);
postprocessSorterChanged(e);
}
/** flag to indicate if forced revalidate is needed. */
protected boolean forceRevalidate;
/** flag to indicate if a sortOrderChanged has happened between pre- and postProcessModelChange. */
protected boolean filteredRowCountChanged;
/**
* Hack around core issue 6791934: sets flags to force revalidate if appropriate.
* Called before processing the event.
* @param e the TableModelEvent received from the model
*/
protected void preprocessModelChange(TableModelEvent e) {
forceRevalidate = getSortsOnUpdates() && getRowFilter() != null && isUpdate(e) ;
}
/**
* Hack around core issue 6791934: forces a revalidate if appropriate and resets
* internal flags.
* Called after processing the event.
* @param e the TableModelEvent received from the model
*/
protected void postprocessModelChange(TableModelEvent e) {
if (forceRevalidate && filteredRowCountChanged) {
resizeAndRepaint();
}
filteredRowCountChanged = false;
forceRevalidate = false;
}
/**
* Hack around core issue 6791934: sets the sorter changed flag if appropriate.
* Called after processing the event.
* @param e the sorter event received from the sorter
*/
protected void postprocessSorterChanged(RowSorterEvent e) {
filteredRowCountChanged = false;
if (forceRevalidate && e.getType() == RowSorterEvent.Type.SORTED) {
filteredRowCountChanged = e.getPreviousRowCount() != getRowCount();
}
}
//----> end hack around core issue 6791934:
So, this is one more reason (if some missing) to use SwingX
. 所以,这是使用SwingX
另一个原因(如果有些遗漏)。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.