简体   繁体   中英

JavaFX force recompute binding

I wanted to achieve something resembling functional reactive programming in JavaFX and I thought that since JavaFX already supports listeners and bindings between properties it should be fairly easy, so I have created small framework for bindings with conversion, eg I now am able to do something like this (example in Scala, but it should be possible to understand what I mean):

val property1: Property[String]
val property2: Property[Path]

Bindings.Conversions
    .bindUni(property1).to(property2)
    .using(p => p.getFileName.toString)
    .connect()

Here I bind value of property2 (which is a java.nio.file.Path ) through a conversion function which takes the last part of the path and converts to a string to the property1 (which is string).

The implementation of this is really simple (even for bidirectional bindings; I simply took some code from openjfx BidirectionalBinding class, translated it to Scala and adapted it for conversions), and I wonder why there is no such thing in JavaFX already.

This all works well, and I even can create complex chains of such bindings. All is OK unless conversion function depends on some external state.

Suppose, for example, that you have the following chain of bindings:

Text field value  -1->  intermediate java.nio.file.Path  -2->  another String  -->  Label

When text field changes, Path and String are recomputed automatically, and the value of the String property is written to the label. All is wonderful. But suppose that -2-> conversion should depend on toggled state of some checkbox:

                                       Checkbox state  ---+
                                                          |
Text field value  -1->  intermediate java.nio.file.Path  -2->  another String  -->  Label

That is, when checkbox is checked, transformation should be slightly different.

Direct implementation of such construction won't work, obviously, because change of checkbox state does not toggle recomputation of the transformation chain. However, I found that JavaFX does not provide any means for forcing change events. I tried overriding SimpleStringProperty , for example, and exposing its fireValueChangedEvent() method, but this didn't help. Currently I'm doing something like textField.setText(""); textField.setText(oldValue); textField.setText(""); textField.setText(oldValue); but this is very ugly and is not the right way, obviously.

Am I missing something, and it is really possible to do what I want to do, or there is no such thing at all and I'm completely screwed here?

If the answer is no, then I think this severly damages expressiveness of the whole framework. I understand that I really can do what I want to with a number of listeners, but this will be ugly, and I want to make the whole thing as general as possible.

You can listen to the CheckBox#selectedProperty() the same way you listen to String properties. See next example:

public class ConditionalBind extends Application {

    Label label = new Label();
    TextField tf = new TextField("hi");
    CheckBox cb = new CheckBox("lowerize");

    @Override
    public void start(Stage primaryStage) {

        label.textProperty().bind(new StringBinding() {

            {
                bind(tf.textProperty(), cb.selectedProperty());
            }

            @Override
            protected String computeValue() {
                return cb.isSelected() ? tf.getText().toLowerCase() : tf.getText();
            }
        });

        VBox root = new VBox(10);
        root.getChildren().addAll(label, cb, tf);
        primaryStage.setScene(new Scene(root, 300, 250));
        primaryStage.show();
    }

    public static void main(String[] args) {
        launch(args);
    }
}

As as "ugly" workaround I ended up randomly changing the values without changing the meaning. Eg for a double value I'd add as small random amount to force a change or for a string value I'd toggle and invisible space. That's all not nice but will get the effect of triggering the property change for a start.

Things get worse in a multithreaded environment. There it can happen that the bounded property will not get the change trigger if the binding happens to be just slightly after the initial setting of the property. If you now set the property to the same value the value will be considered as not changed and the bounded property will not pick up the initial value but stay at the null or default value. Very ugly!

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM