2015-03-18 3 views
2

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

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

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) 

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

ответ

7

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

Потому что это выглядит как ошибка в 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()) { 

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

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

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(); 
}); 

Надеется, что это помогает!

+0

Я занимаюсь этой проблемой некоторое время и просто натолкнулся на этот ответ. Кажется, он работает очень хорошо. Благодарю. Я также согласен с тем, что это настоящий позор, что эта ошибка все еще находится в базе кода. – melston

+0

Один дополнительный комментарий, здесь. Без этого исправления я видел проблему даже * без * множественного выбора. – melston