We know that ListExpression has a method ObjectBinding<E> valueAt(ObservableIntegerValue). We can use this method to listen to an element of a ListProperty exactly.
I expect it will both bind to the ListProperty and the ObservableNumberValue. So either the list changes or the observable number value changes will make the binding invalid and recompute. But in the following code, the binding is computed only once! (Actually twice, if we don't ignore the initial computation)
The label will display a random string at the beginning. And the property will have 100 Bean as the initial value. If we click button1, the indexProperty will increase by 1. If we click button2, the Bean located at the current index of the ListProperty will change. Both effects will make the binding invalid and recompute the label text.
But in practice, the text will change the first time when one button is clicked. And will not change anymore.
I'm using Liberica JDK17 which contains jmods of JavaFX by default.
class FixmeApp : Application() {
companion object {
fun genRandomDouble(): Double = Math.random() * 10000
fun genRandomString(): String = genRandomDouble().roundToInt().toString(36)
}
class Bean {
val stringProperty = SimpleStringProperty(genRandomString())
val doubleProperty = SimpleDoubleProperty(genRandomDouble())
}
val property: ListProperty<Bean> = SimpleListProperty(FXCollections.observableArrayList(Bean()))
override fun start(primaryStage: Stage) {
property.addAll((0..100).map { Bean() })
val indexProperty = SimpleIntegerProperty(0)
val label = Label().apply {
textProperty().bind(Bindings.createStringBinding(
{ genRandomString() },
property.valueAt(indexProperty)
))
}
val button1 = Button("Change Index").apply {
setOnAction {
indexProperty.set(indexProperty.get() + 1)
}
}
val button2 = Button("Change Bean").apply {
setOnAction {
property[indexProperty.get()] = Bean()
}
}
val scene = Scene(BorderPane().apply {
center = label
bottom = HBox(button1, button2)
})
primaryStage.scene = scene
primaryStage.show()
}
}
fun main() {
Application.launch(FixmeApp::class.java)
}
By the way, if we change the binding dependencies from property.valueAt(indexProperty) to property, indexProperty, the code will run as we expected.
In my program, the binding will return the property of Bean at the location indexProperty.get() of property
Edit (by James_D):
To increase the audience for this question, here is (to the best of my knowledge) a translation of this example to Java.
import javafx.application.Application;
import javafx.beans.binding.Bindings;
import javafx.beans.property.*;
import javafx.collections.FXCollections;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.stage.Stage;
import java.util.Random;
public class FixMeApp extends Application {
private final Random rng = new Random();
private final ListProperty<Bean> property = new SimpleListProperty<>(FXCollections.observableArrayList());
@Override
public void start(Stage stage) throws Exception {
for (int i = 0 ; i < 100 ; i++) property.add(new Bean());
var index = new SimpleIntegerProperty(0);
var label = new Label();
label.textProperty().bind(Bindings.createStringBinding(
this::generateRandomString,
property.valueAt(index)
));
var button1 = new Button("Change Index");
button1.setOnAction(e -> index.set(index.get() + 1));
var button2 = new Button("Change bean");
button2.setOnAction(e -> property.set(index.get(), new Bean()));
var root = new BorderPane(label);
root.setBottom(new HBox(button1, button2));
var scene = new Scene(root);
stage.setScene(scene);
stage.show();
}
private double generateRandomDouble() {
return rng.nextDouble() * 10000 ;
}
private String generateRandomString() {
return Integer.toString((int) generateRandomDouble());
}
class Bean {
private StringProperty stringProperty = new SimpleStringProperty(generateRandomString());
private DoubleProperty doubleProperty = new SimpleDoubleProperty(generateRandomDouble());
StringProperty stringProperty() { return stringProperty; }
DoubleProperty doubleProperty() { return doubleProperty; }
}
public static void main(String[] args) {
Application.launch(args);
}
}