[英]Row filter doesn't work as expected on cell update events
在共享表模型示例中工作我意識到如果我們將行過濾器附加到表的行分類器,則此過濾器對單元更新事件沒有任何影響。 根據RowSorter API :
RowSorter
具體實現需要引用諸如TableModel
或ListModel
類的模型。 視圖類(例如JTable
和JList
)也將具有對模型的引用。 為避免排序依賴項,RowSorter
實現不應在模型上安裝偵聽器。 相反,視圖類將在模型更改時調用RowSorter
。 例如,如果在TableModel
更新了一行,則JTable
調用rowsUpdated
。 當模型更改時,視圖可能會調用以下任何方法:modelStructureChanged
,allRowsChanged
,rowsInserted
,rowsDeleted
和rowsUpdated
。
因此,據我理解這一段,單元格更新是行更新的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
,如果單元格值為null
或true
,則必須包含entry
(行)。 如果我編輯放置在第三列的單元格並將其值設置為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,bug報告和Oracle論壇后,我發現了一些有趣的東西。
我發現的第一件事是我們必須將sortsOnUpdate
屬性設置為true
,以便在調用rowsUpdated(...)時啟用通知鏈。 否則不會觸發RowSorterEvent ,並且視圖 (我們的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
事件時,它僅重新繪制與所涉及的行相關的區域,而不重新繪制與表的其余部分相關的區域,該區域保持不變。 假設我們編輯第一行,現在應該對其進行過濾。 然后其余的行應該向上移動一行到頂部但事實證明它們不是:只有第一行才能正確重新繪制以顯示第二行但表的其余部分仍然相同。 這實際上是一個錯誤,因為在這種特殊情況下,整個表需要重新繪制。 查看核心bug#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
* 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:
所以,這是使用SwingX
另一個原因(如果有些遺漏)。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.