【问题标题】:How to do a custom binding with ReactFX's Var?如何使用 ReactFX 的 Var 进行自定义绑定?
【发布时间】:2017-05-25 15:11:25
【问题描述】:

在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();
    }
}

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

【问题讨论】:

  • 最后一个代码块中的size 是什么?你真的不需要if (sizeVar.getValue().intValue() == 1) { ...} 之类的东西吗?
  • @James_D size 是 Shape 的属性。我之前确实尝试过你写的那行(取值来自sizeVar),但没有区别。我将创建一个 MCVE。我认为只是错误的用法。
  • 是的,我很困惑。当然size 是Shape 的成员,但绑定在Shape 类之外。这无论如何也不会编译,因为size 是一个IntegerProperty,并且不能与== 与1 进行比较。如果该版本不起作用,则绑定可能正在被垃圾收集。我的回答无效吗?
  • @James_D 这是我将解决的无 MCVE 混淆。你的解决方案在我的情况下不起作用,实际上产生的结果比我发布的结果“更糟糕”,所以至少在某种程度上它们并不相同。请稍等……
  • @James_D 好的,这在我的代码中有点令人费解,但我修复了它。因此,现在我作弊并根据您的回答提出了一个略有不同的问题,但主题仍然相同。我希望没有人会介意...

标签: java javafx reactfx


【解决方案1】:

Val 和 Var 是“可观察的单子”(认为可观察的 Optionals)。它们要么是空的,要么是有值的。 map 方法的工作原理与 Optional.map 类似:如果 Val 为空,则 map 将导致空 Val;否则会导致Val 包含将函数应用于原始Val 的值的结果。因此,如果 sizeVar 的计算结果为 null,则映射会产生一个空的 Val(因此您的材质设置为 null),甚至无需评估您的 lambda 表达式。

要处理null(即空Vals),你应该使用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();
    }
}

【讨论】:

  • 这里用的sizeVar在哪里?
  • @Mark 不是,但您发布的绑定也不依赖它(他们只需要知道形状是否为空/空)。我假设你在代码中的其他地方仍然需要它。
  • 对不起,我认为在绑定中使用sizeVar 会起作用,但它不起作用。我累了s -&gt; { if(sizeVar.getValue().intValue() == 1) return new PhongMaterial(Color.RED); else return new PhongMaterial(Color.BLUE); 我需要告诉绑定听sizeVar。
  • @Mark 您尝试实现的实际逻辑是什么? (包括形状和/或大小为空的情况。)
  • 如果形状或大小为空,则单元格中不显示任何形状。我通过使其透明来做到这一点。如果不是,那么颜色会因算法的大小而异。例如:对于尺寸 == 1 红色,对于尺寸 ==2 蓝色...否则为白色。
猜你喜欢
  • 1970-01-01
  • 2017-06-24
  • 2011-06-01
  • 2018-04-07
  • 2014-07-20
  • 1970-01-01
  • 1970-01-01
  • 2018-01-17
  • 2013-01-20
相关资源
最近更新 更多