Because this is a question about design I\'ll start by saying what i have and what i want.
I have a design that uses composition. A Cell
object hol
Using just the standard JavaFX API you can leverage the Bindings.selectXXX methods to observe a "property of a property".
So for example:
import javafx.beans.binding.Bindings;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.scene.paint.Color;
public class Cell {
private final ObjectProperty<Shape> shape = new SimpleObjectProperty<>(new Shape());
public final ObjectProperty<Shape> shapeProperty() {
return this.shape;
}
public final Cell.Shape getShape() {
return this.shapeProperty().get();
}
public final void setShape(final Cell.Shape shape) {
this.shapeProperty().set(shape);
}
public static class Shape {
private final IntegerProperty size = new SimpleIntegerProperty(0);
private final ObjectProperty<Color> color = new SimpleObjectProperty<>(Color.BLACK);
public final IntegerProperty sizeProperty() {
return this.size;
}
public final int getSize() {
return this.sizeProperty().get();
}
public final void setSize(final int size) {
this.sizeProperty().set(size);
}
public final ObjectProperty<Color> colorProperty() {
return this.color;
}
public final javafx.scene.paint.Color getColor() {
return this.colorProperty().get();
}
public final void setColor(final javafx.scene.paint.Color color) {
this.colorProperty().set(color);
}
}
public static void main(String[] args) {
Cell cell = new Cell();
Bindings.selectInteger(cell.shapeProperty(), "size").addListener(
(obs, oldSize, newSize) -> System.out.println("Size changed from "+oldSize+" to "+newSize));
cell.getShape().setSize(10);
cell.setShape(new Shape());
Shape s = new Shape();
s.setSize(20);
cell.setShape(s);
}
}
Will produce the (desired) output
Size changed from 0 to 10
Size changed from 10 to 0
Size changed from 0 to 20
This API has a bit of a legacy feel to it, in that it relies on passing the property name as a string, and consequently is not typesafe and cannot be checked at compile time. Additionally, if any of the intermediate properties are null (e.g. if cel.getShape()
returns null in this example), the bindings generate annoying and verbose warning messages (even though this is supposed to be a supported use case).
Tomas Mikula has a more modern implementation in his ReactFX library, see this post for a description. Using ReactFX, you would do:
public static void main(String[] args) {
Cell cell = new Cell();
Var<Number> size = Val.selectVar(cell.shapeProperty(), Shape::sizeProperty);
size.addListener(
(obs, oldSize, newSize) -> System.out.println("Size changed from "+oldSize+" to "+newSize));
cell.getShape().setSize(10);
cell.setShape(new Shape());
Shape s = new Shape();
s.setSize(20);
cell.setShape(s);
}
Finally, if you are creating a list of cells, you can create an ObservableList
specifying an extractor. The extractor is a function mapping each element in the list (each Cell
) to an array of Observable
s. If any of those Observable
s changes, the list fires an update event. So you could do
ObservableList<Cell> cellList =
FXCollections.observableArrayList(cell -> new Observable[] {Bindings.selectInteger(cell.shapeProperty(), "size")});
using the standard API, or
ObservableList<Cell> cellList =
FXCollections.observableArrayList(cell -> new Observable[] {Val.selectVar(cell.shapeProperty(), Shape::sizeProperty)});
using ReactFX. Then just add a ListChangeListener
to the list, and it will be notified if the size changes (or if the shape changes to a new shape with a different size). You can add as many observables that are properties (or properties of properties) of the cell in the returned array as you need.