Статьи

Как отсортировать глазированный TreeList

Это моя первая статья, так что не будьте слишком критичны, я просто хотел поделиться некоторыми идеями по проблеме, решение которой заняло у меня пару дней.

Мы используем глазированный список вместе с nattable в нашем проекте. Обычно за таблицей стоит TreeList (и поставщик данных на основе TreeList), который создается на основе отсортированного списка. Это сделано для того, чтобы иметь возможность представлять древовидную структуру внутри таблицы. Более того, узлы этого дерева могут быть разных типов. Иногда поставщик данных изменяется на поставщика данных, который использует обычный отсортированный список — когда применяется фильтрация.

Клиент хочет сортировать по разным типам узлов в дереве по одному столбцу. Это требование заставило нас изменить стандартный механизм сортировки nattable. Это была не ракетостроение, хотя это было немного сложно. По сути, необходимо было передать дополнительный параметр ComparatorChooser и SortingState (SortTarget), который указывает, какой тип узлов должен быть отсортирован:

public PlanningTableSortingColumn(final TableFormat<? super T> tableFormat, final int column, final SortTarget sortTarget) {
	this.column = column;
	this.sortTarget = sortTarget;

	if (tableFormat instanceof PlanningTableColumnFormat) {
		final PlanningTableColumnFormat<? super T> planningTableColumnFormat = (PlanningTableColumnFormat<? super T>) tableFormat;
		planningTableColumnFormat.setSortTarget(sortTarget);
		final Comparator<T> columnComparator = (Comparator<T>) planningTableColumnFormat.getColumnComparator(column);
		if (columnComparator != null) {
			comparators.add(new PlanningTableColumnComparatorDelegate<T>(tableFormat, column, columnComparator, sortTarget));
		}
	} else {
		comparators.add(new TableColumnComparator(tableFormat, column));
	}
}

Компаратор для каждого столбца также был изменен. Изначально это был обычный TableColumnComparator, который принимает в качестве параметра пользовательский компаратор, который отвечает за сортировку значений столбцов. Но из-за того, что необходимо было сортировать по двум различным типам объектов в дереве, мы не могли использовать TableColumComparator.

Основная обязанность PlanningTableColumnComparatorDelegate состоит в том, чтобы выяснить, подходят ли два объекта для сортировки, а затем передать полномочия до TableColumnComparator, который впоследствии вызовет наш пользовательский компаратор с правильными значениями столбца. PlanningTableColumnComparatorDelegate использует следующий простой алгоритм для определения возможности сортировки двух объектов:

  1. Объекты одного типа
  2. Цель сортировки подходит для сортировки текущих объектов
  3. Объекты имеют одного и того же родителя
  4. Если хотя бы одно из условий не выполнено, вернуть 0.

Еще одна особенность, которую нужно было реализовать, — это CellPainter. По умолчанию SortableHeaderTextPainter был расширен, чтобы можно было рисовать «треугольник сортировки» с обеих сторон отсортированной ячейки заголовка.

Когда все (компараторы, модели, слои и т. Д.) Было изменено для поддержки пользовательской сортировки, оно начало работать, но счастье было недолгим. Это работало, когда провайдер данных был один с SortedList. Когда мы изменили его на использование TreeList, сортировка не произошла.

Для того, чтобы он работал с TreeList, прежде всего мне пришлось переключить компаратор, который изначально был реализован для TreeLis # Format. Причина в том, что он переопределяет сортировку, которая была выполнена в SortedList. После возврата null для TreeList.Format # getComparator (int) сортировка снова начала работать, но с одним исключением — для сортировки по одному типу были продублированы дочерние элементы:

No sorting applied:

Type1-el1
   Type2-el1
      Type3-el1
      Type3-el2
   Type2-el2

Sorting by Type2 elements
Type1-el1
   Type2-el1
   Type2-el2
   Type2-el2
      Type3-el1
      Type3-el2

Reversed sorting:
Type1-el1
   Type2-el2
   Type2-el1
   Type2-el2
      Type3-el1
      Type3-el2

 Сортировка по узлам Typ3 работала нормально.

После некоторого времени отладки я обнаружил, что элементы на самом деле не дублируются. Причиной такого поведения было то, что механизм сортировки правильно перемещал те узлы, которые на самом деле должны были быть перемещены, но его дочерние элементы не были перемещены вместе с родительским. Они остались на своих первоначальных местах и ​​(вероятно) из-за TreeList не смогли найти своих родителей там, где они должны быть, он добавил их сам.

Я провел некоторое время, пытаясь переместить родителей вместе с детьми в моих компараторах, но без какой-либо удачи.

По-видимому, первоначальный механизм сортировки должен был быть приспособлен, чтобы иметь возможность перемещать родителей вместе со своими детьми. Сортировка выполняется в ColumnComparatoChooser # rebuildComparator (), когда выбранный компаратор установлен в SortedList. Метод RebuilComparator должен был быть переопределен таким образом, чтобы мы не вызывали setComparator для SortedList, а сортировали его сами.

Я могу сказать, что это, вероятно, не лучший способ сделать это, потому что возникают некоторые другие проблемы — например, сначала нужно очистить SortedList, а затем поместить свой собственный «отсортированный список», который вызывает некоторые магические вещи внутри SortList. и TreeList. Кроме того, вам необходимо иметь возможность восстанавливать состояние списка, когда сортировка не применяется (при восстановлении состояния необходимо учитывать, что этот список может быть изменен) и т. Д. И т. Д. (Я упомяну еще одну проблему позже). Но, несмотря на все эти факты, я придумал следующую реализацию для метода rebuildComparator.

protected void rebuildComparator() {
	final Comparator<T> rebuiltComparator = sortingState.buildComparator();
	SortTarget sortTarget = null;
	final boolean isTypeFilterApplied = SortingUtils.isTypeFilterApplied(sortedList);

	// update comparator with current sorting conditions
        ....

	// select the new comparator
	sortedList.getReadWriteLock().writeLock().lock();
	try {
		sortedListComparator = rebuiltComparator;
		final BasicEventList<T> tmpSortedList;
		if (rebuiltComparator != null) {
			tmpSortedList = SortingUtils.sort(rebuiltComparator, sortedList, sortTarget, isTypeFilterApplied);
			sortedList.clear();
			sortedList.addAll(tmpSortedList);
		} else {
			// restore initial state, when no sorting is applied
			final List<PlanningTableNode> currentState = planningTableContentProvider.getPlanningTableBodyLayerStack()
				       .getCurrentBodyListDataProvider().getList();
			// copy is needed, since further clear clears also currentState list
			final EventList<T> currentStateCopy = (EventList<T>) GlazedLists.eventList(currentState);
			if (currentState.size() > 0) {
				tmpSortedList = SortingUtils.sort(new BENameComparator<T>(), currentStateCopy, SortTarget.AT, isTypeFilterApplied);
				sortedList.clear();
				sortedList.addAll(tmpSortedList);
			}
		}
	} finally {
		sortedList.getReadWriteLock().writeLock().unlock();
	}
}

 

Основная идея — правильно отсортировать tmpSortedList, а затем просто обновить SortedList уже отсортированными элементами.

Теперь все, кажется, работает … или почти все.

Когда применяется фильтр типов, в списке присутствуют элементы только одного типа, а поставщик данных меняется с одного, который использует TreeList на другой, который использует SortedList (в таблице больше нет древовидной структуры). Сортировка также работает в этом случае, но когда фильтр очищен и начальное дерево восстановлено (поставщик данных заменен на древовидный поставщик данных), опять же, некоторые элементы дублируются. Я еще не исследовал это глубоко и не могу сказать прямо сейчас, какова реальная причина такого поведения. Может быть, это тема для другой статьи …

 Любая обратная связь с благодарностью.

AlexG.