首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >双向JavaFX绑定被无关代码破坏。

双向JavaFX绑定被无关代码破坏。
EN

Stack Overflow用户
提问于 2014-10-11 08:25:44
回答 1查看 1.7K关注 0票数 2

更新:找到一种更容易再现buggy行为的方法

当我在三个变量之间建立双向JavaFX绑定时,这个绑定有时会被不相关的代码破坏。

我创建了一个小示例程序,它能够再现buggy的行为:

在MainController中设置绑定,并添加三个侦听器以输出变量的新值:

代码语言:javascript
复制
package bug;

import java.nio.file.Path;

import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.fxml.FXML;

public class MainController {

    @FXML
    private Foo foo;

    @FXML
    private Bar bar;

    private ObjectProperty<Path> pathProperty = new SimpleObjectProperty<>();

    @FXML
    private void initialize() {

    pathProperty.addListener((observablePath, oldPath,
        newPath) -> {
        System.out.println(newPath);
    });

    foo.pathProperty().addListener((observablePath, oldPath,
        newPath) -> {
        System.out.println(newPath);
    });

    bar.pathProperty().addListener((observablePath, oldPath,
        newPath) -> {
        System.out.println(newPath);
        });

    bar.pathProperty()
        .bindBidirectional(pathProperty);
    foo.pathProperty()
        .bindBidirectional(pathProperty);
    }

}

FooController使用按钮单击触发的计数器更改其中一个变量。按下按钮应该输出三次相同的值,因为我们设置了三个侦听器。只要DatePicker的值没有改变,它就会像预期的那样工作。但在此之后,每个数字只输出一次。

代码语言:javascript
复制
package bug;

import java.nio.file.Paths;
import java.time.LocalDate;

import javafx.beans.value.ChangeListener;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.scene.control.DatePicker;

public class FooController extends Base {

    int counter = 0;

    @FXML
    private DatePicker startDatePicker;

    private ChangeListener<LocalDate> breakThings;

    @FXML
    private void onBugClicked(ActionEvent event) {
    for (int i = 0; i < 3; i++) {
        pathProperty.set(Paths.get(String.valueOf(counter++)));
    }
    }

    @FXML
    private void initialize() {

    breakThings = (observableDate, oldDate, newDate)->{
        System.out.println("Triggered");
    };  

    startDatePicker.valueProperty().addListener(breakThings);
    }
}

Foo和Bar控制器的基类

代码语言:javascript
复制
package bug;

import java.nio.file.Path;

import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;

public abstract class Base {

    protected ObjectProperty<Path> pathProperty = new SimpleObjectProperty<>();

    public ObjectProperty<Path> pathProperty() {
    return pathProperty;
    }

}

BarController:

代码语言:javascript
复制
package bug;

public class BarController extends Base {

}

Foo:

代码语言:javascript
复制
package bug;

import java.io.IOException;
import java.nio.file.Path;

import javafx.beans.property.ObjectProperty;
import javafx.fxml.FXMLLoader;
import javafx.scene.layout.BorderPane;

public class Foo extends BorderPane {

    private final FooController controller;

    public Foo() {
    controller = new FooController();
    FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource(
        "Foo.fxml"));
    fxmlLoader.setRoot(this);
    fxmlLoader.setController(controller);
    try {
        fxmlLoader.load();
    } catch (IOException exception) {
        throw new RuntimeException(exception);
    }
    }

    public ObjectProperty<Path> pathProperty() {
    return controller.pathProperty();
    }    

}

酒吧:

代码语言:javascript
复制
package bug;

import java.nio.file.Path;

import javafx.beans.property.ObjectProperty;
import javafx.scene.layout.BorderPane;

public class Bar extends BorderPane {

    private final BarController controller;

    public Bar() {
    controller = new BarController();
    }

    public ObjectProperty<Path> pathProperty() {
    return controller.pathProperty();
    }

}

预期输出(在单击四下按钮后):

代码语言:javascript
复制
0
0
0
1
1
1
2
2
2
3
3
3
4
4
4
5
5
5
6
6
6
7
7
7
8
8
8
9
9
9
10
10
10
11
11
11

实际输出(单击四个按钮后):

代码语言:javascript
复制
0
0
0
1
1
1
2
2
2
3
3
3
4
4
4
5
5
5
6
6
6
7
7
7
8
8
8
(Select date with DatePicker)
9
10
11

Java Version:1.8.0_20

JavaFX版本: 8.0.20-b26

EN

回答 1

Stack Overflow用户

回答已采纳

发布于 2014-10-11 14:06:13

为什么会发生这样的事情

双向绑定的工作方式是创建侦听器并将它们注册到属性中。当属性标记为无效时,将调用这些侦听器,并更改相关属性的值。

绑定使用的侦听器是s。这些侦听器只保留对它们正在观察的对象的弱引用。因此,如果范围中没有对这些属性的其他引用,则这些属性有资格进行垃圾收集。一旦它们被垃圾收集,侦听器就不再有任何要观察的东西了,绑定基本上消失了。这通常是一件好事,因为它可以防止难以追踪的内存泄漏,但有时(在您的示例中)它会造成混乱的情况。

在您的示例中,对属性的引用由MainController保存。当您调用FXMLLoader时,这个控制器会被start()方法实例化(大概是在load()方法中),但是您几乎肯定不会在start()方法之外保留对它的引用,start()方法在应用程序结束之前很久就已经完成并退出了。因此,您的属性有资格进行垃圾收集,当垃圾收集器运行时,它们将与绑定一起从堆中清除。我怀疑当您在DatePicker上调用侦听器时,内存需求会迫使垃圾收集器运行。如果您按了这个按钮足够多次(可能有很多次),那么即使没有DatePicker,您也会看到同样的事情发生。

--一个简单的示例

下面是一个简单的例子。有三个值绑定在一起的IntegerProperty和一个侦听器,就像在您的示例中一样。按下“增量”按钮将直接增加一个,因此每个按钮上的侦听器都应该被调用。如果强制垃圾收集,按下"Run“按钮,就会”破坏“实现。

代码语言:javascript
复制
import javafx.application.Application;
import javafx.beans.binding.Bindings;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.value.ChangeListener;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.layout.HBox;
import javafx.stage.Stage;

public class BidirectionalBindingDemo extends Application {

    @Override
    public void start(Stage primaryStage) {
        IntegerProperty x = new SimpleIntegerProperty();
        IntegerProperty y = new SimpleIntegerProperty();
        IntegerProperty z = new SimpleIntegerProperty();
        y.bindBidirectional(x);
        z.bindBidirectional(x);
        ChangeListener<Number> listener = (obs, oldValue, newValue) -> System.out.println(x.get()) ;
        x.addListener(listener);
        y.addListener(listener);
        z.addListener(listener);

        Button incrementButton = new Button("Increment");
        incrementButton.setOnAction(event -> x.set(x.get()+1));

        Button gcButton = new Button("Run GC");
        gcButton.setOnAction(event -> System.gc());

        HBox root = new HBox(5, incrementButton, gcButton);
        root.setAlignment(Pos.CENTER);
        root.setPadding(new Insets(10));

        primaryStage.setScene(new Scene(root));
        primaryStage.show();
    }

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

--为什么在实际应用程序中这可能不是一个问题

在实际应用程序中,很少创建UI中没有使用的属性。通常,您会观察一个属性,当它更改时,更新UI作为响应。这迫使UI组件(间接地)保存对属性的引用,使其不符合垃圾收集的条件,只要UI组件是场景图的一部分。在我的示例中,如果我们向场景添加一个标签并使其文本依赖于属性:

代码语言:javascript
复制
    Label label = new Label();
    label.textProperty().bind(Bindings.format("x: %s y: %s z:%s", x, y, z));

    HBox root = new HBox(5, button, gcButton, label);

那么,即使在垃圾收集之后,绑定仍然有效。

如果您还需要一个解决方案,

只是偶尔,您需要UI组件没有观察到的属性。在这种情况下,您必须确保只要需要它们,它们就在作用域内。在代码中,尝试将对MainController的引用保存为应用程序类中的实例变量(而不是局部变量)。

票数 10
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/26312651

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档