如何使用ReactFX的自定义绑定?

时间:2017-01-10 23:53:02

标签: java javafx reactfx

this question中,我了解了如何通过更改包装对象来处理属性更改的问题,从而不会发送更改的更新。解决方案是使用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();
    }
}

如果这不是使用绑定为数据类创建表示的好方法,我也愿意设计建议。

1 个答案:

答案 0 :(得分:2)

ValVar是“可观察的monadics”(想想可观察的Optional s)。它们要么是空的,要么是值。 map方法就像Optional.map一样:如果Val为空,map会导致空Val;否则会导致Val包含将函数应用于原始Val值的结果。因此,如果sizeVar评估为null,则映射会导致空Val(因此您的素材设置为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();
    }
}