InvalidationListener仅在带有断点的调试模式下执行

时间:2016-11-08 01:58:01

标签: java javafx javafx-8

我的InvalidationListener存在问题。它在SimpleStringProperty上设置为Listener。但它仅用于SimpleStringProperty的第一次更改。 我进入调试模式并在调用SimpleStringProperty :: set的行上创建了一个断点,它开始工作,直到我再次删除了断点。

我做了一个简短的可执行示例程序,它模拟使用计时器修改SimpleStringProperty。您可以在没有断点的情况下运行程序一次,并且在此行中有一次断点:property.set(value);

import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.application.Application;
import javafx.beans.property.SimpleStringProperty;
import javafx.scene.Scene;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;
import javafx.util.Duration;


public class Main extends Application {

    private SimpleStringProperty property;
    private int counter;

    @Override
    public void start(Stage stage) {
        // open a window to avoid immediately termination
        stage.setWidth(800);
        stage.setHeight(600);
        BorderPane pane = new BorderPane();
        stage.setScene(new Scene(pane));
        stage.show();

        // create a SimpleObjectProperty
        property = new SimpleStringProperty();
        property.addListener(observable ->
            System.out.println("New value is: " + counter)
        );
        counter = 0;

        // create timer to change 'property' every second
        Timeline timeline = new Timeline();
        KeyFrame keyFrame = new KeyFrame(Duration.seconds(2), event ->{
            String value = "" + ++counter;
            System.out.println("Set property to: " + value);
            property.set(value);
        });
        timeline.getKeyFrames().add(keyFrame);
        timeline.setCycleCount(Timeline.INDEFINITE);
        timeline.playFromStart();
    }

    public static void main(String[] args) {
        launch(args);
    }
}

我的机器上的输出(Linux Mint 16.04 64bit,Oracle-Java 1.8.0_111):

Set property to: 1
New value is: 1
Set property to: 2
Set property to: 3
Set property to: 4
...

请向我解释:

  1. 为什么每次更改都不会调用听众?
  2. 当我设置断点时,为什么要调用侦听器?
  3. 如果没有断点,我该怎么办呢?

1 个答案:

答案 0 :(得分:5)

可观察值具有两种不同的状态,其更改可以触发侦听器。有,并且存在当前有效的状态。

通常,可观察值的值可以是计算的值,而不是简单地存储在字段中。一旦价值得到实现" (我的术语),通过计算它是否被计算,或者如果它被简单地存储在一个字段中,则被检索,那么可观察的值在"有效的"州。如果值更改(或可能已更改),则可观察值变为"无效",表示可能需要重新计算或再次查找。

仅当可观察值从有效状态转换为无效状态时才会触发失效侦听器。所以在您的代码中,第一次调用

property.set(value);

属性转换为无效状态(因为最近检索到的值,如果有的话,不是它的当前值)。

由于您无法拨打property.get()(或property.getValue()),因此该属性永远不会得到验证。因此,下次调用property.set(value)时,属性会转换为无效状态(它已处于该状态),因此不会触发侦听器。

如果用

替换侦听器代码
property.addListener(observable ->
    System.out.println("New value is: " + property.get())
);

此侦听器将使属性再次变为有效,因此每次都会触发侦听器。

真正的问题是你在这里使用了错误的听众。如果您想在每次值更改时执行操作,请使用ChangeListener,而不是InvalidationListener

property.addListener((observable, oldValue, newValue) -> 
    System.out.println("New value is: " + newValue)
);

观察到在带有断点的调试模式下运行会导致每次调用失效侦听器都是一个有趣的侦听器。我猜了一下,但我怀疑发生的是当你点击断点时,调试器显示变量的当前值。这不可避免地涉及在属性上调用getValue()(可能是其toString()实现的一部分),因此该属性将被验证。

您不太可能经常明确使用InvalidationListener。它们的主要用途是绑定。请考虑以下示例:

DoubleProperty x = new SimpleDoubleProperty(3);
DoubleProperty y = new SimpleDoubleProperty(4);

DoubleBinding hyp = new DoubleBinding() {
    {
        bind(x);
        bind(y);
    }

    @Override
    protected double computeValue() {
        System.out.println("Computing distance");
        return Math.sqrt(x.get()*x.get() + y.get()*y.get());
    }
};

Label hypLabel = new Label();
hypLabel.textProperty().bind(hyp.asString("Hypotenuse: %f"));

绑定实现中对bind(x)的调用意味着:当x变为无效时,请认为此绑定无效。同样适用于y。当然,bind的实施使用了InvalidationListener

这里的要点是hyp值的计算非常昂贵。如果您要对xy进行多项更改,则hyp将变为无效,并且需要在需要时重新计算 。由于标签的text属性绑定到hyp,这也意味着标签的文本无效。但是,在渲染脉冲中重新绘制标签时,实际上只需要新值;计算xy的每次更改的价值都是过度的。