Binding to JavaFX properties of an object that can be switched
Personally, I do not find the code you have written in your event handler to be that unwieldy or kludgy. This is the sort of thing that event handlers typically do in GUI's, imo.
Ask yourself, though ... is binding really necessary in your circumstance?
If you must have real-time updates for the edits you've made in one panel to be reflected in another then you have probably implemented the easiest solution. There are difficulties inherent in this kind of UI design however and it may not be the best for all situations. What if the user needs to cancel the edits he's made? Do you have a method for rolling back the edits if he's changed his mind? Sometimes, real-time changes from editing are not desireable and in such cases binding data model objects to UI objects may not be a good idea.
It seems this is not something that can be done more elegantly in JavaFX. Binding and unbinding seems the cleanest way.
I did implement one way to do this myself. Not sure if I'll eventually end up using it (as it just replaces the code duplication with hard to read code). But it works, and it is an answer to my own question, so I've added it here.
The new PersonEditor class:
public class PersonEditor implements Initializable {
private SelectedObjectPropertyBinder<Person> selectedObjectPropertyBinder =
new SelectedObjectPropertyBinder<Person>();
@FXML private TextField nameField;
@FXML private TextField ageField;
@FXML private TextField heightField;
@Override
public void initialize(URL url, ResourceBundle rb) {
selectedObjectPropertyBinder.getBinders().add(
new ObjectPropertyBindHelper<Person>(nameField.textProperty()) {
@Override public Property objectProperty(Person p)
{ return p.nameProperty(); }
});
selectedObjectPropertyBinder.getBinders().add(
new ObjectPropertyBindHelper<Person>(ageField.textProperty()) {
@Override public Property objectProperty(Person p)
{ return p.ageProperty(); }
});
selectedObjectPropertyBinder.getBinders().add(
new ObjectPropertyBindHelper<Person>(heightField.textProperty()) {
@Override public Property objectProperty(Person p)
{ return p.heightProperty(); }
});
}
public void setSelection(ObjectProperty<Person> selectedPersonProperty) {
selectedObjectPropertyBinder.
setSelectedObjectProperty(selectedPersonProperty);
}
}
The helper classes:
public class SelectedObjectPropertyBinder<T> implements ChangeListener<T> {
private List<ObjectPropertyBindHelper<T>> binders =
new ArrayList<ObjectPropertyBindHelper<T>>();
public void setSelectedObjectProperty(Property<T> selectionProperty) {
selectionProperty.addListener(this);
}
public List<ObjectPropertyBindHelper<T>> getBinders() {
return binders;
}
@Override
public void changed(ObservableValue<? extends T> observable,
T oldVal, T newVal) {
if (oldVal != null)
for (ObjectPropertyBindHelper b : binders)
b.unbindBi(oldVal);
if (newVal != null)
for (ObjectPropertyBindHelper b : binders)
b.bindBi(newVal);
}
}
public abstract class ObjectPropertyBindHelper<T> {
private Property boundProperty;
public ObjectPropertyBindHelper(Property boundProperty) {
this.boundProperty = boundProperty;
}
public void bindBi(T o) {
boundProperty.bindBidirectional(objectProperty(o));
}
public void unbindBi(T o) {
boundProperty.unbindBidirectional(objectProperty(o));
}
public abstract Property objectProperty(T t);
public Property getBoundProperty() {
return boundProperty;
}
}
As scottb pointed out in his answer, binding link this is not always what you want anyway. If you want to be able to cancel/commit changes, you could implement that using something like this as well (but it probably will be even harder to read the resulting code).