BerndGit
BerndGit

Reputation: 1660

JavaFX: TreeTableView Selection Listener unexpected behaviour

I want to apply an listener to selection changes in my TreeTable View. Anyhow its behaviour is not like expected.

Here is my datamodel:

import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
public class File {
    private StringProperty name = new SimpleStringProperty(this, "name");
    public File (String name)            {nameProperty().set(name);   }
    public StringProperty nameProperty() {return this.name;   }
    @Override public String toString()   {return name.get().toString() ;}
}

And here is my main application:

import javafx.application.Application;
import javafx.collections.ListChangeListener;
import javafx.scene.Scene;
import javafx.scene.control.SelectionMode;
import javafx.scene.control.TreeItem;
import javafx.scene.control.TreeTableColumn;
import javafx.scene.control.TreeTableView;
import javafx.scene.control.cell.TreeItemPropertyValueFactory;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;

public class treeTableViewTest extends Application {
   @Override
    public void start(final Stage stage) {

       // DemoData =========================================================
       File rootFile = new File("/");
       TreeItem<File> child2 = new  TreeItem<File>(new File("Child 2"));

       TreeItem<File> root = new TreeItem<File>(rootFile);
       root.getChildren().add(new  TreeItem<File>(new File("Child 1")));
       root.getChildren().add(child2);
       root.getChildren().add(new  TreeItem<File>(new File("Child 3")));
       root.getChildren().add(new  TreeItem<File>(new File("Child 4")));
       root.getChildren().add(new  TreeItem<File>(new File("Child 5")));
       root.setExpanded(true);
       child2.getChildren().add(new  TreeItem<File>(new File("SubChild 1")));
       child2.getChildren().add(new  TreeItem<File>(new File("SubChild 2")));

       // Layout =========================================================
        final StackPane stackPane = new StackPane();
        TreeTableView<File> treeTable = new TreeTableView<>();

        treeTable.setEditable(true);
        treeTable.getSelectionModel().setSelectionMode(SelectionMode.SINGLE);
        treeTable.getSelectionModel().setCellSelectionEnabled(false);
        treeTable.setRoot(root);

        TreeTableColumn<File,String> fileNameCol = new TreeTableColumn<>("Filename");
        treeTable.getColumns().setAll(fileNameCol);
        fileNameCol.setCellValueFactory(new TreeItemPropertyValueFactory("name"));

        stackPane.getChildren().add(treeTable);
        stage.setScene(new Scene(stackPane,250,250));
        stage.show();

       // Selection Listener ================================================

        treeTable.getSelectionModel().getSelectedItems().addListener(new ListChangeListener<TreeItem<File>>() {

        @Override
        public void onChanged(ListChangeListener.Change<? extends TreeItem<File>> c) {
            while (c.next()) {
                 if (c.wasPermutated()) {
                         for (int i = c.getFrom(); i < c.getTo(); ++i) {
                              //permutate
                         }
                     } else if (c.wasUpdated()) {
                            System.out.println("updated: " + c.toString());
                     } else {
                         for (TreeItem<File>  remitem : c.getRemoved()) {
                             System.out.println("removed: " + remitem.toString());
                         }
                         for (TreeItem<File> additem : c.getAddedSubList()) {
                             System.out.println("added: " + additem.toString());
                         }
                     }
                 for(TreeItem<File> file :  c.getList() ){
                     System.out.println("  -> " + file.toString());
                 }
            }
        }});

     // Random Interactions =========================================================
          treeTable.getSelectionModel().select(3);
          treeTable.getSelectionModel().select(5);
          treeTable.getSelectionModel().clearAndSelect(1);

    }

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

Since I have defined treeTable.getSelectionModel().setSelectionMode(SelectionMode.SINGLE); I would expect that every time I select one item, all others automitically are deselected.

The output of my code is:

added: TreeItem [ value: Child 3 ]
  -> TreeItem [ value: Child 3 ]
added: TreeItem [ value: Child 5 ]
  -> TreeItem [ value: Child 5 ]
removed: TreeItem [ value: Child 5 ]
added: TreeItem [ value: Child 1 ]
  -> TreeItem [ value: Child 1 ]

I would have expected this output:

added: TreeItem [ value: Child 3 ]
  -> TreeItem [ value: Child 3 ]
removed: TreeItem [ value: Child 3 ]   // <- missing in original output
added: TreeItem [ value: Child 5 ]
  -> TreeItem [ value: Child 5 ]
removed: TreeItem [ value: Child 5 ]
added: TreeItem [ value: Child 1 ]
  -> TreeItem [ value: Child 1 ]

Why is the removed Child 3 not directy seen in the listener? Obviously it has been removed since it was not in c.getList() anymore.

Edit

Issue is now sumitted as a bug report here: http://bugs.java.com/bugdatabase/view_bug.do?bug_id=JDK-8175260

Upvotes: 1

Views: 1861

Answers (1)

Maciej Dobrowolski
Maciej Dobrowolski

Reputation: 12122

Unfortunately it is not how this selection model works (it is TreeTableViewArrayListSelectionModel).

Despite being ObservableList, selectedItems property doesn't always notify you about its changes (it is implemented with ReadOnlyUnbackedObservableList which doesn't provide any notifications - it is the selection model what creates and emits them).

If SelectionMode == SINGLE, the selection model, when being ordered to select another row, almost always performs quietClearSelection - which just removes all selections items without notyfying observers about that fact.

So, why does it work properly when manually clicking on the rows? I have checked what method is called after a mouse click on a row - in case of SelectionMode.SINGLE it is always clearAndSelect method.

I am sure that this is a not documented contract, that when selection model is SINGLE all "users" should clear selection before selecting another row (since selecting two or more rows would be illegal).

In my opinion, with SINGLE selection model, you should observe selectedItem property - below you can find a working example:

treeTable.getSelectionModel().selectedItemProperty().addListener(new ChangeListener<TreeItem<File>>() {
    @Override
    public void changed(ObservableValue<? extends TreeItem<File>> observable, TreeItem<File> oldValue, TreeItem<File> newValue) {
        System.out.println(oldValue + "->" + newValue);
    }
});

Upvotes: 2

Related Questions