我的问题涉及在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窗格)`,看起来像这样:
(我不知道我应该对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)如果是,那么我应该如何处理更新方法?
答案 0 :(得分:6)
(1)总的来说,这会有用吗,还是我错过了什么?
是的,它看起来应该有效。
(2)如果是,那我应该如何处理更新方法?
你可能不需要。
ObservableList
更新事件和提取器:
首先,请注意(根据Javadocs)ObservableList
上的更新事件是可选事件(并非所有ObservableList
都会触发它们)。更新事件旨在指示列表中的元素已更改其值,同时仍保留在同一索引的列表中。让ObservableList
生成更新事件的方法是使用&#34;提取器&#34;创建列表。例如,假设您的Slot
类定义了textProperty()
:
public class Slot {
private final StringProperty text = new SimpleStringProperty(this, "text", "");
public StringProperty textProperty() {
return text ;
}
// ...
}
然后,您可以通过调用FXCollections.observableArrayList(...)
method taking a Callback
来创建ObservableList<Slot>
,以便在任何元素的text属性发生更改时触发更新事件:
ObservableList<Slot> slots = FXCollections.observableArrayList(
(Slot slot) -> new Observable[] {slot.textProperty()} );
这里的Callback
是一个函数,将每个槽映射到Observable
s的数组:如果其中任何一个发生变化,列表将观察那些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
无需担心任何textProperty()
的{{1}}何时发生变化:Slot
中显示的Label
将自动更新。
因此,GridPane
可以真正忽略ListChangeListener
返回true的change
;只需处理wasUpdated()
和wasAdded()
即可。
另类解决方案
如果您想使用EasyBind framework考虑替代解决方案,请首先注意您可以按照上面显示的相同方式管理网格中wasRemoved()
的位置:
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
列表中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
。 EasyBind framework允许您创建一个ObservableList
,这是通过任意函数映射另一个ObservableList
中的每个元素的结果。
所以
ObservableList
创建的ObservableList<Node> nodes = EasyBind.map(slots, Slot::getSlotContents);
始终等于ObservableList<Node>
的每个元素调用getSlotContents()
的结果。
这允许您将slots
替换为单行:
ListChangeListener