简体   繁体   English

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

[英]Why does calling get of a bound value in the binding throw a stackoverflow error

This code throws a stackoverflowerror in a label binding when getting the value of the label being bound. 获取绑定的标签的值时,此代码在标签绑定中引发stackoverflowerror。 I expect the label to be "test" initially then on the first press "test pressed" then "test pressed pressed" and so on. 我希望标签最初是“测试”,然后在第一次按下“测试按下”,然后是“按下测试”,依此类推。 However reading the value throws a stackoverflowerror because calling the getText() method triggers the binding. 但是,读取值会引发stackoverflowerror,因为调用getText()方法会触发绑定。 I expect only button press events to trigger the binding. 我希望只有按钮按下事件才能触发绑定。

Note: I've commented out the code which causes the error and added another button to better show what I'm confused about. 注意:我已注释掉导致错误的代码,并添加了另一个按钮以更好地显示我的困惑。

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

My objective here is to have a binding which, on a certain condition, does not change the text. 我的目标是在特定条件下具有不会更改文本的绑定。 The logic in the Callable Lambda is something like: Callable Lambda中的逻辑类似于:

if(condition){
    return "ok";
}else{
    return l.getText(); //if the condition is not met then use the current value.
}

I know I can use a listener on the pressed property and then set the text label that way, so I have a solution but I would like to know why the above is happening. 我知道我可以在被按下的属性上使用侦听器,然后以这种方式设置文本标签,因此我有一个解决方案,但我想知道为什么会发生上述情况。

Just semantically, your binding expresses the rule that the text of the label is the text of the label concatenated with " pressed" . 仅在语义上,绑定表示规则,即标签的文本是与" pressed"串联的标签的文本。 Clearly this is saying that the label's text depends on the label's text, so it's recursive. 显然,这是说标签的文本取决于标签的文本,因此它是递归的。

I don't think this is the rule you want to impose anyway. 无论如何,我认为这不是您要强加的规则。 I think you want the rule to be "the label's text is "test" if the button is not pressed, and "test pressed" if the button is pressed. (Right now your binding is told to recompute if the button's pressed property changes, but the value doesn't actually depend on that property.) 我认为您希望规则为:如果未按下按钮,则标签文本为"test"如果未按下按钮,则规则为"test pressed" 。(现在,如果按钮的pressed属性更改,则告诉绑定重新计算,但该值实际上并不取决于该属性。)

Technically what's happening is something along the following lines: 从技术上讲,正在发生的事情大致如下:

public class Label {

    private final StringProperty textProperty = new SimpleStringProperty() ;

    public String getText() {
        return textProperty().get();
    }

    // ...
}

and

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

Finally, string binding has logic along the following lines: 最后,字符串绑定具有以下几方面的逻辑:

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

And in your example, the implementation of computeValue() invokes the label's getText() method. 在您的示例中, computeValue()的实现将调用标签的getText()方法。

So when you create the binding, the value of the label's text property is set from the value of the binding. 因此,当您创建绑定时,将从绑定的值中设置标签的text属性的值。 The binding isn't valid (because it hasn't been computed yet), so it is computed via the method you provide. 绑定无效(因为尚未计算),因此通过您提供的方法进行计算。 That method invokes label.getText() , which gets the value from the property. 该方法调用label.getText() ,该方法从属性获取值。 Because the property is bound, it check the binding, which still isn't valid (because the computation of its value hasn't been completed), so it computes its value, which invokes label.getText() ... 因为该属性已绑定,所以它将检查仍然无效的绑定(因为尚未完成其值的计算),因此它将计算其值,从而调用label.getText() ...

So you probably want something like: 因此,您可能想要以下内容:

label.textProperty().bind(Bindings.createStringBinding(() -> {
    if (b.isPressed()) {
         return "test pressed";
    } else {
         return "test";
    }
}, b.pressedProperty());

If you want the underlying string to be able to be changed, you need to create a new property for it: 如果要更改基础字符串,则需要为其创建一个新属性:

StringProperty text = new SimpleStringProperty("test");
label.textProperty().bind(Bindings.createStringBinding(() -> {
    if (b.isPressed)() {
        return text.get() + " pressed" ;
    } else {
        return text.get();
    }
}, text, b.pressedProperty());

or, equivalently 或者,等效地

label.textProperty().bind(text.concat(
    Bindings.when(b.pressedProperty())
    .then(" pressed")
    .otherwise("")));

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM