Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Binding to JavaFX properties of an object that can be switched

Tags:

javafx-2

Binding to properties of an object which is itself wrapped in a property seems like something one does a lot in typical applications, is there a better way to do this in JavaFX than what I do below?

Some more details to explain: I want to make GUI in JavaFX 2.2, for managing a number of items. I've created a small example to test everything, in which the items are persons. The set of persons is shown in a custom way (not a list or tree, but I don't think that matters here), and I can select a single one.

In a side panel I can edit the currently selected person. Updates are immediately visible in the set of persons, and when I select another person, the edit panel is updated.

JavaFX's bidirectional binding seems perfect for this purpose. I currently have this for the fx:controller of the "person editing" Pane:

public class PersonEditor implements ChangeListener<Person> {
    @FXML private TextField nameField;
    @FXML private TextField ageField;
    @FXML private TextField heightField;

    public void setSelection(ObjectProperty<Person> selectedPersonProperty) {
        selectedPersonProperty.addListener(this);
    }

    @Override
    public void changed(ObservableValue<? extends Person> observable, Person oldVal, Person newVal) {
        if (oldVal != null) {
            nameField.textProperty().unbindBidirectional(oldVal.nameProperty());
            ageField.textProperty().unbindBidirectional(oldVal.ageProperty());
            heightField.textProperty().unbindBidirectional(oldVal.heightProperty());
        }
        if (newVal != null) {
            nameField.textProperty().bindBidirectional(newVal.nameProperty());
            ageField.textProperty().bindBidirectional(newVal.ageProperty());
            heightField.textProperty().bindBidirectional(newVal.heightProperty());
        }
    }
}

I'm wondering if there is a nicer way, perhaps something in JavaFX to do bind to properties of an object that can change? I don't like the fact that I have to manually unbind all properties, it feels like duplicate code. Or is this as simple as it can be in JavaFx?

like image 856
Coder Nr 23 Avatar asked May 16 '13 14:05

Coder Nr 23


2 Answers

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.

like image 132
scottb Avatar answered Nov 14 '22 09:11

scottb


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).

like image 38
Coder Nr 23 Avatar answered Nov 14 '22 08:11

Coder Nr 23