JavaFX Bind UI to a nested class's property

711 Views Asked by At

I have a class called Passage that contains a non-observable class called Action. And Action contains a SimpleStringProperty.

class Passage {

    Action action = null;

    Action getAction() { return action; }

    void setAction(Action action) { this.action = action; }

    boolean hasAction() { return action != null; }
}

class Action {

    StringProperty myStringProperty = new SimpleStringProperty();

    StringProperty getStringProperty() { return myStringProperty; }

    // and other methods, many of which modify myStringProperty
}

I would like to bind that string property to a UI Label. It seems like it should be a straightforward thing to do:

label.textProperty.bind(passage.getAction().getStringProperty());

However, Action could sometimes be null. I've tried the following:

label.textProperty.bind(passage.hasAction() ? passage.getAction().getStringProperty() : new SimpleStringProperty("") );

Then, if Action starts out null (and therefore the label starts out blank) and then the Action is assigned to something non-null (i.e. setAction() is called), the label does not update to reflect the change.

Do I need to make Action observable? See: Binding to javafx properties of a object that can be null

The solution above uses monadic operations, which I'm not familiar with. So I read this helpful blog post: http://tomasmikula.github.io/blog/2014/03/26/monadic-operations-on-observablevalue.html

I have tried doing this binding using EasyBind.

I created a new class ObservableAction that wraps an Action, making it an observable value (though I'm not sure I did this step correctly):

import javafx.beans.binding.ObjectBinding;

public class ObservableAction extends ObjectBinding <Action>
{
    private final Action value;

    public ObservableAction(Action action) {
        this.value = action;
    }

    @Override
    public Action computeValue()
    {
        return value;
    }
}

Then I have the following code in my ViewController (again, not sure I've done this correctly):

MonadicObservableValue<Action> monadicAction = EasyBind.monadic(new ObservableAction(passage.getAction()));

actionLabel.textProperty().bind(monadicAction.flatMap(action -> action.getTextProperty()).orElse(""));

The result is the same as I've experienced before: it works fine when the Action is not null. However if the Action starts null and then becomes non-null, the label is not updated.

1

There are 1 best solutions below

1
On BEST ANSWER

I would recommend to have a property in Passage which will be binded with action's property. Once you set a new action you bind it's property and use only Passage's one.

class Passage {
    private Action action;

    private StringProperty actionMyStringProperty = new SimpleStringProperty();

    void setAction(Action action) {
        this.action = action;

        // Unbind the previous binding if any (it is safe when nothing to unbind).
        actionMyStringProperty.unbind();

        if (action != null){
            actionMyStringProperty.bind(action.getStringProperty());
        }
    }

    public StringProperty actionMyStringProperty() {
        return actionMyStringProperty;
    }
}

class Action {
    private StringProperty myStringProperty = new SimpleStringProperty();

    StringProperty getStringProperty() {
        return myStringProperty;
    }
}

On client side:

label.textProperty.bind(passage.actionMyStringProperty());