In this question I was shown how to deal with the problem of having a property change by changing it's wrapping object and thus not sending updates that it changed. A solution was using ReactFX:
class Cell {
private final ObjectProperty<Shape> shape = new SimpleObjectProperty<>(new Shape());
// all getters and setterts
public static class Shape {
private final IntegerProperty size = new SimpleIntegerProperty(0);
// all getters and setterts
}
public static void main(String[] args) {
Var<Number> sizeVar = Val.selectVar(cell.shapeProperty(), Shape::sizeProperty);
sizeVar.addListener(
(obs, oldSize, newSize) -> System.out.println("Size changed from "+oldSize+" to "+newSize));
}
So now if the shape
property itself changes it triggers a change in size
too (unless the new shape has the same size). But now I want to bind to the property with custom bindings and I have an problem explained below.
My data classes are these:
class Cell {
private final ObjectProperty<Shape> shape = new SimpleObjectProperty<>();
public final ObjectProperty<Shape> shapeProperty() { return shape; }
public final Shape getShape() { return shapeProperty().get(); }
public final void setShape(Shape shape) { shapeProperty().set(shape); }
// other properties
}
class Shape {
private final IntegerProperty size = new SimpleIntegerProperty();
public final IntegerProperty sizeProperty() { return size; }
public final int getSize() { return size.get(); }
public final void setSize(int size) { sizeProperty().set(size); }
// other properties
}
And i want to create a GUI representation for them by binding their properties to GUI properties. I do it this way:
class CellRepresentation extends Group {
private final Cell cell;
CellRepresentation(Cell cell) {
this.cell = cell;
getChildren().add(new ShapeRepresentation() /*, other representations of things in the cell*/);
}
private class ShapeRepresentation extends Cylinder {
ObjectProperty<Shape> shape;
private ShapeRepresentation() {
super(100, 100);
shape = new SimpleObjectProperty<Shape>(cell.getShape());
shape.bind(cell.shapeProperty());
Var<Number> sizeVar = Val.selectVar(cell.shapeProperty(), Shape::sizeProperty);
// THIS WILL WORK
materialProperty().bind(Bindings.createObjectBinding(() -> {
if (shape.get() == null)
return new PhongMaterial(Color.TRANSPARENT);
return new PhongMaterial(Color.RED);
}, sizeVar));
// THIS WILL NOT WORK
materialProperty().bind(sizeVar.map(n -> {
if (shape.get() == null)
return new PhongMaterial(Color.TRANSPARENT);
return new PhongMaterial(Color.RED);
}));
}
}
// the other representations of things in the cell
}
When I run the code below the first option for binding will create a transparent cylinder. The second option will create a white (default color) cylinder. I don't know why this happens.
public class Example extends Application {
public static void main(String[] args) {
launch(args);
}
@Override
public void start(Stage stage) throws Exception {
Cell cell = new Cell();
CellRepresentation cellRep = new CellRepresentation(cell);
Group group = new Group(cellRep);
Scene scene = new Scene(group, 200, 200, Color.AQUA);
stage.setScene(scene);
stage.show();
}
}
I am also open to design suggestions if this is not a good way to create representations for the data classes using bindings.
Val
andVar
are "observable monadics" (think observableOptional
s). They are either empty or hold a value. Themap
method works just likeOptional.map
: if theVal
is empty,map
results in an emptyVal
; otherwise it results in aVal
containing the result of applying the function to the originalVal
's value. So ifsizeVar
evaluates tonull
, the mapping results in an emptyVal
(so your material is set tonull
) without even evaluating your lambda expression.To handle
null
(i.e. emptyVal
s), you should useorElse
or similar methods:Updated example for testing: