Подтвердить что ты не робот

Может ли JavaFX ListChangeListener.Change.getRemoved() возвращать несмежные элементы?

Проблема

Когда элементы удаляются из ObservableList, событие изменения запускается там, где getFrom() указывает местоположение удаления и getRemoved() предоставляет список элементов, которые были удалены. В документации говорится:

Метод getRemoved() возвращает список элементов, которые были заменены или удалены из списка.

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

Пример

Возьмем, к примеру, простую таблицу деревьев с тремя строками "Node". Если я выберу эти три строки...

выбрано три строки

... и затем щелкните и выберите только среднюю строку...

введите описание изображения здесь

... событие изменения, запущенное на treeTableView.getSelectionModel().getSelectedItems(), выглядит следующим образом:

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

В одном событии изменения сообщается, что "Node 1" и "Node 3" были удалены из индекса 0 списка selectedItems.

Я бы ожидал, что объект Change будет иметь два отдельных события удаления, разделенных вызовами next(). Первый вызов next() сказал бы мне, что "Node 1" был удален с индексом 0, а второй вызов next() сказал бы мне, что "Node 3" был удален с индексом 1. Но нет, Я получаю одно событие с двумя строками, перечисленными сразу.

Вопрос

Может ли getRemoved() действительно возвращать несмежные элементы? Является ли это недоразумением в моей части того, как работают события изменения списка, или это ошибка в TreeTableView?

Обычно я не решаюсь обвинять стандартные библиотеки, но это не будет первой ошибкой, которую я нашел в JavaFX, поэтому это немыслимо.


Update

Если я добавлю вызов 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();
    }
}
4b9b3361

Ответ 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);}
}

Связанные (?) вопросы:

  • Нажмите кнопку "Ctrl"
  • Выберите "Ребенок Node 2"
  • Выберите "Ребенок Node 3"
  • Выберите "Ребенок Node 1"

Событие ListChangeListener.Change означает, что выбрано "Child Node 2".

  • Выберите "Root Node"
  • Отмените выбор кнопки "Показать корень"

"Ребенок Node 1" выбран, но событие выбора не передается.

  • Выберите "Ребенок Node 2"
  • Отмените выбор кнопки "Дети".

'selection' содержит нуль.

  • Выберите "Ребенок Node 1"
  • Свернуть "Root Node"

Событие ListChangeListener.Change не включает удаление.

  • Отмените выбор кнопки "Дети".
  • Выберите "Root Node"
  • Отмените выбор кнопки "Показать корень"

Событие транслируется ни для TreeView, ни для TreeTableView. Оттуда, если нажата кнопка "Показать корень" , TreeView передает событие, но TreeTableView этого не делает.