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

Zer*_*ono 1 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);
    }
}
Run Code Online (Sandbox Code Playgroud)

在我的机器上输出(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
...
Run Code Online (Sandbox Code Playgroud)

请向我解释一下:

  1. 为什么每次改变都没有呼叫听众?
  2. 当我设置一个断点时,为什么听众会被叫?
  3. 我该怎么办才能让它在没有断点的情况下工作?

Jam*_*s_D 5

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

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

仅当可观察值从有效状态转换为无效状态时才会触发失效侦听器.所以在你的代码中,你第一次打电话

property.set(value);
Run Code Online (Sandbox Code Playgroud)

属性转换为无效状态(因为最近检索的值(如果有)不是其当前值).

由于您从未调用过property.get()(或property.getValue()),因此该属性永远不会被验证.因此,下次你打电话时property.set(value),该属性也不会转变为无效状态(它已经在该状态),所以听众不会被解雇.

如果用您的侦听器代码替换

property.addListener(observable ->
    System.out.println("New value is: " + property.get())
);
Run Code Online (Sandbox Code Playgroud)

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

真正的问题是你在这里使用了错误的监听器.如果要在每次值更改时执行操作,请使用a ChangeListener,而不是InvalidationListener:

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

观察到在带有断点的调试模式下运行会导致每次调用失效侦听器都是一个有趣的调用.我猜了一下,但我怀疑发生的是当你点击断点时,调试器显示变量的当前值.这不可避免地涉及调用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"));
Run Code Online (Sandbox Code Playgroud)

bind(x)绑定实现中的调用意味着:当x变为无效时,请认为此绑定无效.同样的y.当然,在引擎盖下bind使用InvalidationListeners 的实现.

这里的要点是计算hyp的价值非常昂贵.如果你要进行多次修改xy,然后hyp变为无效,需要重新计算,当你再次需要它.由于绑定了标签的text属性hyp,这也意味着标签的文本无效.但是,在渲染脉冲中重新绘制标签时,实际上只需要新值; 这将是矫枉过正来计算的每一个变化值xy.