JavaFx ComboBox binding confusion



I have an I18N implementation that binds JavaFX UI elements through properties, for e.g.:

def translateLabel(l: Label, key: String, args: Any*): Unit =
    l.textProperty().bind(createStringBinding(key, args))

Having a property binding is easy and works well. However I struggle with ComboBox as it takes an ObservableList (of Strings in my case) and I have no idea how to bind my translator functions to that. I am conflicted about the difference between ObservableValue, ObservableList and Property interfaces as they all sound the same.

It has itemsProperty() and valueProperty() however the documentation for these is lacking and vague so I am not sure where they can be used.

What I want to do is have a ComboBox where all elements (or at least the selected / visible one) changes the language dynamically (I18N) as if it was bound, just like a property.

EDIT:

Just to make it easier understand, my current implementation is:

private def setAggregatorComboBox(a: Any): Unit = {

    val items: ObservableList[String] = FXCollections.observableArrayList(
        noneOptionText.getValue,
        "COUNT()",
        "AVG()",
        "SUM()"
    )

    measureAggregatorComboBox.getItems.clear()

    measureAggregatorComboBox.getItems.addAll(items)
}

Where noneOptionText is a StringProperty that’s already bound to a StringBinding that’s translated upon class instantiation in this manner:

def translateString(sp: StringProperty, key: String, args: Any*): Unit =
        sp.bind(createStringBinding(key, args))

Answer

The itemsProperty() is the list of items to show in the combo box popup; it’s value is an ObservableList.

The valueProperty() is the selected item (or the value input by the user if the combo box is editable).

What I’d recommend is to have the data in the combo box be the list of keys, and use custom cells to bind the text in each cell to the translation of those keys. I don’t speak scala, but in Java it looks like:

ComboBox<String> comboBox = new ComboBox<>();
comboBox.getItems().setAll(getAllKeys());

class TranslationCell extends ListCell<String> {

    @Override
    protected void updateItem(String item, boolean empty) {
        super.updateItem(item, empty);
        textProperty().unbind();
        if (empty || item == null) {
            setText("");
        } else {
            textProperty().bind(createStringBinding(item));
        }
    }
}

comboBox.setCellFactory(lv -> new TranslationCell());
comboBox.setButtonCell(new TranslationCell());

Note now that the valueProperty() contains the key for the selected value.

If you really want to bind the items to an ObservableValue<ObservableList<String>> you can do something like:

comboBox.itemsProperty().bind(Bindings.createObjectBinding(() ->
    FXCollections.observableArrayList(...),
    ...));

where the first ... is a varargs of String values, and the second ... is an observable value, changes in which would prompt the list to be recomputed. (So in your case, I’m guessing you have an ObservableValue<Locale> somewhere representing the current locale; you would use that for the second argument.)

In your specific use case (where only the first element of the list is internationalizable), it might be easier simply to use a listener:

comboBox.getItems().setAll(
    noneOptionTest.getValue(), 
    "COUNT()",
    "AVG()",
    "SUM");
noneOptionTest.addListener((obs, oldVal, newVal) ->
    comboBox.getItems().set(0, newVal));

though I agree this is slightly less elegant.

For completeness:

I am conflicted about the difference between ObservableValue, ObservableList and Property interfaces as they all sound the same.

ObservableValue<T>: represents a single value of type T which can be observed (meaning that code can be executed when it changes).

Property<T>: represents a writable ObservableValue<T>; the intention is that implementations would have an actual variable representing the value. It defines additional functionality allowing its value to be bound to other ObservableValue<T>.

So, for example:

DoubleProperty x = new SimpleDoubleProperty(6);
DoubleProperty y = new SimpleDoubleProperty(9);
ObservableValue<Number> product = x.multiply(y);

x and y are both Property<Number>; the implementation of SimpleDoubleProperty has an actual double variable representing this value, and you can do things like y.set(7); to change the value.

On the other hand, product is not a Property<Number>; you can’t change its value (because doing so would violate the binding: the declared invariant that product.getValue() == x.getValue() * y.getValue()); however it is observable, so you can bind to it:

BooleanProperty answerCorrect = new SimpleBooleanProperty();
answerCorrect.bind(product.isEqualTo(42));

etc.

An ObservableList is somewhat different: it is a java.util.List (a collection of elements), and you can observe it to respond to operations on the list. I.e. if you add a listener to an ObservableList, the listener can determine if elements were added or removed, etc.



Source: stackoverflow