有条件的绑定不能按预期方式工作 [英] Conditional binding doesn't work as expected
问题描述
看看Layout
-> initPanelSizeBinding()
.问题是即使panelProperty.get()
是null
,也正在执行then()
.我在做错什么吗?
Take a look at Layout
-> initPanelSizeBinding()
. The problem is then()
is being executed even though panelProperty.get()
is null
. Am I doing something wrong?
您可以自己尝试一下,以下是完整的可复制示例. (在OpenJFX 15上测试)
You can try it out yourself, below is the full reproducible example. (Tested on OpenJFX 15)
可复制的示例-单击此处(pastebin)
或在下面看看:
代码:
Main.java
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$0(Layout.java:20)
at javafx.beans.binding.Bindings$6.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
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
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();
}
}
推荐答案
至少从JavaFX 15开始,then
和otherwise
绑定至少得到了评估 1 .我不确定是否应该急切地对它们进行评估,但确实如此.因此,尽管您可能以panelProperty.isNotNull()
为条件,但无论如何,使用createObjectBinding
创建的绑定仍会尝试计算该值.在这种情况下,最好放弃使用Bindings.when
并直接在自定义绑定中自己实现逻辑:
The then
and otherwise
bindings are, at least as of JavaFX 15, evaluated eagerly1. I'm not sure if they're supposed1 to be evaluated eagerly, but they are. So while you may be conditioning upon panelProperty.isNotNull()
the binding you create with createObjectBinding
still attempts to compute the value no matter what. In this case, it's probably better to forgo the use of Bindings.when
and simply implement the logic yourself in a custom binding:
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
应该是惰性侦听器.
1. Looking into it more, it appears the conditional binding does try to be lazy. Unfortunately, the act of the condition binding adding a listener to the then
observable causes said observable to be validated (and thus eagerly evaluated). This is a consequence of how ExpressionHelper
is implemented, which calls observable.getValue()
to validate it. I don't understand why the code does this, especially considering it's an InvalidationListener
being added which is supposed to be the lazy listener.
这篇关于有条件的绑定不能按预期方式工作的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!