diff --git a/modules/controls/src/main/java/javafx/scene/control/TreeItem.java b/modules/controls/src/main/java/javafx/scene/control/TreeItem.java --- a/modules/controls/src/main/java/javafx/scene/control/TreeItem.java +++ b/modules/controls/src/main/java/javafx/scene/control/TreeItem.java @@ -362,7 +362,9 @@ * Instance Variables * * * **************************************************************************/ - + + private boolean ignoreSortUpdate = false; + private boolean expandedDescendentCountDirty = true; // The ObservableList containing all children belonging to this TreeItem. @@ -632,8 +634,15 @@ // we need to check if this TreeItem needs to have its children sorted. // There are two different ways that this could be possible. if (children.isEmpty()) return children; - - checkSortState(); + + // checkSortState should in almost all instances be called, but there + // are situations where checking the sort state will result in + // unwanted permutation events being fired (if a sort is applied). To + // avoid this (which resolves RT-37593), we set the ignoreSortUpdate + // to true (and of course, we're careful to set it back to false again) + if (!ignoreSortUpdate) { + checkSortState(); + } return children; } @@ -875,17 +884,19 @@ } return expandedDescendentCount; } - + private void updateExpandedDescendentCount(boolean reset) { previousExpandedDescendentCount = expandedDescendentCount; expandedDescendentCount = 1; - + + ignoreSortUpdate = true; if (!isLeaf() && isExpanded()) { for (TreeItem child : getChildren()) { if (child == null) continue; expandedDescendentCount += child.isExpanded() ? child.getExpandedDescendentCount(reset) : 1; } } + ignoreSortUpdate = false; } private void updateChildren(List> added, List> removed) { diff --git a/modules/controls/src/test/java/javafx/scene/control/TreeTableViewTest.java b/modules/controls/src/test/java/javafx/scene/control/TreeTableViewTest.java --- a/modules/controls/src/test/java/javafx/scene/control/TreeTableViewTest.java +++ b/modules/controls/src/test/java/javafx/scene/control/TreeTableViewTest.java @@ -3770,4 +3770,43 @@ assertEquals(1, rt_37538_count); sl.dispose(); } + + @Test public void test_rt_37593() { + TreeItem root = new TreeItem<>(); + + TreeItem one = new TreeItem<>("one"); + root.getChildren().add(one); + + TreeItem two = new TreeItem<>("two"); + two.getChildren().add(new TreeItem<>("childOne")); + two.getChildren().add(new TreeItem<>("childTwo")); + root.getChildren().add(two); + + root.getChildren().add(new TreeItem<>("three")); + + TreeTableColumn nameColumn = new TreeTableColumn<>("name"); + nameColumn.setCellValueFactory(param -> new ReadOnlyObjectWrapper(param.getValue().getValue())); + + treeTableView.setShowRoot(false); + treeTableView.setRoot(root); + treeTableView.getColumns().addAll(nameColumn); + + treeTableView.getSortOrder().add(nameColumn); + nameColumn.setSortType(TreeTableColumn.SortType.DESCENDING); + sm.select(one); + + // at this point, the 'one' item should be in row 2 + assertTrue(sm.isSelected(2)); + assertEquals(one, sm.getSelectedItem()); + + two.setExpanded(true); + + // this line would create a NPE + VirtualFlowTestUtils.clickOnRow(treeTableView, 4); + + // we should end up with the selection being on index 4, which is the + // final location of the 'one' tree item, after sorting and expanding 'two' + assertEquals(one, sm.getSelectedItem()); + assertTrue(debug(), sm.isSelected(4)); + } }