JavaFX 的 ListChangeListener.Change.getRemoved() 可以返回不连续的项目吗?

2022-09-03 03:00:48

问题

当从 中删除项时,将触发一个更改事件,其中提供删除的位置,getRemoved() 提供已删除项的列表。文档说:ObservableListgetFrom()

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

它没有这样说,但我认为它暗示项目清单是原始清单的连续子清单。我已经用这个假设编写了很多代码,但现在在使用TreeTableView的选择模型时遇到了困难,该模型的行为方式并非如此。

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

three rows selected

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

enter image description here

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

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

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

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

问题

真的可以退回不连续的物品吗?这是我对列表更改事件如何工作的误解,还是一个错误?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();
    }
}

答案 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不会。


答案 2

推荐