Java JTable 通过始终对固定列进行排序并允许用户对任何辅助列进行排序来将行冻结到顶部

Java JTable freeze rows to top by always sorting fixed column and allowing user to sort any secondary column

我正在使用显示多个 sortable 列的 JTable。我需要将包含特定状态的任何行冻结到列表的顶部,而不管用户如何对列表的其余部分进行排序。例如,使用以下未排序的列表:Unsorted Table

当用户点击文件大小的 header 时,它应该将状态为 "Downloading" 的任何行冻结到顶部,然后按文件大小列排序,如下所示:Sorted Table

可以通过简单地使用两个 JTables 来解决这个问题,但是这样做看起来很丑陋并且提出了自己的挑战:

  1. 调整列的大小需要某种侦听器来调整第二个列的大小table 当第一个列中的列被调整大小时
  2. 排序需要某种侦听器在第一个排序时对第二个进行排序table
  3. 随着 Status 值的变化,需要不断地在 table 秒之间来回移动数据行

我决定不接受这种方法table,并想出了另一种方法:添加一个隐藏的 Position 列,该列的值为 '1' 用于我想冻结到顶部的任何行和 '2'对于所有其他状态,并永久保持此列按升序排序。我能够使用具有多个 SortKey 的 TableRowSorter 在初始加载时以这种方式对数据进行排序;但是,我无法使 Position 列排序发生在 user-selected 列排序之前,因为没有任何机制可以在 SortKey 之前添加。尝试添加另一个 SortKey 总是在现有的 SortKey 之后添加它。

我首先尝试将 RowSorterListener 添加到 TableRowSorter,然后 re-sorting 首先在 Position 列上添加 table,然后在 sorterChanged() 事件的 user-selected 列上添加,但这确实不起作用,因为它创建了一个无限循环,因为每次排序顺序出现混乱时都会触发 sorterChanged() 事件。

接下来,我尝试将 MouseListener 添加到 TableHeader 并根据用户单击的任何列手动对 table 进行排序。这一半工作但最终失败了,因为 JTable built-in 排序程序代码仍然会触发。因此,当用户单击 header 时,它会通过我的代码对其进行一次排序,然后通过 JTable 内部排序代码对其进行反向排序。从而导致根本没有排序。除了完全禁用 TableHeader 之外,我想不出任何方法来使用或阻止此内部事件。这是 unacceptable,因为它还禁用了用户调整列大小的功能,并且方向箭头不再显示。

在网上搜索了一下,我遇到了一个功能完美的解决方案(),但最终无法使用,因为它在 Java 的未来版本中将不被支持,并打印警告详细信息Java 9:

WARNING: An illegal reflective access operation has occurred 
WARNING: Illegal reflective access by pkg.PredefinedRowSorter to field javax.swing.DefaultRowSorter.sortKeys WARNING: Please consider reporting this to the maintainers of pkg.PredefinedRowSorter 
WARNING: Use --illegal-access=warn to enable warnings of further illegal reflective access operations
WARNING: All illegal access operations will be denied in a future release

为了一周更好的公园,我一直被这个难住了,非常感谢任何帮助或指导。

谢谢, 西德

我能够通过创建自己的 TableRowSorter 并覆盖 toggleSortOrder() 方法来实现预期的结果。

import java.util.ArrayList;
import java.util.List;

import javax.swing.SortOrder;
import javax.swing.table.TableModel;
import javax.swing.table.TableRowSorter;

public class PredefinedTableRowSorter<M extends TableModel> extends TableRowSorter<TableModel>
{
    public PredefinedTableRowSorter (TableModel model)
    {
        super(model);
    }

    @Override
    public void toggleSortOrder (int column)
    {
        List<SortKey> newSortKeys = new ArrayList<>();
        newSortKeys.add(new SortKey(0, SortOrder.ASCENDING));

        SortKey currentKey = getSortKey(column);
        SortOrder sortOrder = SortOrder.ASCENDING;

        if (currentKey != null) {
            sortOrder = currentKey.getSortOrder() == SortOrder.ASCENDING ? SortOrder.DESCENDING : SortOrder.ASCENDING;
        }

        currentKey = new SortKey(column, sortOrder);
        newSortKeys.add(currentKey);

        super.setSortKeys(newSortKeys);

        super.sort();
    }

    private SortKey getSortKey (int column)
    {
        for (SortKey key : super.getSortKeys()) {
            if (key.getColumn() == column) {
                return key;
            }
        }

        return null;
    }
}

此解决方案出现了一个新问题:排序箭头图标仅出现在已排序的第一列上。我通过编写自定义 DefaultTableCellRenderer 并根据需要将其应用于 table headers 来克服这个问题,但它与系统外观不匹配(Windows 10 通常显示排序箭头header 的顶部中间,而在我的自定义渲染器中它被放置在文本之后)。