JavaFX complex string binding

2.1k Views Asked by At

I'm new to JavaFX and was wondering if the Bindings API allowed an easier way to achieve the following. Consider a model that contains a database that may be null (because the database loads asynchronously) and a view that displays a label status reflecting the state of the database. If it is null it should say something like "Loading..." and if it isn't it should display how many items are in the database. It also would be great if the status could reflect the size of the database as it grows or shrinks.

So far, I understand that I could bind an integer property (size of the database) to the text property of the label by using a converter. This is fine, but I want the label to display more than the number. A localized string like "Loaded {0} items" precisely. And let's not forget that the database may still be null.

This is the solution I have in place

@Override
public void initialize(URL url, ResourceBundle bundle) {
    // Initialize label with default value
    status();
    model.databaseProperty().addListener((obs, old, neu) -> {
        // Update label when database is no longer null
        status();
        // Update label when size of database changes
        neu.sizeProperty().addListener(x -> status());
    });
}

public void status() {
    if (model.database() == null) {
        status.setText(bundle.getString("status.loading"));
    } else {
        String text = bundle.getString("status.ready");
        int size = model.database().size();
        text = new MessageFormat(text).format(size);
        status.setText(text);
    }
}

It works, but is there a way to do it with a chain of bindings, or at least part of it? I've seen how powerful (and lenghty) boolean bindings can be but I'm not sure something as flexible is possible with string bindings.

1

There are 1 best solutions below

6
On BEST ANSWER

You can use Bindings.when, which is essentially a dynamic if/then binding:*

status.textProperty().bind(
    Bindings.when(model.databaseProperty().isNull())
        .then(bundle.getString("status.loading"))
        .otherwise(
            Bindings.selectInteger(model.databaseProperty(), "size").asString(
                bundle.getString("status.ready")))
);

However, the above assumes bundle.getString("status.ready") returns a java.util.Formatter string, not a MessageFormat string. In other words, it would need to be "Loaded %,d items" rather than "Loaded {0,number,integer} items".

Bindings doesn’t have built-in support for MessageFormat, but if you really want to stick with MessageFormat (which is a legitimate requirement, as there are things MessageFormat can do which Formatter cannot), you can create a custom binding with Bindings.createStringBinding:

MessageFormat statusFormat = new MessageFormat(bundle.getString("status.ready"));

status.textProperty().bind(
    Bindings.when(model.databaseProperty().isNull())
        .then(bundle.getString("status.loading"))
        .otherwise(
            Bindings.createStringBinding(
                () -> statusFormat.format(new Object[] { model.getDatabase().getSize() }),
                model.databaseProperty(),
                Bindings.selectInteger(model.databaseProperty(), "size")))
);

* Actually, it’s more like the ternary ?: operator.