【问题标题】:ListChangeListener wasPermutated blockListChangeListener wasPermutated 块
【发布时间】:2014-10-31 14:03:48
【问题描述】:

ListChangeListener 的 JavaDoc 提供了一个用于处理更改的模板。但是,我不知道如何处理排列。对于每个索引,我都可以找出该项目的新索引在哪里,但我不知道如何处理它。这是一个独立于编程语言的难题。一个 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));
                }
            }
        }
    });
}

【问题讨论】:

  • 我也想知道 wasUpdated() 是如何被触发的。 ObservableList的对象需要实现Observable什么的吗?

标签: java javafx javafx-8 observablelist


【解决方案1】:

对于排列情况,我不会费心尝试使用 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,您会看到一个协议,列表实现可以使用该协议来构建有关特定更改的信息。如果实现向nextPermutationnextUpdate 方法提供正确的信息,它将触发这些更多其他更改模式。我不确定什么会在 JavaFX 中触发它们。例如,更改节点堆叠顺序的Node.toFront()Node.toBack() 方法可能会生成排列更改,但它们似乎不会。我也不知道任何会产生更新更改的东西。

在语义上,我认为更新更改意味着列表范围内的元素已更改,但列表的长度保持不变。这与“替换”更改模式形成对比,其中一系列元素可能被不同数量的元素替换。也可能是更新更改意味着元素本身没有被替换——也就是说,列表内容没有改变——只是元素的内部状态发生了变化。

【讨论】:

  • 总的来说,我更喜欢这种方法。请注意,如果您正在操作的列表是(或可能是)Pane 中的子 Nodes 列表,那么这将中断,因为在您处理循环时列表中会有重复的节点。在这种情况下,您可以在循环内使用copy.set(newIndex - from, dest.get(oldIndex)); 将排列应用于副本。然后当你完成后,将dest 的元素替换为dest.subList(from, to).clear();dest.addAll(from, copy);
  • @James_D 如果dest 在场景图中,避免重复的Node 条目的要点是。
【解决方案2】:

请注意,您在此处执行的操作是在 EasyBind 中实现的。看看如何mapbind 一个列表。

基本上,这就是您实现 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));
}

【讨论】:

  • 我记得几个月前出于某种原因决定不使用您的课程,但我不记得是什么。我想此时我可以切换回使用它。 :)
  • 另外,题外话问题,但是 Javadoc jar 和源 jar 有什么区别? EasyBind.jar 不是已经是源吗?为什么我需要附加一个来源?
  • 要使用EasyBind,您只需要easybind-&lt;version&gt;.jar。它只包含编译的类。源 jar 仅包含源代码。在 IDE 中附加源 jar 非常有用,这样它就可以向您显示 Javadoc cmets 和/或源代码,例如在您调试和单步执行代码时。
  • 我明白了。谢谢!我最终也将不得不为我的客户执行此操作(打包成 .jar 并提供源代码/javadocs)。
  • EasyBind 非常好,是我的 JavaFX 项目的必备工具,但我发现很难弄清楚 API。到目前为止,我只能通过我遇到的代码示例来使用
猜你喜欢
  • 1970-01-01
  • 2015-06-10
  • 2014-06-13
  • 2019-07-23
  • 2018-04-28
  • 2020-07-14
  • 2011-03-16
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多