如何使用 ReactFX 的 Var 进行自定义绑定?
How to do a custom binding with ReactFX's Var?
在 中,我看到了如何处理 属性 更改的问题,方法是更改它的包装对象,从而不发送它已更改的更新。解决方案是使用 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));
}
所以现在如果 shape
属性 本身发生变化,它也会触发 size
的变化(除非新形状具有相同的大小)。但是现在我想使用自定义绑定绑定到 属性,我遇到了一个问题,解释如下。
我的数据类是这些:
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
}
我想通过将它们的属性绑定到 GUI 属性来为它们创建 GUI 表示。我是这样做的:
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
}
当我运行第一个绑定选项下面的代码将创建一个透明圆柱体。第二个选项将创建一个白色(默认颜色)圆柱体。我不知道为什么会这样。
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();
}
}
如果这不是使用绑定创建数据表示的好方法 类,我也乐于接受设计建议。
Val
和 Var
是 "observable monadics"(想想可观察到的 Optional
s)。它们要么是空的,要么有一个值。 map
方法的工作原理与 Optional.map
相同:如果 Val
为空,则 map
会导致空 Val
;否则它会导致 Val
包含将函数应用于原始 Val
值的结果。因此,如果 sizeVar
计算为 null
,则映射结果为空 Val
(因此您的 material 设置为 null
),甚至不计算您的 lambda 表达式。
要处理null
(即空Val
s),你应该使用orElse
或类似的方法:
private class ShapeRepresentation extends Cylinder {
Val<Shape> shape;
private ShapeRepresentation() {
super(100, 100);
shape = Val.wrap(cell.shapeProperty());
Var<Number> sizeVar = shape.selectVar(Shape::sizeProperty);
// THIS WILL WORK
materialProperty().bind(shape
.map(s -> new PhongMaterial(Color.RED))
.orElseConst(new PhongMaterial(Color.TRANSPARENT)));
// SO WILL THIS
materialProperty().bind(sizeVar
.map(n -> {
if (n.intValue() == 1) return new PhongMaterial(Color.RED) ;
if (n.intValue() == 2) return new PhongMaterial(Color.BLUE) ;
return new PhongMaterial(Color.WHITE);
})
.orElseConst(new PhongMaterial(Color.TRANSPARENT)));
}
}
更新的测试示例:
import javafx.application.Application;
import javafx.beans.binding.Bindings;
import javafx.geometry.Insets;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.control.CheckBox;
import javafx.scene.control.ComboBox;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
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);
ComboBox<Integer> sizeCombo = new ComboBox<>();
sizeCombo.getItems().addAll(0, 1, 2);
Shape shape = new Shape();
shape.sizeProperty().bind(sizeCombo.valueProperty());
CheckBox showShape = new CheckBox("Show shape");
cell.shapeProperty().bind(Bindings.when(showShape.selectedProperty()).then(shape).otherwise((Shape)null));
HBox controls = new HBox(5, showShape, sizeCombo);
controls.setPadding(new Insets(5));
BorderPane root = new BorderPane(group, controls, null, null, null);
root.setBackground(null);
Scene scene = new Scene(root, 400, 400, Color.AQUA);
stage.setScene(scene);
stage.show();
}
}
在
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));
}
所以现在如果 shape
属性 本身发生变化,它也会触发 size
的变化(除非新形状具有相同的大小)。但是现在我想使用自定义绑定绑定到 属性,我遇到了一个问题,解释如下。
我的数据类是这些:
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
}
我想通过将它们的属性绑定到 GUI 属性来为它们创建 GUI 表示。我是这样做的:
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
}
当我运行第一个绑定选项下面的代码将创建一个透明圆柱体。第二个选项将创建一个白色(默认颜色)圆柱体。我不知道为什么会这样。
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();
}
}
如果这不是使用绑定创建数据表示的好方法 类,我也乐于接受设计建议。
Val
和 Var
是 "observable monadics"(想想可观察到的 Optional
s)。它们要么是空的,要么有一个值。 map
方法的工作原理与 Optional.map
相同:如果 Val
为空,则 map
会导致空 Val
;否则它会导致 Val
包含将函数应用于原始 Val
值的结果。因此,如果 sizeVar
计算为 null
,则映射结果为空 Val
(因此您的 material 设置为 null
),甚至不计算您的 lambda 表达式。
要处理null
(即空Val
s),你应该使用orElse
或类似的方法:
private class ShapeRepresentation extends Cylinder {
Val<Shape> shape;
private ShapeRepresentation() {
super(100, 100);
shape = Val.wrap(cell.shapeProperty());
Var<Number> sizeVar = shape.selectVar(Shape::sizeProperty);
// THIS WILL WORK
materialProperty().bind(shape
.map(s -> new PhongMaterial(Color.RED))
.orElseConst(new PhongMaterial(Color.TRANSPARENT)));
// SO WILL THIS
materialProperty().bind(sizeVar
.map(n -> {
if (n.intValue() == 1) return new PhongMaterial(Color.RED) ;
if (n.intValue() == 2) return new PhongMaterial(Color.BLUE) ;
return new PhongMaterial(Color.WHITE);
})
.orElseConst(new PhongMaterial(Color.TRANSPARENT)));
}
}
更新的测试示例:
import javafx.application.Application;
import javafx.beans.binding.Bindings;
import javafx.geometry.Insets;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.control.CheckBox;
import javafx.scene.control.ComboBox;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
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);
ComboBox<Integer> sizeCombo = new ComboBox<>();
sizeCombo.getItems().addAll(0, 1, 2);
Shape shape = new Shape();
shape.sizeProperty().bind(sizeCombo.valueProperty());
CheckBox showShape = new CheckBox("Show shape");
cell.shapeProperty().bind(Bindings.when(showShape.selectedProperty()).then(shape).otherwise((Shape)null));
HBox controls = new HBox(5, showShape, sizeCombo);
controls.setPadding(new Insets(5));
BorderPane root = new BorderPane(group, controls, null, null, null);
root.setBackground(null);
Scene scene = new Scene(root, 400, 400, Color.AQUA);
stage.setScene(scene);
stage.show();
}
}