Блок ListChangeListener wasPermutated

JavaDoc для ListChangeListener предоставляет шаблон для обработки изменений. Однако я не знаю, как обрабатывать перестановки. Для каждого индекса я могу узнать, где находится новый индекс элемента, но я не знаю, что с ним делать. Это небольшая головоломка, не зависящая от языка программирования. ObservableList может только add(), remove(), set(), а также имеет итератор.

Если у меня есть исходный список [1,2,3] и я привязываю к нему список [], связанный список [1,2,3] должен соответствовать ему. Если исходный список меняет свой компаратор так, что исходный список теперь читается как [3,2,1], как мне сделать так, чтобы связанный список следовал за ним?

/**
 * Binds a source list's elements to a destination list. Any changes made in
 * the source list will reflect in the destination list.
 *
 * @param <SRC> The source list's object type.
 * @param <DEST> The destination list's object type.
 * @param dest The destination list that will be bound to the src list.
 * @param src The source list to watch for changes, and propagate up to the
 * destination list.
 * @param transformer A function that will transform a source list data
 * type, A, into a destination list data type, B.
 */
public static <SRC, DEST> void bindLists(
        ObservableList<DEST> dest, ObservableList<SRC> src, Function<? super SRC, ? extends DEST> transformer) {
    /*Add the initial data into the destination list.*/
    for (SRC a : src) {
        dest.add(transformer.apply(a));
    }
    /*Watch for future data to add to the destination list. Also watch for removal
     of data form the source list to remove its respective item in the destination
     list.*/
    src.addListener((ListChangeListener.Change<? extends SRC> c) -> {
        while (c.next()) {
            if (c.wasPermutated()) {
                /*How do you handle permutations? Do you remove and then add, 
                 or add and then remove, or use set, or use a copy arraylist 
                 and set the right indices? Removing/adding causes concurrent modifications.*/
                for (int oldIndex = c.getFrom(); oldIndex < c.getTo(); oldIndex++) {
                    int newIndex = c.getPermutation(oldIndex);
                    dest.remove(oldIndex);
                    dest.add(newIndex, dest.get(oldIndex));
                }
            } else if (c.wasUpdated()) {

            } else {
                /*Respond to removed data.*/
                for (SRC item : c.getRemoved()) {
                    int from = c.getFrom();
                    dest.remove(from);
                }
                /*Respond to added data.*/
                for (SRC item : c.getAddedSubList()) {
                    int indexAdded = src.indexOf(item);
                    dest.add(indexAdded, transformer.apply(item));
                }
            }
        }
    });
}

person Toni_Entranced    schedule 06.09.2014    source источник
comment
Я также хотел бы знать, как срабатывает wasUpdated(). Нужно ли объектам ObservableList реализовывать Observable или что-то в этом роде?   -  person Toni_Entranced    schedule 07.09.2014


Ответы (2)


В случае перестановки я бы не стал пытаться использовать add() и remove(), чтобы справиться с этим. Это приведет к смещению индексов и запутает (по крайней мере, меня).

Концептуально вы получаете диапазон затронутых элементов и массив, содержащий некоторые числа, указывающие, куда был перемещен каждый элемент. Я думаю, вы это понимаете. В вашем коде у вас есть,

    newIndex = getPermutation(oldIndex);

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

    int from = c.getFrom();
    int to = c.getTo();
    List<DEST> copy = new ArrayList<>(dest.subList(from, to));
    for (int oldIndex = from; oldIndex < to; oldIndex++) {
        int newIndex = c.getPermutation(oldIndex);
        dest.set(newIndex, copy.get(oldIndex - from));
    }

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

Режимы перестановки и обновленного изменения не запускаются обычными действиями со списком. Если вы посмотрите на javafx.collections.ObservableListBase, вы увидите протокол, который реализация списка может использовать для создания информации о конкретном изменении. Если реализация предоставляет правильную информацию методам nextPermutation или nextUpdate, она активирует эти другие режимы изменения. Я не уверен, что может вызвать их в JavaFX. Например, методы Node.toFront() и Node.toBack() для изменения порядка размещения узлов потенциально могут генерировать изменения перестановки, но, похоже, этого не происходит. Я также не знаю ничего, что могло бы вызвать изменение обновления.

Семантически я думаю, что изменение обновления подразумевает, что элементы в этом диапазоне списка изменились, но длина списка остается прежней. Это отличается от режима изменения «заменено», где диапазон элементов может быть заменен другим количеством элементов. Также может случиться так, что изменение обновления означает, что сами элементы не были заменены, то есть содержимое списка не изменилось, а изменилось только внутреннее состояние элементов.

person Stuart Marks    schedule 07.09.2014
comment
В целом я бы одобрил такой подход. Просто обратите внимание, что если список, которым вы манипулируете, является (или может быть) списком дочерних элементов Node в Pane, то это сломается, поскольку во время обработки цикла в списке будут дублироваться узлы. В этом случае вы можете применить перестановку к копии с copy.set(newIndex - from, dest.get(oldIndex)); внутри цикла. Затем, когда вы закончите, замените элементы dest на dest.subList(from, to).clear(); и dest.addAll(from, copy);. - person James_D; 07.09.2014
comment
@James_D Хороший совет, чтобы избежать дублирования записей Node, если dest находится в графе сцены. - person Stuart Marks; 07.09.2014

Обратите внимание, что то, что вы здесь делаете, реализовано в EasyBind. Узнайте, как сопоставить и связать список.

По сути, вот как вы реализуете свой метод bindLists:

public static <SRC, DEST> void bindLists(
        ObservableList<DEST> dest, ObservableList<SRC> src,
        Function<? super SRC, ? extends DEST> transformer) {

    EasyBind.listBind(dest, EasyBind.map(src, transformer));
}
person Tomas Mikula    schedule 10.09.2014
comment
Помню, несколько месяцев назад я решил не использовать ваш класс по какой-то причине, но не могу вспомнить почему. Я полагаю, что в этот момент я могу вернуться к его использованию. :) - person Toni_Entranced; 11.09.2014
comment
Кроме того, вопрос не по теме, но в чем разница между банкой Javadoc и исходной банкой? Разве EasyBind.jar уже не является исходным кодом? Зачем мне прикреплять к нему источник? - person Toni_Entranced; 11.09.2014
comment
Чтобы использовать EasyBind, вам нужно только easybind-<version>.jar. Он содержит только скомпилированные классы. Исходный jar содержит только исходный код. Полезно прикрепить исходный файл JAR к вашей IDE, чтобы он мог показывать вам комментарии Javadoc и/или исходный код, например, при отладке и пошаговом выполнении кода. - person Tomas Mikula; 11.09.2014
comment
Я понимаю. Благодарю вас! В конечном итоге мне также придется сделать это (упаковать в .jar и предоставить исходный код/javadocs) для моего клиента. - person Toni_Entranced; 11.09.2014
comment
EasyBind очень хорош и необходим для моего проекта JavaFX, но мне очень сложно разобраться в API. Пока что я могу использовать только тот образец кода, с которым я сталкивался. - person Wulf; 04.03.2018