当获取绑定标签的值时,此代码会在标签绑定中抛出stackoverflower错误。我希望标签首先进行“测试”然后在第一次按下“测试按下”然后“测试按下按下”等等。但是,读取该值会引发stackoverflower错误,因为调用getText()方法会触发绑定。我希望只有按钮按下事件才能触发绑定。
注意:我已经注释掉导致错误的代码,并添加了另一个按钮以更好地显示我感到困惑的内容。
import javafx.application.Application;
import javafx.beans.binding.Bindings;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class Main extends Application{
@Override
public void start(Stage primaryStage) {
Label l = new Label("test");
Button b = new Button("press me");
l.textProperty().bind(Bindings.createStringBinding(() ->{
System.out.println("changing label text");
return "ok";
//return l.getText() + " pressed"; //Causes a stackoverflow error
},b.pressedProperty()));
Button b2 = new Button("press me 2");
b2.pressedProperty().addListener((o) -> {
l.getText(); //Why does this not triggger the binding?
});
VBox root = new VBox();
root.getChildren().addAll(l,b,b2);
Scene scene = new Scene(root, 300, 250);
primaryStage.setTitle("Binding test");
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args){
launch(args);
}
}
我的目标是拥有一种在某种条件下不会改变文本的约束。 Callable Lambda中的逻辑类似于:
if(condition){
return "ok";
}else{
return l.getText(); //if the condition is not met then use the current value.
}
我知道我可以在被按下的属性上使用一个监听器然后设置文本标签,所以我有一个解决方案,但我想知道为什么会发生这种情况。
答案 0 :(得分:2)
从语义上讲,您的绑定表达了标签文本是与" pressed"
连接的标签文本的规则。很明显,这就是说标签的文字取决于标签的文字,所以它是递归的。
我不认为这是你想要施加的规则。我想你想要的
规则为"如果未按下按钮,则标签的文本为"test"
,如果按下按钮,则为"test pressed"
。 (现在,如果按钮的pressed
属性发生更改,则会告诉您绑定重新计算,但该值实际上并不取决于该属性。)
从技术上讲,发生的事情大致如下:
public class Label {
private final StringProperty textProperty = new SimpleStringProperty() ;
public String getText() {
return textProperty().get();
}
// ...
}
和
public class SimpleStringProperty {
private StringBinding binding ;
private boolean bound ;
private String value ;
// ...
public String get() {
if (bound) {
value = binding.get();
}
return value ;
}
public void bind(StringBinding binding) {
bound = true ;
this.binding = binding ;
value = binding.get();
}
}
最后,字符串绑定具有以下几行逻辑:
public abstract class StringBinding {
private boolean valid = false;
private String value ;
protected void bind(ObservableStringValue dependency) {
dependency.addListener(o -> invalidate());
}
private void invalidate() {
valid = false ;
// notify invalidation listeners...
}
public String get() {
if (!valid) {
value = computeValue();
valid = true ;
}
return value ;
}
public abstract String computeValue();
}
在您的示例中,computeValue()
的实现会调用标签的getText()
方法。
因此,在创建绑定时,标签文本属性的值是根据绑定的值设置的。绑定不是有效的(因为它尚未计算),因此它是通过您提供的方法计算的。该方法调用label.getText()
,它从属性中获取值。因为属性是绑定的,所以它检查绑定,它仍然无效(因为它的值的计算还没有完成),所以它计算它的值,调用label.getText()
..
所以你可能想要这样的东西:
label.textProperty().bind(Bindings.createStringBinding(() -> {
if (b.isPressed()) {
return "test pressed";
} else {
return "test";
}
}, b.pressedProperty());
如果您希望能够更改基础字符串,则需要为其创建新属性:
StringProperty text = new SimpleStringProperty("test");
label.textProperty().bind(Bindings.createStringBinding(() -> {
if (b.isPressed)() {
return text.get() + " pressed" ;
} else {
return text.get();
}
}, text, b.pressedProperty());
或等同于
label.textProperty().bind(text.concat(
Bindings.when(b.pressedProperty())
.then(" pressed")
.otherwise("")));