我的问题是ObservableList和ListChangeListeners在JavaFX中的正确用法(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);
}
}
}
}
});
}其中,插槽将有方法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)如果是,那么我应该如何处理更新方法?
发布于 2014-08-26 16:36:48
(1)整体而言,这是可行的,还是我遗漏了一些东西?
是的,看上去应该管用。
(2)如果是,我应该如何处理更新方法?
你可能不需要。
ObservableList 更新事件和提取器:
首先,注意(根据Javadocs),ObservableList上的更新事件是可选事件(并非所有ObservableList都会触发它们)。Update事件旨在指示列表中的元素已更改其值,同时仍保留在相同索引下的列表中。让ObservableList生成更新事件的方法是使用“提取器”创建列表。例如,假设Slot类定义了一个textProperty()
public class Slot {
private final StringProperty text = new SimpleStringProperty(this, "text", "");
public StringProperty textProperty() {
return text ;
}
// ...
}然后,您可以创建一个ObservableList<Slot>,当任何元素的text属性更改时,该事件将通过调用Callback触发更新事件。
ObservableList<Slot> slots = FXCollections.observableArrayList(
(Slot slot) -> new Observable[] {slot.textProperty()} );这里的Callback是一个函数,将每个插槽映射到一个Observable的数组中:列表将观察这些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不需要关注任何Slot的textProperty()何时发生变化:显示在GridPane中的Label将自动更新。
因此,您的ListChangeListener实际上可以忽略wasUpdated()返回true的change;只需像已经处理的那样处理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的子节点列表始终是在Slot列表上调用getSlotContents()的结果。
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());
}
}
}
}
});并将addTo和removeFrom方法从Slot中删除。
但是还要注意,Bindings类定义了一个方法bindContent,该方法将一个列表的内容绑定到相同类型的ObservableList。EasyBind framework允许您创建一个ObservableList,这是通过任意函数映射另一个ObservableList中的每个元素的结果。
所以
ObservableList<Node> nodes = EasyBind.map(slots, Slot::getSlotContents);创建一个ObservableList<Node>,它总是等于对slots的每个元素调用getSlotContents()的结果。
这允许您将ListChangeListener替换为一行:
Bindings.bindContent(pane.getChildren(),
EasyBind.map(slots, Slot::getSlotContents));https://stackoverflow.com/questions/25498747
复制相似问题