JavaFX:GridPane,ObservableList和ListChangeListener

kek*_*nen 4 java javafx javafx-8

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

假设我有一个自定义对象列表(我们称之为Slots),我想在GridPane中渲染:每个Slot都知道GridPane'grid'应该去哪里(=行和列数据).

将存在一个初始(数组)插槽列表,但由于列表的内容可能会发生变化,我认为创建一个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);
                    }
                } 
            }
        }

    });
}
Run Code Online (Sandbox Code Playgroud)

其中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) {
        // ????
    }
}
Run Code Online (Sandbox Code Playgroud)

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

Jam*_*s_D 6

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

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

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

你可能不需要.

ObservableList 更新事件和提取器:

首先,请注意(根据Javadocs),更新事件ObservableList是可选事件(并非所有事件ObservableList都会触发它们).更新事件旨在指示列表中的元素已更改其值,同时仍保留在同一索引的列表中.有一个的方式ObservableList产生更新事件是与"提取"创建列表.例如,假设您的Slot类定义了textProperty():

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

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

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

Callback是一个将每个槽映射到一个Observables 数组的函数:Observable如果它们中的任何一个发生变化,列表将观察那些s和fire更新事件.

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 ;
    }
}
Run Code Online (Sandbox Code Playgroud)

GridPane不用关心时textProperty()的任何Slot变化:Label在显示S. GridPane将自动更新.

所以你ListChangeListener真的可以忽略changes wasUpdated()返回true; 刚刚处理wasAdded()wasRemoved()因为你已经做的.

另类解决方案

如果您想使用EasyBind框架考虑替代解决方案,请首先注意您可以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);
    }

    // ...
}
Run Code Online (Sandbox Code Playgroud)

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

});
Run Code Online (Sandbox Code Playgroud)

并从中删除addToremoveFrom方法Slot.

但请注意,Bindings该类定义了一个方法bindContent,该方法将一个列表的内容绑定到ObservableList相同类型的内容.所述EasyBind框架允许创建一个ObservableList其是在另一个每个元素映射的结果ObservableList通过一个任意的功能.

所以

ObservableList<Node> nodes = EasyBind.map(slots, Slot::getSlotContents);
Run Code Online (Sandbox Code Playgroud)

创建一个ObservableList<Node>始终等于调用getSlotContents()每个元素的结果slots.

这允许您使用单线代替您ListChangeListener:

Bindings.bindContent(pane.getChildren(), 
    EasyBind.map(slots, Slot::getSlotContents));
Run Code Online (Sandbox Code Playgroud)