条件绑定无法按预期工作
Conditional binding doesn't work as expected
看看 Layout
-> initPanelSizeBinding()
。问题是 then()
正在执行,即使 panelProperty.get()
是 null
。我做错了什么吗?
您可以自己尝试一下,下面是完整的可重现示例。 (在 OpenJFX 15 上测试)
可重现的例子 - click here (pastebin)
或往下看:
代码:
Main.java
public class Main {
public static void main(String[] args) {
Layout layout = new Layout();
/*
WARNING: Exception while evaluating binding
java.lang.NullPointerException: Cannot invoke "Panel.getWidth()" because the return value of "javafx.beans.property.ObjectProperty.get()" is null
at Layout.lambda$initPanelSizeBinding[=10=](Layout.java:20)
at javafx.beans.binding.Bindings.computeValue(Bindings.java:358)
at javafx.beans.binding.ObjectBinding.get(ObjectBinding.java:157)
at javafx.beans.binding.ObjectExpression.getValue(ObjectExpression.java:49)
at com.sun.javafx.binding.ExpressionHelper.addListener(ExpressionHelper.java:53)
at javafx.beans.binding.ObjectBinding.addListener(ObjectBinding.java:77)
at javafx.beans.binding.When$ObjectCondition.<init>(When.java:757)
at javafx.beans.binding.When$ObjectConditionBuilder.otherwise(When.java:854)
at Layout.initPanelSizeBinding(Layout.java:24)
at Layout.<init>(Layout.java:12)
at Main.main(Main.java:4)
*/
}
}
Layout.java
public final class Layout {
public Layout() {
panelSize.bind(initPanelSizeBinding(panel));
// there's no need to do anything with panel, because it's an example
}
private ObjectBinding<Dimension2D> initPanelSizeBinding(ObjectProperty<Panel> panelProperty) {
return Bindings
.when(panelProperty.isNotNull())
.then(Bindings.createObjectBinding(
() -> new Dimension2D(panelProperty.get().getWidth(), panelProperty.get().getHeight()),
Bindings.selectDouble(panelProperty, "width"),
Bindings.selectDouble(panelProperty, "height"))
)
.otherwise(new Dimension2D(150.0, 150.0));
}
//------Properties
//panel
private final ObjectProperty<Panel> panel = new SimpleObjectProperty<>
(Layout.this, "panel", null);
public ObjectProperty<Panel> panelProperty() {
return panel;
}
public void setPanel(Panel value) {
panel.set(value);
}
public Panel getPanel() {
return panel.get();
}
//panelSize
private final ReadOnlyObjectWrapper<Dimension2D> panelSize = new ReadOnlyObjectWrapper<>
(Layout.this, "panelSize");
public ReadOnlyObjectProperty<Dimension2D> panelSizeProperty() {
return panelSize.getReadOnlyProperty();
}
public Dimension2D getPanelSize() {
return panelSize.get();
}
}
Panel.java
public final class Panel {
private final VBox rootNode;
public Panel() {
rootNode = new VBox();
width.bind(rootNode.widthProperty());
height.bind(rootNode.heightProperty());
}
public VBox getRootNode() {
return rootNode;
}
//------Properties
//width
private final ReadOnlyDoubleWrapper width = new ReadOnlyDoubleWrapper
(Panel.this, "width");
public ReadOnlyDoubleProperty widthProperty() {
return width.getReadOnlyProperty();
}
public double getWidth() {
return width.get();
}
//height
private final ReadOnlyDoubleWrapper height = new ReadOnlyDoubleWrapper
(Panel.this, "height");
public ReadOnlyDoubleProperty heightProperty() {
return height.getReadOnlyProperty();
}
public double getHeight() {
return height.get();
}
}
then
和 otherwise
绑定,至少从 JavaFX 15 开始,被急切1 评估.我不确定是否应该1 急切地评估它们,但确实如此。因此,尽管您可能以 panelProperty.isNotNull()
为条件,但无论如何,您使用 createObjectBinding
创建的绑定仍然会尝试计算该值。在这种情况下,最好放弃使用 Bindings.when
并简单地自己在自定义绑定中实现逻辑:
private ObjectBinding<Dimension2D> initPanelSizeBinding(ObjectProperty<Panel> panelProperty) {
DoubleBinding width = Bindings.selectDouble(panelProperty, "width");
DoubleBinding height = Bindings.selectDouble(panelProperty, "height");
return Bindings.createObjectBinding(
() -> {
Panel panel = panelProperty.get();
if (panel == null) {
return new Dimension2D(150.0, 150.0);
}
return new Dimension2D(width.get(), height.get());
},
panelProperty,
width,
height);
}
1. 进一步研究,条件绑定似乎确实是懒惰的。不幸的是,条件绑定向 then
可观察对象添加侦听器的行为导致所述可观察对象被验证(并因此被急切地评估)。这是 ExpressionHelper
实现方式的结果,它调用 observable.getValue()
来验证它。我不明白为什么代码会这样做,特别是考虑到它添加了一个 InvalidationListener
,它应该是懒惰的监听器。
看看 Layout
-> initPanelSizeBinding()
。问题是 then()
正在执行,即使 panelProperty.get()
是 null
。我做错了什么吗?
您可以自己尝试一下,下面是完整的可重现示例。 (在 OpenJFX 15 上测试)
可重现的例子 - click here (pastebin)
或往下看:
代码:
Main.java
public class Main {
public static void main(String[] args) {
Layout layout = new Layout();
/*
WARNING: Exception while evaluating binding
java.lang.NullPointerException: Cannot invoke "Panel.getWidth()" because the return value of "javafx.beans.property.ObjectProperty.get()" is null
at Layout.lambda$initPanelSizeBinding[=10=](Layout.java:20)
at javafx.beans.binding.Bindings.computeValue(Bindings.java:358)
at javafx.beans.binding.ObjectBinding.get(ObjectBinding.java:157)
at javafx.beans.binding.ObjectExpression.getValue(ObjectExpression.java:49)
at com.sun.javafx.binding.ExpressionHelper.addListener(ExpressionHelper.java:53)
at javafx.beans.binding.ObjectBinding.addListener(ObjectBinding.java:77)
at javafx.beans.binding.When$ObjectCondition.<init>(When.java:757)
at javafx.beans.binding.When$ObjectConditionBuilder.otherwise(When.java:854)
at Layout.initPanelSizeBinding(Layout.java:24)
at Layout.<init>(Layout.java:12)
at Main.main(Main.java:4)
*/
}
}
Layout.java
public final class Layout {
public Layout() {
panelSize.bind(initPanelSizeBinding(panel));
// there's no need to do anything with panel, because it's an example
}
private ObjectBinding<Dimension2D> initPanelSizeBinding(ObjectProperty<Panel> panelProperty) {
return Bindings
.when(panelProperty.isNotNull())
.then(Bindings.createObjectBinding(
() -> new Dimension2D(panelProperty.get().getWidth(), panelProperty.get().getHeight()),
Bindings.selectDouble(panelProperty, "width"),
Bindings.selectDouble(panelProperty, "height"))
)
.otherwise(new Dimension2D(150.0, 150.0));
}
//------Properties
//panel
private final ObjectProperty<Panel> panel = new SimpleObjectProperty<>
(Layout.this, "panel", null);
public ObjectProperty<Panel> panelProperty() {
return panel;
}
public void setPanel(Panel value) {
panel.set(value);
}
public Panel getPanel() {
return panel.get();
}
//panelSize
private final ReadOnlyObjectWrapper<Dimension2D> panelSize = new ReadOnlyObjectWrapper<>
(Layout.this, "panelSize");
public ReadOnlyObjectProperty<Dimension2D> panelSizeProperty() {
return panelSize.getReadOnlyProperty();
}
public Dimension2D getPanelSize() {
return panelSize.get();
}
}
Panel.java
public final class Panel {
private final VBox rootNode;
public Panel() {
rootNode = new VBox();
width.bind(rootNode.widthProperty());
height.bind(rootNode.heightProperty());
}
public VBox getRootNode() {
return rootNode;
}
//------Properties
//width
private final ReadOnlyDoubleWrapper width = new ReadOnlyDoubleWrapper
(Panel.this, "width");
public ReadOnlyDoubleProperty widthProperty() {
return width.getReadOnlyProperty();
}
public double getWidth() {
return width.get();
}
//height
private final ReadOnlyDoubleWrapper height = new ReadOnlyDoubleWrapper
(Panel.this, "height");
public ReadOnlyDoubleProperty heightProperty() {
return height.getReadOnlyProperty();
}
public double getHeight() {
return height.get();
}
}
then
和 otherwise
绑定,至少从 JavaFX 15 开始,被急切1 评估.我不确定是否应该1 急切地评估它们,但确实如此。因此,尽管您可能以 panelProperty.isNotNull()
为条件,但无论如何,您使用 createObjectBinding
创建的绑定仍然会尝试计算该值。在这种情况下,最好放弃使用 Bindings.when
并简单地自己在自定义绑定中实现逻辑:
private ObjectBinding<Dimension2D> initPanelSizeBinding(ObjectProperty<Panel> panelProperty) {
DoubleBinding width = Bindings.selectDouble(panelProperty, "width");
DoubleBinding height = Bindings.selectDouble(panelProperty, "height");
return Bindings.createObjectBinding(
() -> {
Panel panel = panelProperty.get();
if (panel == null) {
return new Dimension2D(150.0, 150.0);
}
return new Dimension2D(width.get(), height.get());
},
panelProperty,
width,
height);
}
1. 进一步研究,条件绑定似乎确实是懒惰的。不幸的是,条件绑定向 then
可观察对象添加侦听器的行为导致所述可观察对象被验证(并因此被急切地评估)。这是 ExpressionHelper
实现方式的结果,它调用 observable.getValue()
来验证它。我不明白为什么代码会这样做,特别是考虑到它添加了一个 InvalidationListener
,它应该是懒惰的监听器。