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:
Concrete implementations of
RowSorter
need to reference a model such asTableModel
orListModel
. The view classes, such asJTable
andJList
, will also have a reference to the model. To avoid ordering dependencies,RowSorter
implementations should not install a listener on the model. Instead the view class will call into theRowSorter
when the model changes. For example, if a row is updated in aTableModel
JTable
invokesrowsUpdated
. When the model changes, the view may call into any of the following methods:modelStructureChanged
,allRowsChanged
,rowsInserted
,rowsDeleted
androwsUpdated
.
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.
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
. 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. 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:
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:
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?
Here is a complete MCVE illustrating the problem.
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();
}
});
}
}