JavaFX valueAt()绑定只计算一次



我们知道ListExpression有一个方法ObjectBinding<E> valueAt(ObservableIntegerValue)。我们可以使用这个方法来精确地监听ListProperty的一个元素。

我预计它将同时绑定到ListPropertyObservableNumberValue。因此,列表更改或可观察的数值更改都将使绑定无效并重新计算。但是在下面的代码中,绑定只计算一次!(实际上是两次,如果我们不忽略初始计算的话(

标签将在开头显示一个随机字符串。并且property将具有100Bean作为初始值。如果单击button1,则indexProperty将增加1。如果我们单击button2,位于ListProperty的当前索引处的Bean将发生更改。这两种效果都会使绑定无效,并重新计算标签文本。

但在实践中,当第一次点击一个按钮时,文本会发生变化。而且不会再改变了。

我使用的是Liberica JDK17,默认情况下它包含JavaFX的jmods。

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)
}

顺便说一句,如果我们将绑定依赖项从property.valueAt(indexProperty)更改为property, indexProperty,代码将按预期运行。

在我的程序中,绑定将在propertyindexProperty.get((位置返回Bean的属性


编辑(James_D(:

为了增加这个问题的受众,这里(据我所知(将这个例子翻译成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);
}
}

我将用Java回复,因为我更熟悉它。这一推理也适用于kotlin。

CCD_ 12创建一个绑定,该绑定在任何依赖项失效时都会失效。这里";无效";是指";从有效状态变为无效状态";。代码的问题在于,您将依赖项定义为property.valueAt(index),这是一个没有其他引用的绑定。

当您更改列表中该索引处的索引或bean时,绑定将变为无效。由于您再也不会计算该绑定的值,它再也不会变为有效(即,它永远不会保持或返回有效值(。因此,后续的更改不会更改其验证状态(它仍然是无效的;它无法从有效转换为无效(。

将代码更改为

label.textProperty().bind(Bindings.createStringBinding(
() -> property.valueAt(index).get().stringBinding().get(),
property.valueAt(index)
));

没有帮助:您有一个用于依赖项的绑定(其值从未计算过,因此它从未返回到有效状态(,每次计算值时都有一个不同的绑定。用作依赖项的绑定的值永远不会被计算,因此它永远不会返回到有效状态,因此永远不会再次失效。

然而

ObjectBinding<Bean> bean = property.valueAt(index);
label.textProperty().bind(Bindings.createStringBinding(
() -> bean.get().stringProperty().get(),
bean
));

将起作用。这里,文本的计算迫使bean计算其当前值,将其返回到有效状态。然后,对索引或列表的后续更改将再次使bean无效,从而触发text所绑定的绑定的重新计算。

我认为这是科特林翻译的


val bean = property.valueAt(indexProperty)
val label = Label().apply {
textProperty().bind(Bindings.createStringBinding(
{ bean.value.stringProperty.value },
bean
))
}

你可以尝试其他变体。这个更新不会超过一次,因为bean从未经过验证:

ObjectBinding<Bean> bean = property.valueAt(index);
label.textProperty().bind(Bindings.createStringBinding(
this::generateRandomString,
bean
));

而这个强制验证,所以它总是更新:

ObjectBinding<Bean> bean = property.valueAt(index);
label.textProperty().bind(Bindings.createStringBinding(
() -> {
bean.get();
return generateRandomString();
},
bean
));

感谢@James_D,我不仅知道为什么这段代码没有按预期运行,还解决了另一个与绑定有关的问题。

TL;DR-JavaFX不会在依赖关系无效时自动验证绑定。您应该通过获取值或以某种方式验证它们。

在下面的代码中,bean将变为无效propertyindex更改,并且StringBinding将进行计算。计算后,bean仍然无效,因为我们没有进行计算来验证它。因此,下次bean的依赖关系发生变化时,StringBinding将不会重新计算。

ObjectBinding<Bean> bean = property.valueAt(index);
label.textProperty().bind(Bindings.createStringBinding(
this::generateRandomString,
bean
));

但对于这一次,情况有所不同。尽管我们从未使用绑定bean的值,但我们通过获取来验证它。因此,代码按预期运行。

ObjectBinding<Bean> bean = property.valueAt(index);
label.textProperty().bind(Bindings.createStringBinding(
() -> {
bean.get();
return generateRandomString();
},
bean
));

对于另一种情况,要小心!5月bean失效,在计算过程中,flagfalse,因此bean未验证。如果在下次bean的依赖项再次无效之前,我们不以某种方式验证bean,则绑定将不会计算。

ObjectBinding<Bean> bean = property.valueAt(index);
label.textProperty().bind(Bindings.createStringBinding(
() -> {
if (flag) return bean.value 
else return generateRandomString();
},
bean
));

相关内容

最新更新