Java JTable закрепляет строки вверху, всегда сортируя фиксированный столбец и позволяя пользователю сортировать любой вторичный столбец.

Я использую JTable, который отображает несколько сортируемых столбцов. Мне нужно, чтобы все строки, содержащие определенный статус, были заморожены в верхней части списка независимо от того, как пользователь отсортировал остальную часть списка. Например, используя следующий несортированный список: Несортированная таблица

Когда пользователь щелкает заголовок Размер файла, все строки со статусом «Загрузка» должны закрепляться вверху, а затем сортироваться по столбцу Размер файла, как показано здесь: Отсортированная таблица

Это можно решить, просто используя два JTable, но это выглядит некрасиво и создает свои проблемы:

  1. Изменение размера столбца требует, чтобы какой-то прослушиватель менял размер столбца второй таблицы, когда изменяется размер столбца в первой.
  2. Для сортировки требуется какой-то прослушиватель для сортировки второй таблицы при сортировке первой.
  3. Необходимо постоянно перемещать строки данных между таблицами при изменении значения состояния.

Я решил, что этот подход неприемлем, и придумал другой подход: добавить скрытый столбец «Позиция» со значением «1» для любых строк, которые я хочу закрепить вверху, и «2» для всех других статусов и постоянно держать этот столбец отсортированным. в порядке возрастания. Я смог использовать TableRowSorter с несколькими SortKeys для сортировки данных таким образом при начальной загрузке; однако я не могу сохранить сортировку столбца Position до сортировки столбца, выбранного пользователем, поскольку нет механизма для добавления 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

Я был в тупике на этом для лучшего парка недели и был бы очень признателен за любую помощь или руководство.

Спасибо, Сид


person Syd    schedule 08.06.2018    source источник
comment
Мало того, что Field field = DefaultRowSorter.class.getDeclaredField( "sortKeys" ); потерпит неудачу из-за модульной инкапсуляции Java 9+, это всегда был сомнительный взлом, обреченный на окончательную неудачу. Частное состояние является частным по той причине, что будущие версии Java могут свободно изменять частную реализацию в любое время.   -  person VGR    schedule 08.06.2018


Ответы (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 обычно показывает стрелки сортировки в верхней середине заголовка, тогда как он помещается после текст в моем пользовательском рендерере).

person Syd    schedule 03.08.2018