Reputation: 1660
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
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