装饰ObservableList并保留更改事件的最佳做法 [英] Best practice to decorate an ObservableList and retain change events
问题描述
我的数据源提供了一个 ObservableList< String>
,但是对于我的ListView,我需要一个 ObservableList< Warning>
。
My datasource provides an ObservableList<String>
, however for my ListView I need an ObservableList<Warning>
.
A 警告
基本上只是字符串的装饰器,添加一个布尔值来提供跟踪状态的方法的复选框我的ListView,如此答案中提出的。
A Warning
is basically just a decorator for the strings, adding a boolean to provide a means for tracking the state of checkboxes my ListView, as proposed in this answer.
class Warning {
private final ReadOnlyStringWrapper name;
private final BooleanProperty checked;
/* ... */
}
目前,我正在观察原始列表中的更改事件,并手动添加/删除警告列表中的项目:
Currently I am observing change-events in the original list and add/remove items in the warnings list manually:
ObservableList<String> stringList = ...;
ObservableList<Warning> warningList = ...;
stringList.addListener(new ListChangeListener<String>() {
@Override
public void onChanged(ListChangeListener.Change<? extends String> change) {
if (change.wasAdded()) {
warningList.addAll(change.getAddedSubList().stream().map(Warning::new).collect(Collectors.toList()));
} else if (change.wasRemoved()) {
change.getRemoved().forEach(str -> {
warningList.removeIf(w -> str.equals(w.name));
});
}
}
});
我的问题是:是否有更优雅的方式来装饰我的字符串所以它可以用作警告类型的列表,而不用手动通过更改事件?
My question is: Is there a more elegant way to decorate my String-typed list, so it can be used as a Warning-typed list without manually passing through change events?
更精确的是:如果将字符串添加到或删除从原始列表中,我想立即在Warnings列表中查看此更改,从而查看ListView。
To be more precise: If a string is added to or removed from the original list, I want to see this change immediately in the Warnings-list and thus the ListView.
推荐答案
自从你发布以来,一直在想这个。使用EasyBind,如我在注释中所建议的,将无法正常工作,因为每次调用 get(...)时都会创建一个新的
在映射列表中。所以警告
I've been thinking about this a bit since you posted it. Using EasyBind as I had suggested in the comment won't work, since it would create a new Warning
every time you called get(...)
on the mapped list. So
stringList.add("foo");
warningList.get(0).setChecked(true);
assert warningList.get(0).isChecked();
将失败。
此外,您的机制出错(我想)如果您在源列表( stringList
)中有重复的条目,因为您将从 warningList
当单个元素从 stringList
中删除时。实际上,删除的元素是正确的是非常棘手的。
Additionally, your mechanism goes wrong (I think) if you have duplicate entries in the source list (stringList
), as you would remove all corresponding entries from the warningList
when a single element was removed from the stringList
. In fact, getting the removed elements correct is quite tricky.
这是一个基于Tomas Mikula的 MappedList
源元素和映射元素之间。它使用 IdentityHashMap
来确保重复的元素在两个列表中的行为正确。请注意,这仅适用于在项目添加到源列表中时要创建新对象的特定情况,因此它不适用于(并且不起作用)作为EasyBind中的机制的替代。
Here is a solution based on Tomas Mikula's MappedList
which caches the mapping between the source elements and the mapped elements. It uses an IdentityHashMap
to ensure that duplicate elements behave correctly in both lists. Note this only works for the specific case where you want to create new objects when items are added to the source list, so it is not intended (and would not work) as a replacement for the mechanism in EasyBind.
import java.util.ArrayList;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.function.Function;
import javafx.application.Application;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.ReadOnlyStringProperty;
import javafx.beans.property.ReadOnlyStringWrapper;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.collections.FXCollections;
import javafx.collections.ListChangeListener.Change;
import javafx.collections.ObservableList;
import javafx.collections.transformation.TransformationList;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.ListView;
import javafx.scene.control.TextField;
import javafx.scene.control.cell.CheckBoxListCell;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class WrappedObjectListExample extends Application {
@Override
public void start(Stage primaryStage) {
ObservableList<String> stringList = FXCollections.observableArrayList("One", "Two", "Three");
ObservableList<Warning> warningList = new CachingMappedList<Warning, String>(stringList, Warning::new);
ListView<String> stringListView = new ListView<>(stringList);
ListView<Warning> warningListView = new ListView<>(warningList);
warningListView.setCellFactory(CheckBoxListCell.forListView(Warning::checkedProperty));
TextField textField = new TextField();
textField.setOnAction(e -> {
if (! textField.getText().isEmpty()) {
stringList.add(textField.getText());
textField.setText("");
}
});
Button remove = new Button("Remove");
remove.setOnAction(e -> stringList.remove(stringListView.getSelectionModel().getSelectedIndex()));
remove.disableProperty().bind(stringListView.getSelectionModel().selectedItemProperty().isNull());
HBox lists = new HBox(10, stringListView, warningListView);
VBox root = new VBox(10, lists, textField, remove);
root.setPadding(new Insets(20));
primaryStage.setScene(new Scene(root));
primaryStage.show();
}
public static class Warning {
private final ReadOnlyStringWrapper name = new ReadOnlyStringWrapper();
private final BooleanProperty checked = new SimpleBooleanProperty();
public Warning(String name) {
this.name.set(name);
}
public final ReadOnlyStringProperty nameProperty() {
return this.name.getReadOnlyProperty();
}
public final String getName() {
return this.nameProperty().get();
}
public final BooleanProperty checkedProperty() {
return this.checked;
}
public final boolean isChecked() {
return this.checkedProperty().get();
}
public final void setChecked(final boolean checked) {
this.checkedProperty().set(checked);
}
@Override
public String toString() {
return getName();
}
}
public static class CachingMappedList<S,T> extends TransformationList<S, T> {
private final Function<T, S> mapper ;
private final IdentityHashMap<T, S> cache ;
public CachingMappedList(ObservableList<T> source, Function<T,S> mapper) {
super(source);
this.mapper = mapper ;
this.cache = new IdentityHashMap<>();
}
@Override
protected void sourceChanged(Change<? extends T> c) {
fireChange(new Change<S>(this) {
@Override
public boolean wasAdded() {
return c.wasAdded();
}
@Override
public boolean wasRemoved() {
return c.wasRemoved();
}
@Override
public boolean wasReplaced() {
return c.wasReplaced();
}
@Override
public boolean wasUpdated() {
return c.wasUpdated();
}
@Override
public boolean wasPermutated() {
return c.wasPermutated();
}
@Override
public boolean next() {
return c.next();
}
@Override
public void reset() {
c.reset();
}
@Override
public int getFrom() {
return c.getFrom();
}
@Override
public int getTo() {
return c.getTo();
}
@Override
public List<S> getRemoved() {
List<S> removed = new ArrayList<>();
c.getRemoved().forEach(t -> removed.add(cache.get(t)));
return removed;
}
@Override
public int getPermutation(int i) {
return c.getPermutation(i);
}
@Override
protected int[] getPermutation() {
throw new AssertionError("Unreachable code");
}
});
// clean up cache:
c.reset();
while (c.next()) {
if (c.wasRemoved()) {
c.getRemoved().forEach(cache::remove);
}
}
}
@Override
public int getSourceIndex(int index) {
return index ;
}
@Override
public S get(int index) {
return cache.computeIfAbsent(getSource().get(index), mapper);
}
@Override
public int size() {
return getSource().size();
}
}
public static void main(String[] args) {
launch(args);
}
}
这篇关于装饰ObservableList并保留更改事件的最佳做法的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!