JavaFX:GridPane、ObservableList和ListChangeListener

JavaFX:GridPane、ObservableList和ListChangeListener,java,javafx,javafx-8,Java,Javafx,Javafx 8,我的问题涉及JavaFX(JavaFX8,尽管我猜它在2.x中会类似)中ObservableList和ListChangeListeners的正确使用。由于我是一名JavaFX初学者,我想问自己是否正确理解了如何使用它们 假设我有一个自定义对象列表(我们称之为Slot),我想在GridPane中呈现这些对象:每个Slot都知道它应该在GridPane“grid”中的哪个位置(=行和列数据) 将存在插槽的初始(数组)列表,但由于列表的内容可能会发生更改,我认为创建一个ObservableList并

我的问题涉及JavaFX(JavaFX8,尽管我猜它在2.x中会类似)中ObservableList和ListChangeListeners的正确使用。由于我是一名JavaFX初学者,我想问自己是否正确理解了如何使用它们

假设我有一个自定义对象列表(我们称之为Slot),我想在GridPane中呈现这些对象:每个Slot都知道它应该在GridPane“grid”中的哪个位置(=行和列数据)

将存在插槽的初始(数组)列表,但由于列表的内容可能会发生更改,我认为创建一个ObservableList并附加一个ListChangeListener是有意义的。然而,我对
change.wasdupdated()
etc.方法做了些什么感到有点困惑

以下设置是否合理:

public class SlotViewController {

@FXML
private GridPane pane;

private ObservableList<Slot> slots;

@FXML
public void initialize() {
    List<Slot> originalSlots = ...

    slots = FXCollections.observableList(originalSlots);
    slots.addListener(new ListChangeListener<Slot>() {

        @Override
        public void onChanged(ListChangeListener.Change<? extends Slot> change) {
            while (change.next()) {
                if (change.wasUpdated()) {
                    for (Slot slot : change.getList()) {
                        slot.update(pane);
                    }
                } else {
                    for (Slot removedSlot : change.getRemoved()) {
                        removedSlot.removeFrom(pane);
                    }

                    for (Slot addedSlot : change.getAddedSubList()) {
                        addedSlot.addTO(pane);
                    }
                } 
            }
        }

    });
}
(1) 总的来说,这是可行的,还是我遗漏了什么?这就是
onChanged(ListChangeListener…
的使用方式吗? (2) 如果是,那么我应该如何处理更新方法

(1) 总的来说,这是可行的,还是我遗漏了什么

是的,看起来应该能用

(2) 如果是,那么我应该如何处理更新方法

你可能不需要

可观察列表
更新事件和提取器:

首先,请注意(根据)可观察列表上的更新事件是可选事件(并非所有的可观察列表都会触发它们)。更新事件旨在指示列表中的元素已更改其值,但仍保留在列表中的同一索引处。让
可观察列表
生成更新事件的方法是使用“提取器”创建列表。例如,假设您的
Slot
类定义了一个
textProperty()

然后,您可以创建一个
ObservableList
,当任何元素的文本属性更改时,该列表将通过调用以下命令触发更新事件:

GridPane
无需关心任何
插槽的
textProperty()
何时更改:显示在
GridPane
中的
标签将自动更新

因此,您的
ListChangeListener
实际上可以忽略
change
s,因为它的
wasUpdate()
返回true;只需像您已经做的那样处理
wasAdded()
wasaremove()

替代解决方案

如果您想考虑另一种解决方案,请首先注意,您可以像上面所示的那样管理网格中的<代码>插槽< /代码>的位置:

public class Slot {
    private final IntegerProperty column = new SimpleIntegerProperty(this, "column");
    public IntegerProperty columnProperty() {
        return column ;
    }
    public final int getColumn() {
        return columnProperty().get();
    }
    public final void setColumn(int column) {
        columnProperty().set(column);
    }

    // similarly for row...

    public Slot(int column, int row) {
        column.addListener((obs, oldColumn, newColumn) -> 
            GridPane.setColumnIndex(getSlotContents(), newColumn.intValue()));
        // similarly for row
        setColumn(column);
        setRow(row);
    }

    // ...
}
现在,您的
ListChangeListener
只需确保
GridPane
s子节点列表始终是调用
Slot
s列表上的
getSlotContents()
的结果。您可以简化
ListChangeListener
实现:

slots.addListener(new ListChangeListener<Slot>() {

    @Override
    public void onChanged(ListChangeListener.Change<? extends Slot> change) {
        while (change.next()) {
            if (change.wasAdded()) {
                for (Slot slot : change.getAddedSublist) {
                    pane.getChildren().add(slot.getSlotContents());
                }
            } else if (change.wasRemoved()) {
                for (Slot slot : change.getRemoved()) {
                    pane.getChildren().remove(slot.getSlotContents());
                }
            }
        }
    }

});
创建一个
可观察列表
,该列表始终等于在
插槽的每个元素上调用
getSlotContents()
的结果

这允许您用一行代码替换
ListChangeListener

Bindings.bindContent(pane.getChildren(), 
    EasyBind.map(slots, Slot::getSlotContents));
public class Slot {
    private final StringProperty text = new SimpleStringProperty(this, "text", "");
    public StringProperty textProperty() {
        return text ;
    }
    public final String getText() {
        return textProperty().get();
    }
    public final void setText(String text) {
        textProperty().set(text);
    }

    private final Label label = new Label();

    public Slot(String text) {
        label.textProperty().bind(text);
        setText(text);
    }

    public Node getSlotContents() {
        return label ;
    }
}
public class Slot {
    private final IntegerProperty column = new SimpleIntegerProperty(this, "column");
    public IntegerProperty columnProperty() {
        return column ;
    }
    public final int getColumn() {
        return columnProperty().get();
    }
    public final void setColumn(int column) {
        columnProperty().set(column);
    }

    // similarly for row...

    public Slot(int column, int row) {
        column.addListener((obs, oldColumn, newColumn) -> 
            GridPane.setColumnIndex(getSlotContents(), newColumn.intValue()));
        // similarly for row
        setColumn(column);
        setRow(row);
    }

    // ...
}
slots.addListener(new ListChangeListener<Slot>() {

    @Override
    public void onChanged(ListChangeListener.Change<? extends Slot> change) {
        while (change.next()) {
            if (change.wasAdded()) {
                for (Slot slot : change.getAddedSublist) {
                    pane.getChildren().add(slot.getSlotContents());
                }
            } else if (change.wasRemoved()) {
                for (Slot slot : change.getRemoved()) {
                    pane.getChildren().remove(slot.getSlotContents());
                }
            }
        }
    }

});
ObservableList<Node> nodes = EasyBind.map(slots, Slot::getSlotContents);
Bindings.bindContent(pane.getChildren(), 
    EasyBind.map(slots, Slot::getSlotContents));