【问题标题】:Do properties of properties make sense?属性的属性有意义吗?
【发布时间】:2017-03-22 13:15:51
【问题描述】:

因为这是一个关于设计的问题,所以我会先说我拥有什么以及我想要什么。

我有一个使用组合的设计。 Cell 对象包含 ShapeBackground 对象(此示例的自定义对象)。这两个都有自己的数据来定义它们。这是代码中的示例:

class Cell {

    Shape shape;
    Background background;

    class Shape {

        int size;
        Color color;
        Point location;
        //...
    }

    class Background {

        Color color;
        String name;
        CoverType type;
        //...
    }
}

我还有一个需要表示许多单元格的 GUI,并且我已经编写了如何操作(如何使用颜色、大小等在屏幕上创建我想要的内容)。它包括诸如 CellRepresentation、ShapeRepresentation 和 BackgroundRepresentation 之类的类,它们的显示属性绑定到数据属性(我认为这称为模型和视图)。

我希望能够通过更改上述数据来表示 GUI 中的变化:

  • 用户可以(例如)右键单击形状并设置其颜色。所以上面的数据发生了变化,变化需要在GUI中体现出来。
  • 用户还可以更改整个形状(例如从另一个单元格复制粘贴)。甚至整个细胞。这些更改也需要反映在 GUI 中。

我的问题是哪些类成员需要是我绑定到的 JavaFX 属性。

这就是我的想法:“叶子”属性(大小、颜色、位置...)必须是属性,因此我可以将 GUI 属性绑定到它们。但是我是否也需要制作形状和背景对象属性?只有它们的属性在屏幕上具有“实际”表示。理想情况下,我会喜欢它,如果 Shape 发生变化,那么它的所有属性都会告诉它们的绑定它们可能已经改变(也许颜色没有,但大小没有)。但它不能以这种方式工作 - 即使 Shape 的 Color 可以在 Shape 更改时更改,Color 属性也不会告诉绑定到它的任何内容它已更改

在有许多单元格的较大图片中将 Cell 设置为属性也是如此:委托更改的属性的属性。

所以我想使 Shape 和 Background 也成为属性,并向它们注册 InvalidationListener 以更新它们的属性。这似乎不正确,因为我认为有了对属性的所有支持,就会有一种方法可以做我想做的事。

有人可以建议一种方法吗?

【问题讨论】:

  • @zyexal 我知道继承,java 对我来说并不陌生。这有什么关系?
  • 您可能想看看Bindings.selectXXX 方法,它为您提供了一种绑定到“属性的属性”的方法。这个 API 有一种“遗留”的感觉,因为它不是类型安全的或编译时可验证的。 Tomas Mikula 有一个library,它对相同的想法进行了更现代的实现,请参阅this post
  • 最重要的是,您应该将 JavaFX 属性用于任何可能更改的内容,并且您可能希望对这些更改做出反应。 (所以可能你所有的领域......)。

标签: java javafx javafx-2 reactfx


【解决方案1】:

仅使用标准 JavaFX API,您就可以利用 Bindings.selectXXX 方法来观察“属性的属性”。

例如:

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

}

将产生(期望的)输出

Size changed from 0 to 10
Size changed from 10 to 0
Size changed from 0 to 20

这个 API 有一点遗留的感觉,因为它依赖于将属性名称作为字符串传递,因此不是类型安全的,无法在编译时检查。此外,如果任何中间属性为 null(例如,如果 cel.getShape() 在此示例中返回 null),则绑定会生成烦人且冗长的警告消息(即使这应该是受支持的用例)。

Tomas Mikula 在他的ReactFX library 中有一个更现代的实现,有关描述,请参阅this post。使用 ReactFX,你会这样做:

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

最后,如果您要创建一个单元格列表,您可以创建一个ObservableList,指定一个extractor。提取器是将列表中的每个元素(每个 Cell)映射到 Observables 的数组的函数。如果这些Observables 中的任何一个发生更改,则列表会触发更新事件。所以你可以这样做

ObservableList<Cell> cellList = 
    FXCollections.observableArrayList(cell -> new Observable[] {Bindings.selectInteger(cell.shapeProperty(), "size")});

使用标准 API,或

ObservableList<Cell> cellList = 
    FXCollections.observableArrayList(cell -> new Observable[] {Val.selectVar(cell.shapeProperty(), Shape::sizeProperty)});

使用 ReactFX。然后只需将ListChangeListener 添加到列表中,如果大小发生变化(或形状更改为具有不同大小的新形状),它将得到通知。您可以根据需要在返回的数组中添加任意数量的作为单元格的属性(或属性的属性)的 observable。

【讨论】:

  • ReactFX 似乎是一个更好的选择。我绝不会在我的属性反射样式中使用字符串名称——到处都是麻烦。谢谢!
  • @Mark 我在你的问题中添加了 ReactFX 标签,以防更了解它的人想要添加任何东西。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-01-08
  • 2012-01-08
  • 1970-01-01
  • 2014-09-15
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多