简体   繁体   English

行筛选器在单元更新事件上无法正常工作

[英]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 as TableModel or ListModel . RowSorter具体实现需要引用诸如TableModelListModel类的模型。 The view classes, such as JTable and JList , will also have a reference to the model. 视图类(例如JTableJList )也将具有对模型的引用。 To avoid ordering dependencies, RowSorter implementations should not install a listener on the model. 为避免排序依赖项, RowSorter实现不应在模型上安装侦听器。 Instead the view class will call into the RowSorter when the model changes. 相反,视图类将在模型更改时调用RowSorter For example, if a row is updated in a TableModel JTable invokes rowsUpdated . 例如,如果在TableModel更新了一行,则JTable调用rowsUpdated When the model changes, the view may call into any of the following methods: modelStructureChanged , allRowsChanged , rowsInserted , rowsDeleted and rowsUpdated . 当模型更改时,视图可能会调用以下任何方法: modelStructureChangedallRowsChangedrowsInsertedrowsDeletedrowsUpdated

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 ,如果单元格值为nulltrue ,则必须包含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论坛后,我发现了一些有趣的东西。

1. Set DefaultRowSorter's sortsOnUpdate property to true 1.将DefaultRowSorter的sortsOnUpdate属性设置为true

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... 但...

2. There's a bug in JTable component processing the RowSorterEvent notification 2. JTable组件处理RowSorterEvent通知时出现错误

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()
        }
    }
};

3. JXTable component has a workaround to this bug 3. JXTable组件有一个解决此错误的方法

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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM