Sabemos que ListExpression tiene un método ObjectBinding<E> valueAt(ObservableIntegerValue) . Podemos usar este método para escuchar exactamente un elemento de ListProperty .
Espero que se una tanto a ListProperty como a ObservableNumberValue . Por lo tanto, la lista cambia o los cambios en el valor del número observable harán que el enlace sea inválido y se vuelva a calcular. ¡Pero en el siguiente código, el enlace se calcula solo una vez! (En realidad dos veces, si no ignoramos el cálculo inicial)
La etiqueta mostrará una cadena aleatoria al principio. Y la property tendrá 100 Bean como valor inicial. Si hacemos clic en button1 , indexProperty aumentará en 1. Si hacemos clic en button2 , el Bean ubicado en el índice actual de ListProperty cambiará. Ambos efectos invalidarán el enlace y volverán a calcular el texto de la etiqueta.
Pero en la práctica, el texto cambiará la primera vez que se haga clic en un botón. Y no cambiará más.
Estoy usando Liberica JDK17 que contiene jmods de JavaFX por defecto.
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) } Por cierto, si cambiamos las dependencias vinculantes de property.valueAt(indexProperty) a property, indexProperty , el código se ejecutará como esperábamos.
En mi programa, el enlace devolverá la propiedad de Bean en la ubicación indexProperty.get() de propiedad
Para aumentar la audiencia de esta pregunta, aquí hay (hasta donde yo sé) una traducción de este ejemplo a 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); } }Voy a responder en Java, ya que estoy más familiarizado con él. El razonamiento también se aplica a kotlin.
Bindings.createStringBinding(function, dependencies) crea un enlace que se invalida cada vez que se invalida cualquiera de las dependencias. Aquí "invalidado" significa "cambios de un estado válido a un estado no válido". El problema con su código es que define la dependencia como property.valueAt(index) , que es un enlace al que no tiene otra referencia.
Cuando cambia el índice o el bean en ese índice en la lista, el enlace deja de ser válido. Dado que nunca vuelve a calcular el valor de ese enlace, nunca vuelve a ser válido (es decir, nunca contiene ni ha devuelto un valor válido). Por lo tanto, los cambios posteriores no cambiarán su estado de validación (simplemente sigue siendo inválido; no puede pasar de válido a inválido).
Cambiando el código a
label.textProperty().bind(Bindings.createStringBinding( () -> property.valueAt(index).get().stringBinding().get(), property.valueAt(index) ));no ayuda: tiene un enlace utilizado para la dependencia (cuyo valor nunca se calcula, por lo que nunca se devuelve a un estado válido) y un enlace diferente cada vez que se calcula el valor. El valor del enlace utilizado como dependencia nunca se calcula, por lo que nunca se devuelve a un estado válido y, en consecuencia, nunca se puede volver a invalidar.
Sin embargo
ObjectBinding<Bean> bean = property.valueAt(index); label.textProperty().bind(Bindings.createStringBinding( () -> bean.get().stringProperty().get(), bean )); trabajará. Aquí, el cálculo del texto obliga a bean a calcular su valor actual, devolviéndolo a un estado válido. Luego, los cambios posteriores en el índice o la lista invalidarán bean nuevamente, lo que desencadenará un nuevo cálculo del enlace al que está vinculado el text .
Creo que la traducción kotlin de esto es
val bean = property.valueAt(indexProperty) val label = Label().apply { textProperty().bind(Bindings.createStringBinding( { bean.value.stringProperty.value }, bean )) } Puedes experimentar con otras variaciones. Este no se actualiza más de una vez, porque bean nunca se valida:
ObjectBinding<Bean> bean = property.valueAt(index); label.textProperty().bind(Bindings.createStringBinding( this::generateRandomString, bean ));Mientras que este fuerza la validación, por lo que siempre se actualiza:
ObjectBinding<Bean> bean = property.valueAt(index); label.textProperty().bind(Bindings.createStringBinding( () -> { bean.get(); return generateRandomString(); }, bean ));Gracias a @James_D, no solo sé por qué este código no se ejecutó como se esperaba, sino que también resolví otro problema relacionado con los enlaces.
TL; DR: JavaFX no validará automáticamente los enlaces cuando las dependencias dejen de ser válidas . Debe validarlos obteniendo el valor o de alguna manera.
En el siguiente código, bean dejará de ser válido, ya sea la property o los cambios index , y se calculará StringBinding . Después del cálculo, el bean aún no es válido porque no lo calculamos para validarlo. Entonces, la próxima vez que cambien las dependencias del bean , StringBinding no volverá a calcular.
ObjectBinding<Bean> bean = property.valueAt(index); label.textProperty().bind(Bindings.createStringBinding( this::generateRandomString, bean )); Pero para este, es diferente. Aunque nunca usamos el valor del bean vinculante, lo validamos obteniendo. Entonces el código se ejecuta como se esperaba.
ObjectBinding<Bean> bean = property.valueAt(index); label.textProperty().bind(Bindings.createStringBinding( () -> { bean.get(); return generateRandomString(); }, bean )); Y para otra situación, ¡cuidado! May bean ser válido y, durante el cálculo, el flag era false , por lo que bean no se validó . Si no validamos bean alguna manera en algún lugar antes de que las dependencias de bean vuelvan a ser inválidas la próxima vez, el enlace no se calculará .
ObjectBinding<Bean> bean = property.valueAt(index); label.textProperty().bind(Bindings.createStringBinding( () -> { if (flag) return bean.value else return generateRandomString(); }, bean ));