JavaFX:GridPane、ObservableList 和 ListChangeListener

标签 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 pane)`,它们看起来像这样:

(不过,我不知道我应该用 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) Overall, would this work, or am I missing something?

是的,看起来应该可以。

(2) If yes, then how I should handle the update method?

你可能不需要。

ObservableList更新事件和提取器:

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

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

然后你可以创建一个ObservableList<Slot>当通过调用 FXCollections.observableArrayList(...) method taking a Callback 更改任何元素的文本属性时,将触发更新事件:

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

Callback这是一个将每个插槽映射到 Observable 数组的函数s:列表将观察那些 Observable s 并在其中任何一个发生变化时触发更新事件。

GridPane不需要关心 Slot更新:

在您的场景中您不太可能需要它的原因是 Slot类封装了可以更改以创建更新事件的任何数据,以及 Slot 的 View 。 .因此它可以响应其数据的变化并更新其 View 本身: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()任何 Slot变化:Label显示在 GridPane 中将自动更新。

所以你的 ListChangeListener真的可以忽略change s wasUpdated()返回真;只需处理 wasAdded()wasRemoved()正如你已经做的那样。

替代解决方案

如果您想考虑使用 EasyBind framework 的替代解决方案, 首先注意你可以管理 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 s 的子节点列表始终是调用 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类定义了一个方法 bindContent将一个列表的内容绑定(bind)到 ObservableList同类型的。 EasyBind framework允许你创建一个 ObservableList这是将每个元素映射到另一个 ObservableList 中的结果通过任意函数。

所以

ObservableList<Node> nodes = EasyBind.map(slots, Slot::getSlotContents);

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

这允许您替换您的 ListChangeListener用单线:

Bindings.bindContent(pane.getChildren(), 
    EasyBind.map(slots, Slot::getSlotContents));

关于JavaFX:GridPane、ObservableList 和 ListChangeListener,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/25498747/

相关文章:

java - (JavaFX 8) 图形顶部的按钮文本

java - 如果MutableLiveData正在更改,为什么我的ViewModel不调用.observe()并通知适配器?

java - 正在绘制的盒内盒的数量为 N

java - 检测对象是否保存在 javafx 桌面应用程序中的最佳方法是什么?

java - 无法使用 JavaFX 播放 mp3 文件

java - 如何从多个嵌套 FXML 获取数据并调用其 Controller 上的方法

java - 使用 DateTimeFormatter.ofLocalizedDate 格式化 MonthDay

java - JFXtra 议程仅在特定时间之间显示?

java - 如何让2个动画连续运行

css - 自定义日期选择器 JavaFX 8