我的问题涉及在 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());
}
}
}
}
});
并删除 addTo
和 removeFrom
来自 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/