JavaFX TreeTableView исключение при сортировке с несколькими выбранными строками

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

Вот код (Пример 15-1 TreeTableView с одним столбцом из http://docs.oracle.com/javase/8/javafx/user-interface-tutorial/tree-table-view.htm):

import javafx.application.Application;
import javafx.beans.property.ReadOnlyStringWrapper;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.control.TreeTableColumn;
import javafx.scene.control.TreeTableColumn.CellDataFeatures;
import javafx.scene.control.TreeItem;
import javafx.scene.control.TreeTableView;
import javafx.stage.Stage;

public class TreeTableViewSample extends Application {

    public static void main(String[] args) {
        Application.launch(args);
    }

    @Override
    public void start(Stage stage) {
        stage.setTitle("Tree Table View Samples");
        final Scene scene = new Scene(new Group(), 200, 400);
        Group sceneRoot = (Group)scene.getRoot();  

        //Creating tree items
        final TreeItem<String> childNode1 = new TreeItem<>("Child Node 1");
        final TreeItem<String> childNode2 = new TreeItem<>("Child Node 2");
        final TreeItem<String> childNode3 = new TreeItem<>("Child Node 3");

        //Creating the root element
        final TreeItem<String> root = new TreeItem<>("Root node");
        root.setExpanded(true);   

        //Adding tree items to the root
        root.getChildren().setAll(childNode1, childNode2, childNode3);        

        //Creating a column
        TreeTableColumn<String,String> column = new TreeTableColumn<>("Column");
        column.setPrefWidth(150);   

        //Defining cell content
        column.setCellValueFactory((CellDataFeatures<String, String> p) -> 
            new ReadOnlyStringWrapper(p.getValue().getValue()));  

        //Creating a tree table view
        final TreeTableView<String> treeTableView = new TreeTableView<>(root);
        TreeTableView.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE); //Setting SelectionMode to MULTIPLE
        treeTableView.getColumns().add(column);
        treeTableView.setPrefWidth(152);
        treeTableView.setShowRoot(true);             
        sceneRoot.getChildren().add(treeTableView);
        stage.setScene(scene);
        stage.show();
    }     
}

Я добавил эту строку:

TreeTableView.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE); //Setting SelectionMode to MULTIPLE

Все работает нормально, но когда я выбираю несколько строк и пытаюсь отсортировать столбцы, остается выбранной только активная строка (последняя выбранная).

Консоль дает мне этот вывод при сортировке:

Exception in thread "JavaFX Application Thread" java.lang.IndexOutOfBoundsException
    at com.sun.javafx.scene.control.ReadOnlyUnbackedObservableList.subList(Unknown Source)
    at javafx.collections.ListChangeListener$Change.getAddedSubList(Unknown Source)
    at javafx.scene.control.TreeTableView$TreeTableViewArrayListSelectionModel.handleSelectedCellsListChangeEvent(Unknown Source)
    at javafx.scene.control.TreeTableView$TreeTableViewArrayListSelectionModel.access$2100(Unknown Source)
    at javafx.scene.control.TreeTableView.sort(Unknown Source)
    at javafx.scene.control.TreeTableView.doSort(Unknown Source)
    at javafx.scene.control.TreeTableView.lambda$new$115(Unknown Source)
    at javafx.scene.control.TreeTableView$$Lambda$99/1473718685.onChanged(Unknown Source)
    at com.sun.javafx.collections.ListListenerHelper$Generic.fireValueChangedEvent(Unknown Source)
    at com.sun.javafx.collections.ListListenerHelper.fireValueChangedEvent(Unknown Source)
    at javafx.collections.ObservableListBase.fireChange(Unknown Source)
    at javafx.collections.ListChangeBuilder.commit(Unknown Source)
    at javafx.collections.ListChangeBuilder.endChange(Unknown Source)
    at javafx.collections.ObservableListBase.endChange(Unknown Source)
    at javafx.collections.ModifiableObservableListBase.setAll(Unknown Source)
    at javafx.collections.ObservableListBase.setAll(Unknown Source)
    at com.sun.javafx.scene.control.skin.TableColumnHeader.sortColumn(Unknown Source)
    at com.sun.javafx.scene.control.skin.TableColumnHeader.lambda$static$55(Unknown Source)
    at com.sun.javafx.scene.control.skin.TableColumnHeader$$Lambda$152/863692449.handle(Unknown Source)
    at com.sun.javafx.event.CompositeEventHandler.dispatchBubblingEvent(Unknown Source)
    at com.sun.javafx.event.EventHandlerManager.dispatchBubblingEvent(Unknown Source)
    at com.sun.javafx.event.EventHandlerManager.dispatchBubblingEvent(Unknown Source)
    at com.sun.javafx.event.CompositeEventDispatcher.dispatchBubblingEvent(Unknown Source)
    at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(Unknown Source)
    at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(Unknown Source)
    at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(Unknown Source)
    at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(Unknown Source)
    at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(Unknown Source)
    at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(Unknown Source)
    at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(Unknown Source)
    at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(Unknown Source)
    at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(Unknown Source)
    at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(Unknown Source)
    at com.sun.javafx.event.EventUtil.fireEventImpl(Unknown Source)
    at com.sun.javafx.event.EventUtil.fireEvent(Unknown Source)
    at javafx.event.Event.fireEvent(Unknown Source)
    at javafx.scene.Scene$MouseHandler.process(Unknown Source)
    at javafx.scene.Scene$MouseHandler.access$1500(Unknown Source)
    at javafx.scene.Scene.impl_processMouseEvent(Unknown Source)
    at javafx.scene.Scene$ScenePeerListener.mouseEvent(Unknown Source)
    at com.sun.javafx.tk.quantum.GlassViewEventHandler$MouseEventNotification.run(Unknown Source)
    at com.sun.javafx.tk.quantum.GlassViewEventHandler$MouseEventNotification.run(Unknown Source)
    at java.security.AccessController.doPrivileged(Native Method)
    at com.sun.javafx.tk.quantum.GlassViewEventHandler.lambda$handleMouseEvent$350(Unknown Source)
    at com.sun.javafx.tk.quantum.GlassViewEventHandler$$Lambda$224/2145564822.get(Unknown Source)
    at com.sun.javafx.tk.quantum.QuantumToolkit.runWithoutRenderLock(Unknown Source)
    at com.sun.javafx.tk.quantum.GlassViewEventHandler.handleMouseEvent(Unknown Source)
    at com.sun.glass.ui.View.handleMouseEvent(Unknown Source)
    at com.sun.glass.ui.View.notifyMouse(Unknown Source)
    at com.sun.glass.ui.win.WinApplication._runLoop(Native Method)
    at com.sun.glass.ui.win.WinApplication.lambda$null$145(Unknown Source)
    at com.sun.glass.ui.win.WinApplication$$Lambda$36/2117255219.run(Unknown Source)
    at java.lang.Thread.run(Unknown Source)

Заранее благодарю за любую помощь.

1 ответ

Я наткнулся на ваш пост, когда столкнулся с той же проблемой вчера. Жаль, что никто не ответил вам, и я решил исследовать себя. Все еще невероятно, что ошибка в такой общей функциональности может оставаться незамеченной и нефиксированной годами...

Потому что это выглядит для меня как ошибка в JavaFX, в TreeTableView.TreeTableViewArrayListSelectionModel, Есть обработчик, который обновляет выбор в ответ на изменения в модели данных:

        private EventHandler<TreeItem.TreeModificationEvent<S>> treeItemListener = new EventHandler<TreeItem.TreeModificationEvent<S>>() {
        @Override public void handle(TreeItem.TreeModificationEvent<S> e) {

            if (getSelectedIndex() == -1 && getSelectedItem() == null) return;
            <...>

В какой-то момент (строка 2421) он обрабатывает случай сортировки (т. Е. Перестановку):

               } else if (e.wasPermutated()) {
                // This handles the sorting case where nothing was added or
                // removed, but the location of the selected index / item
                // has likely changed. This was added to fix RT-30156 and
                // unit tests exist to prevent it from regressing.
                quietClearSelection();
                select(oldSelectedItem);
            } else if (e.wasAdded()) {

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

Авария (исключение) происходит потому, что TreeTableView"s sort() Похоже, что метод решает ту же проблему (восстанавливает правильный выбор после выполнения сортировки), сохраняя индексы выбора до и после сортировки и генерируя событие перестановки:

    final List<TreeTablePosition<S,?>> prevState = new ArrayList<>(getSelectionModel().getSelectedCells());
    final int itemCount = prevState.size();

    <...>

            final TreeTableViewArrayListSelectionModel<S> sm = (TreeTableViewArrayListSelectionModel<S>) getSelectionModel();
            final ObservableList<TreeTablePosition<S, ?>> newState = sm.getSelectedCells();

            List<TreeTablePosition<S, ?>> removed = new ArrayList<>();
            for (int i = 0; i < itemCount; i++) {
                TreeTablePosition<S, ?> prevItem = prevState.get(i);
                if (!newState.contains(prevItem)) {
                    removed.add(prevItem);
                }
            }

            if (!removed.isEmpty()) {
                // the sort operation effectively permutates the selectedCells list,
                // but we cannot fire a permutation event as we are talking about
                // TreeTablePosition's changing (which may reside in the same list
                // position before and after the sort). Therefore, we need to fire
                // a single add/remove event to cover the added and removed positions.
                ListChangeListener.Change<TreeTablePosition<S, ?>> c = new NonIterableChange.GenericAddRemoveChange<>(0, itemCount, removed, newState);
                sm.handleSelectedCellsListChangeEvent(c);
            }

В последней строке onIterableChange.GenericAddRemoveChange объект создается при условии, что newState список имеет itemCount элементы (в то время как newState всегда будет содержать 1 элемент, как описано выше), и он вылетает при попытке получить itemCount элементы из него.

Теперь, что вы можете с этим поделать? Чтобы исправить это, вам нужно либо

  1. подкласс TreeTableView и переопределить sort() метод или
  2. обеспечить собственную реализацию SelectionModel (вероятно, на основе TreeTableViewArrayListSelectionModel реализация) с TreeTableView.SetSelectionModel()

Оба метода не просты, так как в коде интенсивно используются закрытые члены. С первым решением вы также столкнетесь с проблемой FXMLLoader что только может создать TreeTableView (а не ваш подкласс MyTreeTableView), но вы должны иметь возможность создать объект вручную.

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

 myTreeTable.setOnSort(event -> { 
     if(myTreeTable.getSelectionModel().getSelectedIndices().size() > 1) 
         myTreeTable.getSelectionModel().clearSelection(); 
 });

Надеюсь это поможет!

Другие вопросы по тегам