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

时间:2018-06-08 16:12:54

标签: java sorting jtable multiple-columns tablerowsorter

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

当用户点击文件大小的标题时,它应该冻结状态为"下载"的所有行。到顶部,然后按文件大小列排序,如下所示:Sorted Table

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

  1. 调整列的大小需要某种类型的侦听器在第一个表中的列调整大小时调整第二个表的列的大小
  2. 排序需要某种侦听器,以便在第一个表排序时对第二个表进行排序
  3. 当状态值更改时,需要在表之间来回移动数据行
  4. 我认为这种方法是不可接受的,并想出了另一种方法:添加一个隐藏的位置列,其值为' 1'对于我想要冻结到顶部的任何行和一个' 2'对于所有其他状态并永久保持此列按升序排序。我能够使用具有多个SortKeys的TableRowSorter在初始加载时以这种方式对数据进行排序;但是,我无法在用户选择的列排序之前保持Position列排序,因为没有机制来预置SortKey。尝试添加另一个SortKey总是在现有的SortKey之后添加它。

    我首先尝试将RowSorterListener添加到TableRowSorter并首先在Position列上重新排序表,然后在sorterChanged()事件的用户选择列上重新排序,但这不起作用,因为它创建了无限循环,因为它每次与排序顺序混淆时,sorterChanged()事件都会被触发。

    接下来,我尝试将MouseListener添加到TableHeader,并根据用户单击的列手动对表进行排序。这一半工作但最终失败了,因为JTable内置的分拣机代码仍在激活。因此,当用户点击标题时,它会通过我的代码对其进行一次排序,然后通过JTable内部排序代码对其进行相反的排序。因此根本没有任何排序。除了完全禁用TableHeader之外,我无法找出消耗或阻止此内部事件的任何方法。这是不可接受的,因为它还会禁用用户调整列的大小并且不再显示方向箭头的能力。

    在网上进行一些搜索,我遇到了一个完美运行的解决方案(https://stackoverflow.com/a/37721594/6143124),但最终无法使用,因为在将来的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
    

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

    谢谢, SYD

1 个答案:

答案 0 :(得分:1)

通过创建自己的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并将其应用到表格标题中来解决了这个问题,但这与系统的外观和风格不符(Windows 10通常在标题的顶部中间显示排序箭头,而将其放置在我的自定义渲染器中的文本)。