Reputation: 13
Here is my very simple example. (down...)
What it does: TreeView is populated with three Persons, when TreeItem is selected Textfield will be populated with name of the selected person. If user changes the name and loses focus from textfield (or hit Enter) it will change Person's name and "updates" TreeView item's display text.
My problem is with this line:
selectedItem.valueProperty().set(new Person(selectedPerson.getName(), selectedPerson.getAge()));
Only thanks to that line I'm able to refresh the TreeView. Without that, I was able to refresh the treeView only during e.g. resizing the window (or collapse & expand of a root item).
I think this solution is very silly and there must be a way how to code it better. I cannot create a new instance of Person every time, that's unacceptable for me.
I also tried to fire an event for treeView but this approach messes up with the focus and it is also a silly solution too. I found also solution like this:
treeView.getRoot().getChildren().set(treeView.getSelectionModel().getSelectedIndex(), new TreeItem<MainAppTF.Person>(updatedPerson));
which is bad solution too.
Maybe the solution can be found using property binding, but binding is a "realtime/instant" change, unless I play with bind() unbind(), but perhaps there is something in Binding with which I'm not familiar yet. (I prefer listeners)
My main goal is to "commit" my change on focus change or on key event and update the TreeView right after that moment.
PS: I would be very grateful for real examples.
public class MainAppTF extends Application {
private TreeView<Person> treeView;
private final TreeItem<Person> rootNode = new TreeItem<Person>(new Person("Root", 0));
private TextField textField;
@Override
public void start(Stage stage) {
VBox box = new VBox();
Scene scene = new Scene(box, 400, 400);
treeView = new TreeView<Person>(rootNode);
treeView.setShowRoot(false);
rootNode.setExpanded(true);
List<TreeItem<Person>> list = new ArrayList<>();
list.add(new TreeItem<Person>(new Person("Adam", 20)));
list.add(new TreeItem<Person>(new Person("Eva", 19)));
list.add(new TreeItem<Person>(new Person("Carl", 30)));
rootNode.getChildren().setAll(list);
textField = new TextField("");
attachListeners();
box.getChildren().add(treeView);
box.getChildren().add(textField);
VBox.setMargin(treeView, new Insets(10));
VBox.setMargin(textField, new Insets(10));
stage.setScene(scene);
stage.show();
}
private void attachListeners() {
treeView.getSelectionModel().selectedItemProperty().addListener(new ChangeListener<TreeItem<Person>>() {
@Override
public void changed(ObservableValue<? extends TreeItem<Person>> observable, TreeItem<Person> oldValue, TreeItem<Person> newValue) {
textField.setText(newValue.getValue().getName());
}
});
textField.focusedProperty().addListener(new ChangeListener<Boolean>() {
@Override
public void changed(ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean newValue) {
if (!newValue) {
updateTreeViewItem();
}
}
});
textField.setOnAction(new EventHandler<ActionEvent>() {
@Override
public void handle(ActionEvent event) {
updateTreeViewItem();
}
});
}
private void updateTreeViewItem() {
TreeItem<Person> selectedItem = treeView.getSelectionModel().getSelectedItem();
Person selectedPerson = selectedItem.getValue();
selectedPerson.nameProperty().set(textField.getText());
// FIXME This is silly! There must be another way!
selectedItem.valueProperty().set(new Person(selectedPerson.getName(), selectedPerson.getAge()));
}
public static void main(String[] args) {
Application.launch(args);
}
private class Person {
private StringProperty name;
private int age;
public Person() {
this(null, 0);
}
public Person(String name, int age) {
this.name = new SimpleStringProperty(name);
this.age = age;
}
public StringProperty nameProperty() {
return name;
}
public String getName() {
return name.getValue();
}
public void setName(String name) {
this.name.setValue(name);
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return getName() + " - " + getAge();
}
}
}
Upvotes: 1
Views: 5395
Reputation: 209319
You want the TreeItem
to receive TreeModificationEvent
s when the name of the Person
wrapped by the TreeItem
changes.
You can do this by attaching a listener to the nameProperty()
of the person, and then firing the appropriate event:
TreeItem<Person> treeItem = new TreeItem<>(person);
ChangeListener<String> nameListener = (obs, oldName, newName) -> {
TreeModificationEvent<Person> event = new TreeModificationEvent<>(TreeItem.valueChangedEvent(), treeItem);
Event.fireEvent(treeItem, event);
};
person.nameProperty().addListener(nameListener);
If there is a possibility you ever change the value wrapped by the TreeItem
(treeItem.setValue(new Person(...))
), then you need to make sure you remove the listener from the old person and add it to the new one. So it might be prudent to also do:
treeItem.valueProperty().addListener((obs, oldValue, newValue) -> {
if (oldValue != null) {
oldValue.nameProperty().removeListener(nameListener);
}
if (newValue != null) {
newValue.nameProperty().addListener(nameListener);
}
});
Obviously, you don't want to repeat this code every time, so you can either create a utility method:
private TreeItem<Person> createTreeItem(Person person) {
TreeItem<Person> treeItem = new TreeItem<>(person);
ChangeListener<String> nameListener = (obs, oldName, newName) -> {
TreeModificationEvent<Person> event = new TreeModificationEvent<>(TreeItem.valueChangedEvent(), treeItem);
Event.fireEvent(treeItem, event);
};
person.nameProperty().addListener(nameListener);
treeItem.valueProperty().addListener((obs, oldValue, newValue) -> {
if (oldValue != null) {
oldValue.nameProperty().removeListener(nameListener);
}
if (newValue != null) {
newValue.nameProperty().addListener(nameListener);
}
});
return treeItem ;
}
and then do
list.add(createTreeItem(new Person("Adam", 20)));
list.add(createTreeItem(new Person("Eva", 19)));
list.add(createTreeItem(new Person("Carl", 30)));
Or you can create a subclass of TreeItem<Person>
:
private class PersonTreeItem extends TreeItem<Person> {
private ChangeListener<String> nameListener = (obs, oldName, newName) -> {
TreeModificationEvent<Person> event = new TreeModificationEvent<>(TreeItem.valueChangedEvent(), this);
Event.fireEvent(this, event);
};
public PersonTreeItem(Person person) {
super(person);
person.nameProperty().addListener(nameListener);
this.valueProperty().addListener((obs, oldValue, newValue) -> {
if (oldValue != null) {
oldValue.nameProperty().removeListener(nameListener);
}
if (newValue != null) {
newValue.nameProperty().addListener(nameListener);
}
});
}
}
and do
list.add(new PersonTreeItem(new Person("Adam", 20)));
list.add(new PersonTreeItem(new Person("Eva", 19)));
list.add(new PersonTreeItem(new Person("Carl", 30)));
(The choice between the two is essentially just a matter of style.)
SSCCE:
import java.util.ArrayList;
import java.util.List;
import javafx.application.Application;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.event.ActionEvent;
import javafx.event.Event;
import javafx.event.EventHandler;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.TextField;
import javafx.scene.control.TreeItem;
import javafx.scene.control.TreeItem.TreeModificationEvent;
import javafx.scene.control.TreeView;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class MainAppTF extends Application {
private TreeView<Person> treeView;
private final TreeItem<Person> rootNode = new TreeItem<Person>(new Person("Root", 0));
private TextField textField;
@Override
public void start(Stage stage) {
VBox box = new VBox();
Scene scene = new Scene(box, 400, 400);
treeView = new TreeView<Person>(rootNode);
treeView.setShowRoot(false);
rootNode.setExpanded(true);
List<TreeItem<Person>> list = new ArrayList<>();
list.add(createTreeItem(new Person("Adam", 20)));
list.add(createTreeItem(new Person("Eva", 19)));
list.add(createTreeItem(new Person("Carl", 30)));
rootNode.getChildren().setAll(list);
textField = new TextField("");
attachListeners();
box.getChildren().add(treeView);
box.getChildren().add(textField);
VBox.setMargin(treeView, new Insets(10));
VBox.setMargin(textField, new Insets(10));
stage.setScene(scene);
stage.show();
}
private void attachListeners() {
treeView.getSelectionModel().selectedItemProperty().addListener(new ChangeListener<TreeItem<Person>>() {
@Override
public void changed(ObservableValue<? extends TreeItem<Person>> observable, TreeItem<Person> oldValue, TreeItem<Person> newValue) {
textField.setText(newValue.getValue().getName());
}
});
textField.focusedProperty().addListener(new ChangeListener<Boolean>() {
@Override
public void changed(ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean newValue) {
if (!newValue) {
updateTreeViewItem();
}
}
});
textField.setOnAction(new EventHandler<ActionEvent>() {
@Override
public void handle(ActionEvent event) {
updateTreeViewItem();
}
});
}
private void updateTreeViewItem() {
TreeItem<Person> selectedItem = treeView.getSelectionModel().getSelectedItem();
Person selectedPerson = selectedItem.getValue();
selectedPerson.nameProperty().set(textField.getText());
// FIXME This is silly! There must be another way!
// selectedItem.valueProperty().set(new Person(selectedPerson.getName(), selectedPerson.getAge()));
}
public static void main(String[] args) {
Application.launch(args);
}
private TreeItem<Person> createTreeItem(Person person) {
TreeItem<Person> treeItem = new TreeItem<>(person);
ChangeListener<String> nameListener = (obs, oldName, newName) -> {
TreeModificationEvent<Person> event = new TreeModificationEvent<>(TreeItem.valueChangedEvent(), treeItem);
Event.fireEvent(treeItem, event);
};
person.nameProperty().addListener(nameListener);
treeItem.valueProperty().addListener((obs, oldValue, newValue) -> {
if (oldValue != null) {
oldValue.nameProperty().removeListener(nameListener);
}
if (newValue != null) {
newValue.nameProperty().addListener(nameListener);
}
});
return treeItem ;
}
private class Person {
private StringProperty name;
private int age;
public Person() {
this(null, 0);
}
public Person(String name, int age) {
this.name = new SimpleStringProperty(name);
this.age = age;
}
public StringProperty nameProperty() {
return name;
}
public String getName() {
return name.getValue();
}
public void setName(String name) {
this.name.setValue(name);
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return getName() + " - " + getAge();
}
}
}
Upvotes: 6