JavaFX TreeTableView异常排序时选择多行

问题描述:

我正在学习JavaFX,我似乎无法得到一件好事。 基本上我想要做的是一个TreeTableView与多个选择,这工作正常,直到我尝试排序列表。JavaFX TreeTableView异常排序时选择多行

下面的代码(例15-1 TreeTableView有一列从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) 

在此先感谢您的帮助:

控制台排序时给我这个输出。

昨天遇到同样的问题时,我绊倒了你的帖子。可惜没人回答你,我决定调查一下自己。在这样一个常见功能中的错误可能会被忽视多年...

因为它看起来像我在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()) { 

此代码尝试根据其内容重新选择正确的项目(而不是在排序过程中可能更改的行号)。但不幸的是,它没有考虑多重选择的情况,只在排序后选择了一个项目。

崩溃(异常)发生,因为TreeTableViewsort()方法似乎为解决同一个问题(恢复正确的选择,一旦排序完成),由分拣之前和之后保存的选择指标,并发出排列事件:

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

在之前-最后一行,而假定newState列表具有itemCount元件(而newState将总是包含1个元件如以上所解释)被创建onIterableChange.GenericAddRemoveChange对象,它崩溃试图获得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 2017-09-23 19:12:20

+0

一个额外的评论,在这里。如果没有此修复程序,即使*没有启用多项选择,我也会看到问题。 – melston 2017-09-23 19:13:51