首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >JavaFX: GridPane,ObservableList和ListChangeListener

JavaFX: GridPane,ObservableList和ListChangeListener
EN

Stack Overflow用户
提问于 2014-08-26 05:35:39
回答 1查看 8.6K关注 0票数 4

我的问题是ObservableList和ListChangeListeners在JavaFX中的正确用法(JavaFX 8,虽然我猜在2.x中类似)。因为我是JavaFX初学者,所以我想问我是否正确地理解了应该如何使用它们。

假设我有一个自定义对象的列表(让我们把它们称为插槽),我想在GridPane中呈现它们:每个插槽都知道在GridPane‘网格’中它应该去哪里(=行和列数据)。

将有一个初始的(数组)插槽列表,但是由于列表的内容可能会发生变化,所以我认为创建一个ObservableList并将一个ListChangeListener附加到其中是有意义的。但是,我对如何使用change.wasUpdated()等方法感到有点困惑。

下面的设置有意义吗?

代码语言:javascript
复制
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);
                    }
                } 
            }
        }

    });
}

其中,插槽将有方法update(GridPane pane)addTO(GridPane pane)和removeFrom(GridPane窗格),这些方法看起来如下所示:

(不过,我不知道该如何处理update(GridPane pane)。)

代码语言:javascript
复制
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)如果是,那么我应该如何处理更新方法?

EN

回答 1

Stack Overflow用户

回答已采纳

发布于 2014-08-26 16:36:48

(1)整体而言,这是可行的,还是我遗漏了一些东西?

是的,看上去应该管用。

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

你可能不需要。

ObservableList 更新事件和提取器:

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

代码语言:javascript
复制
public class Slot {
    private final StringProperty text = new SimpleStringProperty(this, "text", "");
    public StringProperty textProperty() {
        return text ;
    }
    // ...
}

然后,您可以创建一个ObservableList<Slot>,当任何元素的text属性更改时,该事件将通过调用Callback触发更新事件。

代码语言:javascript
复制
ObservableList<Slot> slots = FXCollections.observableArrayList( 
    (Slot slot) -> new Observable[] {slot.textProperty()} );

这里的Callback是一个函数,将每个插槽映射到一个Observable的数组中:列表将观察这些Observable,如果其中任何一个更改,则触发更新事件。

GridPane 不需要关心 Slot 更新:

在您的场景中不太可能需要这一点的原因是,Slot类封装了任何可能更改以创建更新事件的数据,以及Slot的视图。因此,它可以对数据中的更改做出响应,并自行更新视图:GridPane不需要知道任何有关这些更改的信息。

对于一个简单的例子,假设您的Slot类只有上面的textProperty(),而getSlotContents()只是返回一个显示该文本的简单Label。你可能会:

代码语言:javascript
复制
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不需要关注任何SlottextProperty()何时发生变化:显示在GridPane中的Label将自动更新。

因此,您的ListChangeListener实际上可以忽略wasUpdated()返回true的change;只需像已经处理的那样处理wasAdded()wasRemoved()

一种替代解决方案

如果要考虑使用EasyBind framework解决此问题的替代方案,请首先注意,您可以按照上面所示的相同方式管理Slot在网格中的位置:

代码语言:javascript
复制
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的子节点列表始终是在Slot列表上调用getSlotContents()的结果。

代码语言:javascript
复制
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类定义了一个方法bindContent,该方法将一个列表的内容绑定到相同类型的ObservableListEasyBind framework允许您创建一个ObservableList,这是通过任意函数映射另一个ObservableList中的每个元素的结果。

所以

代码语言:javascript
复制
ObservableList<Node> nodes = EasyBind.map(slots, Slot::getSlotContents);

创建一个ObservableList<Node>,它总是等于对slots的每个元素调用getSlotContents()的结果。

这允许您将ListChangeListener替换为一行:

代码语言:javascript
复制
Bindings.bindContent(pane.getChildren(), 
    EasyBind.map(slots, Slot::getSlotContents));
票数 6
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/25498747

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档