Nov*_*ovi 1 data-binding javafx
更新:找到一种更简单的方法来重现错误的行为
当我在三个变量之间设置双向JavaFX绑定时,这种绑定有时会被不相关的代码破坏.
我创建了一个能够重现错误行为的小示例程序:
在MainController中,设置绑定并添加三个侦听器以输出变量的新值:
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);
}
}
Run Code Online (Sandbox Code Playgroud)
FooController使用由按钮单击触发的计数器更改其中一个变量.按下按钮应输出相同的值三次,因为我们设置了三个监听器.只要不更改DatePicker的值,这就可以正常工作.但之后每个号码只输出一次.
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);
}
}
Run Code Online (Sandbox Code Playgroud)
Foo和Bar控制器的基类
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;
}
}
Run Code Online (Sandbox Code Playgroud)
BarController:
package bug;
public class BarController extends Base {
}
Run Code Online (Sandbox Code Playgroud)
富:
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();
}
}
Run Code Online (Sandbox Code Playgroud)
酒吧:
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();
}
}
Run Code Online (Sandbox Code Playgroud)
预期输出(点击四次按钮后):
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
Run Code Online (Sandbox Code Playgroud)
实际输出(点击四次按钮后):
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
Run Code Online (Sandbox Code Playgroud)
Java版本:1.8.0_20
JavaFX版本:8.0.20-b26
为什么会这样
双向绑定通过创建侦听器并使用属性注册它们来工作.当属性标记为无效时,将调用这些侦听器,并更改依赖属性的值.
绑定使用的侦听器是WeakListeners.这些是仅保留对其正在观察的对象的弱引用的侦听器.因此,如果范围内没有对这些属性的其他引用,则属性可用于垃圾回收.一旦它们被垃圾收集,听众就不再需要观察任何东西,并且绑定基本上消失了.这通常是一件好事,因为它可以防止难以追踪的内存泄漏,但偶尔(如在您的示例中)会产生令人困惑的情况.
在您的示例中,属性的引用由MainController.当你调用时,这个控制器由FXMLLoader(可能在某个start()方法中)实例化load(),但你几乎肯定不会在start()方法之外保留对它的引用,该方法在应用程序终止之前很久就完成并退出.因此,您的属性有资格进行垃圾收集,当垃圾收集器运行时,它们将从堆中清除,并与绑定一起清除.我怀疑当你调用监听器时DatePicker,内存需求会强制垃圾收集器运行.如果你按下按钮足够多次(可能是很多次),你应该看到同样的事情发生,即使没有DatePicker.
一个更简单的例子
这是一个更简单的例子.有三个IntegerPropertys的值绑定在一起,并且每个都有一个监听器,如示例所示.按"递增"按钮将直接递增一个,因此应调用每个按钮上的监听器.如果您强制进行垃圾回收,按"运行GC"按钮,您将"中断"实施.
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);
}
}
Run Code Online (Sandbox Code Playgroud)
为什么这可能不是真正的应用程序中的问题
在实际应用程序中,您很少创建在UI中某处未使用的属性.通常,您会观察一个属性,当它发生更改时,请更新UI作为响应.这会强制UI组件保持(间接)对属性的引用,只要UI组件是场景图的一部分,它就不符合垃圾回收的条件.在我的示例中,如果我们向场景添加标签并使其文本依赖于属性:
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);
Run Code Online (Sandbox Code Playgroud)
然后,即使在垃圾收集之后绑定仍然存在.
如果您仍需要解决方法
偶尔,您确实需要UI组件未观察到的属性.在这种情况下,只要需要它们,您必须确保它们保持在范围内.在您的代码中,尝试MainController在应用程序类中保存对实例变量(不是局部变量)的引用.
| 归档时间: |
|
| 查看次数: |
1201 次 |
| 最近记录: |