java - 行过滤器在单元格更新事件中无法按预期工作

标签 java swing jtable rowfilter rowsorter

在共享表模型示例中工作时,我意识到如果我们将行过滤器附加到表的行排序器,则此过滤器不会对单元格更新事件产生任何影响。根据RowSorter API :

Concrete implementations of RowSorter need to reference a model such as TableModel or ListModel. The view classes, such as JTable and JList, 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 the RowSorter when the model changes. For example, if a row is updated in a TableModel JTable invokes rowsUpdated. When the model changes, the view may call into any of the following methods: modelStructureChanged, allRowsChanged, rowsInserted, rowsDeleted and 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,则必须包含 entry(行)或 true。如果我编辑位于第三列的单元格并将其值设置为 false,那么我希望该行从 View 中“消失”。但是,要完成此操作,我必须再次设置一个新过滤器,因为它似乎无法“自动”工作。

按如下方式将 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 论坛之后,我发现了一些有趣的东西。

1。将 DefaultRowSorter 的 sortsOnUpdate 属性设置为 true

我发现的第一件事是我们必须设置 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);

我们不必在表模型更新时重新应用过滤器。但是……

2。 JTable 组件处理 RowSorterEvent 通知时存在错误

当 JTable 实现 RowSorterListener 接口(interface)时,将自己订阅到行排序器作为监听器并处理 RowSorterEvents。重绘表格时出现错误。这些帖子中很好地描述了奇怪的行为:

简而言之:

当 JTable 处理 RowSorterEvent.TYPE.SORTED 事件时,它只重绘与涉及的行相关的区域,而不是表的其余部分,它保持原样。假设我们编辑了第一行,现在应该对其进行过滤。然后其余的行应该向上移动一行到顶部但事实证明它们不是:只有第一行将被正确地重新绘制以显示第二行但表格的其余部分仍然相同。这实际上是一个错误,因为在这种特殊情况下需要重新绘制整个表。参见 core bug # 6791934

作为解决方法,我们可以将新的 RowSorterListener 附加到 RowSorter 或覆盖 JTable 的 sorterChanged(...)如下所示,以便在我们的 table 上强制进行整个重绘(恕我直言,第二种方法是首选)。

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

3。 JXTable 组件有一个解决此错误的方法

属于 SwingX 的 JXTable 组件库和 JTable 的扩展没有这个问题,因为 SwingLabs 团队已经重写了 sorterChanged(...) mthod 来破解这个错误:

//----> 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 的另一个原因(如果缺少某些原因)。

关于java - 行过滤器在单元格更新事件中无法按预期工作,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/26408440/

相关文章:

java - 什么是堆栈图框

java - 无法在linux上安装open jdk 8 172版本

java - 使用biojava将文本文件读入JTable的特定列的步骤

java - 当我充电 myScrollPane.getViewport() 时,addTableModelListener 不执行任何操作

java - 无法使用 appium 进行 native 应用程序自动化在真实设备上执行任何操作

java.util.LinkedList.Node<E> 无法分配给 GWT Serialized?

java - 添加 JPanel 时 JScrollPane 不滚动

java - 动态生成Testng.xlm来运行测试

java - 为基本的 Java 矩形添加发光效果

java - 是否可以将数组的 ArrayList 添加到文件中?