Reputation: 47
Take a look at Layout
-> initPanelSizeBinding()
. The problem is then()
is being executed even though panelProperty.get()
is null
. Am I doing something wrong?
You can try it out yourself, below is the full reproducible example. (Tested on OpenJFX 15)
Reproducible example - click here (pastebin)
or take a look below:
Code:
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
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();
}
}
Upvotes: 2
Views: 490
Reputation: 46295
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. 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.
Upvotes: 5