为什么在绑定中调用get绑定值会引发stackoverflow错误

时间:2017-05-16 19:38:17

标签: java javafx binding stack-overflow bind

当获取绑定标签的值时,此代码会在标签绑定中抛出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.
}

我知道我可以在被按下的属性上使用一个监听器然后设置文本标签,所以我有一个解决方案,但我想知道为什么会发生这种情况。

1 个答案:

答案 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("")));