Removing a JavaFX TreeItem Sometimes Changes TreeTableView Selection

1k Views Asked by At

I have a JavaFX TreeTableView. Under its root, there is a set of TreeItem (nodes) and each node may have its own child TreeItem (sub-nodes). Since there is no information to show for any nodes without sub-nodes, I want to remove those. But somehow, the simple remove action sometime changes the selection.

package sample;

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

public class Main extends Application {

    @Override
    public void start(Stage primaryStage) throws Exception{
        TreeItem<String> rootNode = new TreeItem<String>("Root");
        rootNode.setExpanded(true);
        rootNode.getChildren().setAll(new TreeItem<>("Node 0"),
                new TreeItem<>("Node 1"), new TreeItem<>("Node 2"),//);
                new TreeItem<>("Node 3"), new TreeItem<>("Node 4"));
        for (int i = 0; i < 2; i++) {
            TreeItem<String> node = rootNode.getChildren().get(i);
            node.setExpanded(true);
            node.getChildren().setAll(new TreeItem<>("Sub Node " + i + "-0"),
                    new TreeItem<>("Sub Node " + i + "-1"),
                    new TreeItem<>("Sub Node " + i + "-2"));
        }

        TreeTableColumn<String, String> column = new TreeTableColumn<>("Nodes");
        column.setCellValueFactory((TreeTableColumn.CellDataFeatures<String, String> p) -> {
            return new ReadOnlyStringWrapper(p.getValue().getValue());
        });
        column.setPrefWidth(200);

        TreeTableView<String> table = new TreeTableView<>(rootNode);
        table.getColumns().add(column);

        int selectIndex = 4;
        int removeIndex = 3;
        table.getSelectionModel().select(selectIndex);
        System.out.println("Selected index = " + table.getSelectionModel().getSelectedIndex());
        System.out.println("Selected item  = " + table.getSelectionModel().getSelectedItem().getValue());
        table.getRoot().getChildren().remove(removeIndex);
        System.out.println("Selected index = " + table.getSelectionModel().getSelectedIndex());
        System.out.println("Selected item  = " + table.getSelectionModel().getSelectedItem().getValue());

        primaryStage.setTitle("Tree Table View Selection");
        primaryStage.setScene(new Scene(table, 300, 275));
        primaryStage.show();
    }


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

The code as it is produces the following output:

Selected index = 4
Selected item  = Sub Node 0-2
Selected index = 3
Selected item  = Sub Node 0-1

However, if (selectedIndex, removeIndex) is changed to (3,4), then the output becomes:

Selected index = 3
Selected item  = Sub Node 0-1
Selected index = 3
Selected item  = Sub Node 0-1

No change in selected index.

In fact, in my limited testing case, as long as selectedIndex is less or equal to removeIndex, then the remove action does not change the selection. Otherwise it changes the selection.

Why does it happen this way? Is there a way to get around this problem?

2

There are 2 best solutions below

2
On BEST ANSWER

It appears it is a bug in java. I reported it to bugs.java.com and it is now listed as JDK-8193442 and currently no fix yet.

A work around is to save the selectedItem before remove TreeItem, and call select after the removal. One need to watch out any changelistener on the selectedItemProperty, though.

1
On

What's happening here is that you have a selected index, not a selected item, and when you remove nodes from the tree, the selected index remains the same, but the item at that index is now different.

Your tree is indexed depth-first, I think, based on what you've printed out, so picture it's index to item map as an array that looks like this: ["Root", "Node 0", "Sub Node 0-0", "Sub Node 0-1", "Sub Node 0-2", "Node 1", "Sub Node 1-1" ... "Sub Node 4-2"].

When you remove from this tree, you're also removing from this (possibly imaginary) array. You remove something, and everything with a greater index than that is shifted down one spot.

In order to handle this, you need to simply do a check to properly update selectIndex after an item is removed, in order to match its new index. You could do something like the following:

if(selectIndex > removeIndex)
    selectIndex--;