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.
ValandVarare "observable monadics" (think observableOptionals). They are either empty or hold a value. Themapmethod works just likeOptional.map: if theValis empty,mapresults in an emptyVal; otherwise it results in aValcontaining the result of applying the function to the originalVal's value. So ifsizeVarevaluates tonull, the mapping results in an emptyVal(so your material is set tonull) without even evaluating your lambda expression.To handle
null(i.e. emptyVals), you should useorElseor similar methods:Updated example for testing: