1

How can I bind ObjectProperty's attribute (that itself is not a property) to some other property like a TextField's text property without using a ChangeListener?

More specifically:

I would like to make a TextField change an ObjectProperty's attribute.

Sample code:

MapDTO:

public class MapDTO {
    private String cityName;

    public String getCityName() {
        return cityName;
    }

    public void setCityName(String cityName) {
        this.cityName = cityName;
    }
}

MapsManager:

public class MapsManager {

    private static ObjectProperty<MapDTO> map = new SimpleObjectProperty<>();

    public static MapDTO getMap() {
        return map.get();
    }

    public static ObjectProperty<MapDTO> mapProperty() {
        return map;
    }

    public static void setMap(MapDTO map) {
        MapsManager.map.set(map);
    }
}

BindingTestController :

public class BindingTestController {

    private TextField cityNameTF = new TextField();

    private void initialize() {

        // Bind the cityName label to the selected MapsManager mapProperty's cityName   
        cityNameTF.textProperty().bind(Bindings.createStringBinding(
            () -> MapsManager.mapProperty().getValue() == null ? null :
                MapsManager.mapProperty().getValue().getCityName(),
            MapsManager.mapProperty()));
    }
}

I have tried:

Creating a string property from the selected value String attribute but it did not pan out & I wasn't able to find the right way.

cityNameTF.textProperty().bindBidirectional(Bindings.createStringBinding(
() -> selectMapCB.getValue() == null ? null : selectMapCB.getValue().getCityName(), 
selectMapCB.valueProperty()));

Creating a string property from the mapProperty's String attribute.

cityNameTF.textProperty().bindBidirectional(Bindings.createStringBinding(
() -> MapsManager.getMapProperty().getValue() == null ? null : MapsManager.mapProperty().getValue().getCityName(),
MapsManager.mapProperty()));

Both options give the same error:

bindBidirectional (javafx.beans.property.Property<java.lang.String>)
in StringProperty cannot be applied to (javafx.beans.binding.StringBinding)

In both cases replacing bindBidirectional with bind works but then I can't change the text in the TextField. I realized this is because I am binding the TextField's text to the cityName's String. So I thought of binding it one way but in the opposite direction, something like:

MapsManager.mapProperty().????.bind(cityNameTF.textProperty());

But "????" - I don't have a property for the String and I don't know how to create a StringBinding or StringProperty on the fly if that's even possible.

How can I manually create String binding between the ObjectProperty's attribute and another StringProperty?

Julian Broudy
  • 320
  • 3
  • 15
  • 1
    Post a [mcve]. This is a bit of a mess: when you start with the GUI part of the question we don't know what most of the objects are and it seems you're using some 3rd party library. Bindings don't need a GUI, so if your question is about bindings, use pure data structures as in the beginning of the question. – user1803551 Jun 04 '19 at 13:54
  • @user1803551 I added the MapsManager and the MapDTO classes. Is there anything else that should be cleaned or added? (Also, consider the "JFXComboBox" and "JFXTextField" a regular "ComboBox" and "TextField", nothing special there) – Julian Broudy Jun 06 '19 at 13:01
  • the answer is no: the attribute must be an Observable/Value to bind anything to it - without there is no notification on its change – kleopatra Jun 06 '19 at 15:37
  • @kleopatra but I did manage to create another type of binding like so: "mapNameLBL.textProperty().bind(Bindings.createStringBinding(() -> selectMapCB.getValue() == null ? null : selectMapCB.getValue().getVersion() + "", selectMapCB.valueProperty()));" Which means there is a way to "extract" or create a binding on the fly.. no? so isn't there an equivalent opposite action? – Julian Broudy Jun 07 '19 at 22:18
  • 1
    repeating: you need something that notifies on change ... anyway, time vor a [mcve] – kleopatra Jun 08 '19 at 09:08
  • *"Is there anything else that should be cleaned or added?"* Yes, remove everything related to a GUI. Using the classes `MapsManager` and `MapsDTO` (and other data types like those if needed), show what you try to bind to what. Everything before those classes are shown should be removed unless the code is relevant, in which case show how it is so. Also, in the summary you mention a `DTOManager` class that is not shown. The summary is not needed anyway. – user1803551 Jun 08 '19 at 20:26
  • 1
    @user1803551 Done. Is that better? Also, thank you, I am just starting to get the hang of properly asking questions. – Julian Broudy Jun 09 '19 at 00:03
  • 1
    I tried to help a bit with the MCVE (though it's still not runnable), it should capture better what I think you're asking. Basically, all that's important is that you have an object whose field you want to change according to a text field's `text` property (or the other way around?), if I understood you correctly. In the "I have tried" section, I assume that `versionTF` is `cityNameTF`, if so, change that. – user1803551 Jun 09 '19 at 04:10

1 Answers1

0

Your MapDTO is basically a JavaBean - a class with a field and its getter and setter. We'll call such a field a bean property. Since you can't bind to or from these properties, JavaFX provides adapters that bridge them to JavaFX properties. They are located in the package javafx.beans.property.adapter:

Provides various classes that act as adapters between a regular Java Bean property and a corresponding JavaFX Property.

Internally, they function as wrappers of the bean property.

Since your MapDTO contains a String we'll use a JavaBeanStringProperty. Note that:

As a minimum, the Java Bean class must implement a getter and a setter for the property. The class, as well as the getter and a setter methods, must be declared public.

so we obey the requirements.

We create these adapter with their builders, JavaBeanStringPropertyBuilder in this case:

JavaBeanStringProperty cityNameProperty = JavaBeanStringPropertyBuilder.create()
                                              .bean(MapsManager.getMap())
                                              .name("cityName")
                                              .build();

The builders access the data reflectively. Now we can use the created property as usual:

cityNameProperty.bind(cityNameTF.textProperty());

and changes to the text field's text property will change cityName in the MapDTO instance.

user1803551
  • 12,965
  • 5
  • 47
  • 74
  • as-is (the assumed version), it's a javabean as used by web folks - for fx binding, the adapter is not overly useful if the (java) bean property doesn't fire propertyChanges: without those the fx side cannot be updated on changes to the (java) bean side – kleopatra Jun 09 '19 at 09:10