Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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, e.g. 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); 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.

like image 211
Vladimir Matveev Avatar asked Nov 13 '22 03:11

Vladimir Matveev


1 Answers

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);
    }
}
like image 69
Sergey Grinev Avatar answered Nov 15 '22 13:11

Sergey Grinev