JavaFX:GridPane,ObservableList和ListChangeListener

时间:2014-08-26 05:35:39

标签: java javafx javafx-8

我的问题涉及在JavaFX中正确使用ObservableList和ListChangeListeners(JavaFX 8,虽然我猜它在2.x中会类似)。作为一名JavaFX初学者,我问我是否已正确理解应该如何使用它们。

假设我有一个自定义对象列表(让我们称之为插槽)我想在GridPane中渲染:每个插槽都会知道GridPane'网格中的位置'它应该去(=行和列数据)。

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

以下设置是否有意义:

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

    });
}

其中Slot将有方法update(GridPane pane)addTO(GridPane pane)和removeFrom(GridPane窗格)`,看起来像这样:

(我不知道我应该对update(GridPane pane)做些什么。)

public class Slot {

...

    public void addTo(GridPane pane) {
        // slot contents might be e.g. a couple of String labels in a HBox.
        pane.add(this.getSlotContents(), this.getColumn(), this.getRow());
    }

    public void removeFrom(GridPane pane) {
        pane.getChildren().remove(this);
        // ?
    }

    public void update(GridPane pane) {
        // ????
    }
}

(1)总的来说,这会有用,还是我错过了什么?这是onChanged(ListChangeListener ...)应该如何使用的? (2)如果是,那么我应该如何处理更新方法?

1 个答案:

答案 0 :(得分:6)

  

(1)总的来说,这会有用吗,还是我错过了什么?

是的,它看起来应该有效。

  

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

你可能不需要。

ObservableList更新事件和提取器:

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

public class Slot {
    private final StringProperty text = new SimpleStringProperty(this, "text", "");
    public StringProperty textProperty() {
        return text ;
    }
    // ...
}

然后,您可以通过调用FXCollections.observableArrayList(...) method taking a Callback来创建ObservableList<Slot>,以便在任何元素的text属性发生更改时触发更新事件:

ObservableList<Slot> slots = FXCollections.observableArrayList( 
    (Slot slot) -> new Observable[] {slot.textProperty()} );

这里的Callback是一个函数,将每个槽映射到Observable s的数组:如果其中任何一个发生变化,列表将观察那些Observable和火灾更新事件。

GridPane不需要关心Slot更新

在您的方案中您不太可能需要这个的原因是Slot类封装了可能更改以创建更新事件的任何数据,以及Slot的视图。因此,它可以响应其数据中的更改并更新其视图本身:GridPane无需了解这些更改。

对于一个简单的示例,假设您的Slot班级上面只有textProperty()getSlotContents()只返回显示该文字的简单Label。你会:

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

GridPane无需担心任何textProperty()的{​​{1}}何时发生变化:Slot中显示的Label将自动更新。

因此,GridPane可以真正忽略ListChangeListener返回true的change;只需处理wasUpdated()wasAdded()即可。

另类解决方案

如果您想使用EasyBind framework考虑​​替代解决方案,请首先注意您可以按照上面显示的相同方式管理网格中wasRemoved()的位置:

Slot

现在,您的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列表中getSlotContents()调用的结果。您可以简化Slot实施:

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()); } } } } }); 中移除addToremoveFrom方法。

另请注意,Slot类定义了一个方法Bindings,它将一个列表的内容绑定到同一类型的bindContentEasyBind framework允许您创建一个ObservableList,这是通过任意函数映射另一个ObservableList中的每个元素的结果。

所以

ObservableList

创建的ObservableList<Node> nodes = EasyBind.map(slots, Slot::getSlotContents); 始终等于ObservableList<Node>的每个元素调用getSlotContents()的结果。

这允许您将slots替换为单行:

ListChangeListener