在共享表模型示例中工作,我意识到如果我们将行过滤器附加到表的行排序器,则该过滤器不会对单元格更新事件产生任何影响。根据RowSorter API:
的具体实现需要引用这样的模型 如RowSorter
或TableModel
。视图类,例如ListModel
和JTable
,也会有模型的参考。为了避免订购 依赖项,JList
实现不应安装侦听器 在模型上。相反,视图类将在以下情况下调用RowSorter
: 模型发生变化。例如,如果在RowSorter
中更新了一行TableModel
调用JTable
。当模型发生变化时,视图可能会调用 进入以下任何方法:rowsUpdated
,modelStructureChanged
、allRowsChanged
、rowsInserted
和rowsDeleted
。rowsUpdated
据我理解本段,单元格更新是行更新的特殊情况,因此应调用
rowsUpdated
并相应地进行行过滤。
为了说明我的意思,请考虑这个简单的过滤器:
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;
}
});
}
此处,第三列预计为
Boolean
,如果单元格值为 entry
或 null
,则必须包含 true
(行)。如果我编辑位于第三列的单元格并将其值设置为 false
那么我希望这一行只是从视图中“消失”。然而,为了完成此任务,我必须再次设置一个新的过滤器,因为它似乎无法“自动”工作。
将
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);
}
}
});
正如我所说,如果我使用此重置过滤器
TableModelListener
,那么它会按预期工作:
model.addTableModelListener(new TableModelListener() {
@Override
public void tableChanged(TableModelEvent e) {
if (e.getType() == TableModelEvent.UPDATE) {
applyFilter();
}
}
});
问题:这是一个错误/实现问题吗?或者我误解了API?
这是一个完整的 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();
}
});
}
}
在做了一些研究并阅读 API、错误报告和 Oracle 论坛之后,我发现了一些有趣的事情。
sortsOnUpdate
属性设置为 true
,以便在调用 rowsUpdated(...) 时启用通知链。否则,不会触发 RowSorterEvent,并且 view(我们的 JTable)不会意识到发生了某些事情,也不会相应地重新绘制。所以做这个小小的改变:
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);
我们不必在表模型更新时重新应用过滤器。但是...
当 JTable 实现
RowSorterListener
接口时,它会将自身订阅到行排序器作为侦听器并进行处理 RowSorterEvents
。重新绘制桌子时出现错误。这些帖子很好地描述了奇怪的行为:
简而言之:
当 JTable 处理
RowSorterEvent.TYPE.SORTED
事件时,它仅重新绘制与涉及的行相关的区域,而不重新绘制表的其余部分,表的其余部分保持原样。假设我们编辑第一行,现在应该对其进行过滤。然后,其余的行应该向上移动一行到顶部,但事实证明它们不是:只有第一行会被正确地重新绘制以显示第二行,但表的其余部分仍然相同。这实际上是一个错误,因为在这种特殊情况下整个表格需要重新绘制。请参阅 核心错误 # 6791934
作为解决方法,我们可以将新的
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();
}
}
});
或者
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()
}
}
};
作为 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
* https://bugs.java.com/bugdatabase/view_bug?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:
所以,这是使用
SwingX
的又一个原因(如果缺少)。