我现在正在学习JavaFX,但似乎做不到一件事。
基本上,我想做的是一个具有多个选择的TreeTableView,在我尝试对列表进行排序之前,它可以正常工作。
这是代码(示例15-1 http://docs.oracle.com/javase/8/javafx/user-interface-tutorial/tree-table-view.htm#CJAEIFDC中具有一列的TreeTableView):
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
一切正常,但是当我选择多行并尝试对列进行排序时,仅 Activity 行(最后选择的行)保持选中状态。
控制台在排序时为我提供以下输出:
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()) {
此代码尝试根据其内容(而不是排序期间可能更改的行号)重新选择正确的项目。但是不幸的是,它没有考虑多重选择的情况,在排序后只剩下一项被选中。
之所以会发生崩溃(异常),是因为
TreeTableView
的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
元素时崩溃。现在,您能做什么?要彻底修复它,您需要
TreeTableView
并覆盖sort()
方法或SelectionModel
实现(可能基于TreeTableViewArrayListSelectionModel
实现)和TreeTableView.SetSelectionModel()
两种方法都不简单,因为代码大量使用可私有访问的成员。对于第一种解决方案,您还将面临
FXMLLoader
的问题,该问题只能创建TreeTableView
(而不是子类MyTreeTableView
),但是您应该能够手动创建该对象。我想我会坚持以下变通方法,只要对表进行排序就清除选择(在我的情况下不会经常发生):
myTreeTable.setOnSort(event -> {
if(myTreeTable.getSelectionModel().getSelectedIndices().size() > 1)
myTreeTable.getSelectionModel().clearSelection();
});
希望这可以帮助!