【问题标题】:Can JavaFX's ListChangeListener.Change.getRemoved() return non-contiguous items?JavaFX 的 ListChangeListener.Change.getRemoved() 可以返回不连续的项目吗?
【发布时间】:2016-03-18 00:34:56
【问题描述】:

问题

ObservableList 中删除项目时,会触发change event,其中getFrom() 提供删除位置,getRemoved() 提供已删除项目的列表。 The documentation 说:

getRemoved() 方法返回已被替换或从列表中删除的元素列表。

没有这样说明,但我认为它暗示项目列表是原始列表的连续子列表。我已经用这个假设编写了很多代码,但现在遇到了TreeTableView's selection model 的问题,它的行为方式并非如此。

示例

以一个包含三个“节点”行的简单树表为例。如果我选择这三行...

...然后单击并仅选择中间行...

...treeTableView.getSelectionModel().getSelectedItems() 上触发的更改事件如下所示:

{ [TreeItem [ value: Node 1 ], TreeItem [ value: Node 3 ]] removed at 0,  }

在单个更改事件中,它报告“节点 1”和“节点 3”已从 selectedItems 列表的索引 0 中删除。

我本来希望 Change 对象有两个单独的删除事件,由 next() 调用分隔。第一次调用 next() 会告诉我“节点 1”在索引 0 处被删除,第二次调用 next() 会告诉我“节点 3”在索引 1 处被删除。但不,我得到一个两行同时列出的事件。

问题

getRemoved() 真的可以返回不连续的项目吗?这是我对列表更改事件如何工作的误解,还是TreeTableView 中的错误?

通常我会犹豫归咎于标准库,但这不会是我在 JavaFX 中发现的第一个错误,因此并非不可想象。


更新

如果我添加对setShowRoot(false) 的调用,行为会发生变化。我得到了我的期望,删除分为两部分:

{ [TreeItem [ value: Node 1 ]] removed at 0, [TreeItem [ value: Node 3 ]] removed at 1,  }

另外,这是我的MCVE

import java.util.*;

import javafx.application.*;
import javafx.beans.property.*;
import javafx.collections.*;
import javafx.scene.*;
import javafx.scene.control.*;
import javafx.stage.*;

public class TreeTableSelectionEvents extends Application {
    public void start(Stage stage) {
        // Root node.
        TreeItem<String> root = new TreeItem<>("Root");

        root.setExpanded(true);

        root.getChildren().setAll(Arrays.asList(
            new TreeItem<>("Node 1"),
            new TreeItem<>("Node 2"),
            new TreeItem<>("Node 3")
        ));

        // Single column.
        TreeTableColumn<String, String> column = new TreeTableColumn<>("Column");

        column.setPrefWidth(150);
        column.setCellValueFactory((TreeTableColumn.CellDataFeatures<String, String> p) -> {
            return new ReadOnlyStringWrapper(p.getValue().getValue());
        });

        // Tree table.
        TreeTableView<String> table = new TreeTableView<>(root);

        table.getColumns().add(column);
        table.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);
        // table.setShowRoot(false);

        table.getSelectionModel().getSelectedItems().addListener(
            (ListChangeListener.Change<? extends TreeItem<String>> change) -> {
                System.out.printf("item change = %s, list is now %s%n", change, change.getList());
            }
        );

        table.getSelectionModel().getSelectedIndices().addListener(
            (ListChangeListener.Change<? extends Integer> change) -> {
                System.out.printf("index change = %s, list is now %s%n", change, change.getList());
            }
        );

        // Stage.
        stage.setScene(new Scene(table));
        stage.show();
    }
}

【问题讨论】:

  • getTo() 在那种情况下会返回什么?
  • getTo() = getFrom() = 0
  • 那至少与当时的文档一致。似乎答案很简单“是的,getRemoved() 可以返回不连续的项目”。有什么理由相信这是一个错误吗?我在文档中没有看到任何地方表明这是不允许的。
  • 列表可以包含重复项。如果getRemoved() 中的项目不连续,那么我无法明确确定删除了哪些项目。它可以是任何重复项。我怀疑但不确定这是文档中的缺陷和TreeTableViewSelectionModel 中的错误。
  • 明白。可能是 API 设计的缺陷……

标签: java javafx observablelist treetableview


【解决方案1】:

你是对的,事件应该包含两个单独的“删除”更改。从 1.8.0_74 开始,TreeTableView 的选择模型似乎被彻底破坏了。它甚至与 TreeView 的选择模型不一致,后者也很棘手(但不那么严重)。有如此多的故障模式、现有的错误和回归错误,很难判断 Oracle 是否意识到了问题。我建议提交另一个错误。下面的代码提供了一个不错的沙盒来玩功能。

public class Test extends Application {
    public void start(Stage pStage) {
        pStage.setTitle("Test");

        final TabPane tabPane = new TabPane();

        tabPane.getTabs().addAll(
            Stream.of(true, false).map(
                pIsTreeTable -> {
                    final Tab result = new Tab(pIsTreeTable ? "TreeTableView" : "TreeView");

                    // create tree model
                    final TreeItem<String> root = new TreeItem<>("Root Node");
                    root.setExpanded(true);
                    final Collection<TreeItem<String>> children = IntStream.rangeClosed(
                        1, 5
                    ).mapToObj(pIdx -> new TreeItem<>("Child Node " + pIdx)).collect(
                        Collectors.toList()
                    );

                    // create TreeView or TreeTableView
                    final Control tree;
                    final MultipleSelectionModel<TreeItem<String>> selectionModel;
                    if (pIsTreeTable) {
                        final TreeTableView<String> treeTableView = new TreeTableView<>(
                            root
                        );
                        final TreeTableColumn<String,String> column = new TreeTableColumn<>(
                            "Column"
                        );
                        column.setCellValueFactory(
                            pTreeItem -> new ReadOnlyStringWrapper(
                                pTreeItem.getValue().getValue()
                            )
                        );
                        treeTableView.getColumns().add(column);

                        tree = treeTableView;
                        selectionModel = treeTableView.getSelectionModel();
                    } else {
                        final TreeView<String> treeView = new TreeView<>(root);

                        tree = treeView;
                        selectionModel = treeView.getSelectionModel();
                    }
                    selectionModel.setSelectionMode(SelectionMode.MULTIPLE);

                    // add buttons
                    final ToggleButton childrenBtn = new ToggleButton("Children");
                    childrenBtn.selectedProperty().addListener(
                        (pObservable, pOldVal, pNewVal) -> {
                            if (pNewVal) {
                                root.getChildren().addAll(children);
                            } else {
                                root.getChildren().clear();
                            }
                        }
                    );
                    childrenBtn.setSelected(true);
                    final ToggleButton showRootBtn = new ToggleButton("Show Root");
                    showRootBtn.setSelected(true);
                    (
                        pIsTreeTable ?
                        ((TreeTableView<?>) tree).showRootProperty() :
                        ((TreeView<?>) tree).showRootProperty()
                    ).bind(showRootBtn.selectedProperty());

                    // 'getSelectedItems()' tab
                    final Tab selectedItemsTab = new Tab("getSelectedItems()");
                    final TextArea selectedItemsTextArea = new TextArea();
                    selectionModel.getSelectedItems().addListener(
                        (ListChangeListener<TreeItem<String>>) pChange -> {
                            while (pChange.next()) {
                                if (pChange.getRemovedSize() > 0) {
                                    selectedItemsTextArea.appendText(
                                        "Removed " + pChange.getRemoved() + '\n'
                                    );
                                }
                                if (pChange.getAddedSize() > 0) {
                                    selectedItemsTextArea.appendText(
                                        "Added " + pChange.getAddedSubList() + '\n'
                                    );
                                }
                            }
                            selectedItemsTextArea.appendText(
                                "Selection: " + pChange.getList() + "\n\n"
                            );
                        }
                    );
                    selectedItemsTab.setContent(selectedItemsTextArea);

                    // 'getSelectedItem()' tab
                    final Tab selectedItemTab = new Tab("getSelectedItem()");
                    final TextArea selectedItemTextArea = new TextArea();
                    selectionModel.selectedItemProperty().addListener(
                        (pObservable, pOldVal, pNewVal) -> {
                            selectedItemTextArea.appendText("Selected " + pNewVal + '\n');
                        }
                    );
                    selectedItemTab.setContent(selectedItemTextArea);


                    // display selection data in text area
                    final TabPane selectionTabPane = new TabPane();
                    selectionTabPane.getTabs().addAll(selectedItemsTab, selectedItemTab);

                    final SplitPane splitPane = new SplitPane(
                        tree, new HBox(showRootBtn, childrenBtn), selectionTabPane
                    );
                    splitPane.setOrientation(Orientation.VERTICAL);

                    result.setContent(splitPane);

                    return result;
                }
            ).collect(Collectors.toList())
        );

        pStage.setScene(new Scene(tabPane, 300, 450));
        pStage.show();
    }

    public static void main(String[] pArgs) {launch(pArgs);}
}

相关(?)问题:

  1. 按下“Ctrl”按钮
  2. 选择“子节点 2”
  3. 选择“子节点 3”
  4. 选择“子节点 1”

ListChangeListener.Change 事件意味着选择了“子节点 2”。

  1. 选择“根节点”
  2. 取消选择“显示根”按钮

“子节点1”被选中,但没有广播选择事件。

  1. 选择“子节点 2”
  2. 取消选择“儿童”按钮

“选择”列表包含一个空值。

  1. 选择“子节点 1”
  2. 折叠“根节点”

ListChangeListener.Change 事件不包括任何删除。

  1. 取消选择“儿童”按钮
  2. 选择“根节点”
  3. 取消选择“显示根”按钮

TreeView 和 TreeTableView 都不会广播事件。从那里,如果按下“显示根”按钮,TreeView 会广播事件,但 TreeTableView 不会。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-09-06
    • 2019-12-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多