将一个 observableList 镜像到另一个
mirror one observableList to another
在 JavaFX 中,我有一个 ObservableList
个对象,并且想要另一个 ObservableList
来镜像第一个列表但包含每个对象的字符串表示形式。有什么比编写自定义 ListChangeListener
来进行转换更简单的吗?我有一个 StringConverter
可以提供镜像值。
同样,给定一个 ObservableList<String>
,我如何创建第二个 ObservableList<String>
,它在索引 0 处有一个常量条目,并镜像从索引 1 开始的第一个列表?
对于第一个问题,最简单的方法是使用EasyBind框架。那么就这么简单
ObservableList<String> stringList = EasyBind.map(myBaseList, myConverter::toString);
这是一个使用 EasyBind 的 SSCCE:
import org.fxmisc.easybind.EasyBind;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.collections.FXCollections;
import javafx.collections.ListChangeListener.Change;
import javafx.collections.ObservableList;
import javafx.util.StringConverter;
public class MappedAndTransformedListExample {
public static void main(String[] ags) {
ObservableList<Person> baseList = FXCollections.observableArrayList(
new Person("Jacob", "Smith", "jacob.smith@example.com"),
new Person("Isabella", "Johnson", "isabella.johnson@example.com"),
new Person("Ethan", "Williams", "ethan.williams@example.com"),
new Person("Emma", "Jones", "emma.jones@example.com")
);
StringConverter<Person> converter = new StringConverter<Person>() {
@Override
public String toString(Person person) {
return person.getFirstName() + " " + person.getLastName();
}
@Override
public Person fromString(String string) {
int indexOfDelimiter = string.indexOf(' ');
return new Person(string.substring(0, indexOfDelimiter),
string.substring(indexOfDelimiter+1),
"");
}
};
ObservableList<String> namesList = EasyBind.map(baseList, converter::toString);
namesList.forEach(System.out::println);
namesList.addListener((Change<? extends String> c) -> {
while (c.next()) {
if (c.wasAdded()) {
System.out.println("Added "+c.getAddedSubList());
}
}
});
System.out.println("\nAdding Michael to base list...\n");
baseList.add(new Person("Michael", "Brown", "michael.brown@example.com"));
namesList.forEach(System.out::println);
}
public static class Person {
private final StringProperty firstName = new SimpleStringProperty(this, "firstName");
private final StringProperty lastName = new SimpleStringProperty(this, "lastName");
private final StringProperty email = new SimpleStringProperty(this, "email");
public Person(String firstName, String lastName, String email) {
this.firstName.set(firstName);
this.lastName.set(lastName);
this.email.set(email);
}
public final StringProperty firstNameProperty() {
return this.firstName;
}
public final String getFirstName() {
return this.firstNameProperty().get();
}
public final void setFirstName(final String firstName) {
this.firstNameProperty().set(firstName);
}
public final StringProperty lastNameProperty() {
return this.lastName;
}
public final java.lang.String getLastName() {
return this.lastNameProperty().get();
}
public final void setLastName(final java.lang.String lastName) {
this.lastNameProperty().set(lastName);
}
public final StringProperty emailProperty() {
return this.email;
}
public final java.lang.String getEmail() {
return this.emailProperty().get();
}
public final void setEmail(final java.lang.String email) {
this.emailProperty().set(email);
}
}
}
如果您出于某种原因不想使用第三方框架,您可以使用 TransformationList
(这是 EasyBind 在幕后所做的:我从那里的源代码中复制了下面的代码并且修改了它)。
在上面,您将替换
ObservableList<String> namesList = EasyBind.map(baseList, converter::toString);
和
ObservableList<String> namesList = new TransformationList<String, Person>(baseList) {
@Override
public int getSourceIndex(int index) {
return index ;
}
@Override
public String get(int index) {
return converter.toString(getSource().get(index));
}
@Override
public int size() {
return getSource().size();
}
@Override
protected void sourceChanged(Change<? extends Person> c) {
fireChange(new Change<String>(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 int getPermutation(int i) {
return c.getPermutation(i);
}
@Override
protected int[] getPermutation() {
// This method is only called by the superclass methods
// wasPermutated() and getPermutation(int), which are
// both overriden by this class. There is no other way
// this method can be called.
throw new AssertionError("Unreachable code");
}
@Override
public List<String> getRemoved() {
ArrayList<String> res = new ArrayList<>(c.getRemovedSize());
for(Person removedPerson: c.getRemoved()) {
res.add(converter.toString(removedPerson));
}
return res;
}
@Override
public int getFrom() {
return c.getFrom();
}
@Override
public int getTo() {
return c.getTo();
}
@Override
public boolean next() {
return c.next();
}
@Override
public void reset() {
c.reset();
}
});
}
};
对于第二个问题,你必须使用转换列表。这是一个更新的 main(...)
方法,展示了如何执行此操作。 (它与第 1 部分的第二个版本一样有效。)
public static void main(String[] ags) {
ObservableList<Person> baseList = FXCollections.observableArrayList(
new Person("Jacob", "Smith", "jacob.smith@example.com"),
new Person("Isabella", "Johnson", "isabella.johnson@example.com"),
new Person("Ethan", "Williams", "ethan.williams@example.com"),
new Person("Emma", "Jones", "emma.jones@example.com")
);
StringConverter<Person> converter = new StringConverter<Person>() {
@Override
public String toString(Person person) {
return person.getFirstName() + " " + person.getLastName();
}
@Override
public Person fromString(String string) {
int indexOfDelimiter = string.indexOf(' ');
return new Person(string.substring(0, indexOfDelimiter),
string.substring(indexOfDelimiter+1),
"");
}
};
ObservableList<String> namesList = EasyBind.map(baseList, converter::toString);
ObservableList<String> namesListWithHeader = new TransformationList<String, String>(namesList) {
@Override
public int getSourceIndex(int index) {
return index - 1 ;
}
@Override
public String get(int index) {
if (index == 0) {
return "Contacts";
} else {
return getSource().get(index - 1);
}
}
@Override
public int size() {
return getSource().size() + 1 ;
}
@Override
protected void sourceChanged(Change<? extends String> c) {
fireChange(new Change<String>(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 int getPermutation(int i) {
return c.getPermutation(i - 1) + 1;
}
@Override
protected int[] getPermutation() {
// This method is only called by the superclass methods
// wasPermutated() and getPermutation(int), which are
// both overriden by this class. There is no other way
// this method can be called.
throw new AssertionError("Unreachable code");
}
@Override
public List<String> getRemoved() {
ArrayList<String> res = new ArrayList<>(c.getRemovedSize());
for(String removed: c.getRemoved()) {
res.add(removed);
}
return res;
}
@Override
public int getFrom() {
return c.getFrom() + 1;
}
@Override
public int getTo() {
return c.getTo() + 1;
}
@Override
public boolean next() {
return c.next();
}
@Override
public void reset() {
c.reset();
}
});
}
};
namesListWithHeader.forEach(System.out::println);
namesListWithHeader.addListener((Change<? extends String> c) -> {
while (c.next()) {
if (c.wasAdded()) {
System.out.println("Added "+c.getAddedSubList());
System.out.println("From: "+c.getFrom()+", To: "+c.getTo());
}
}
});
System.out.println("\nAdding Michael to base list...\n");
baseList.add(new Person("Michael", "Brown", "michael.brown@example.com"));
namesListWithHeader.forEach(System.out::println);
}
这里是 James_D 使用 Lombok 的 @Delegate 的答案的一个稍微简短的版本,以避免必须编写所有委托方法
/**
* A List that mirrors a base List by applying a converter on all items.
* @param <E> item type of the target List
* @param <F> item type of the base list
*/
public class MirroringList<E, F> extends TransformationList<E, F> implements ObservableList<E> {
/** mapping function from base list item type to target list item type */
private final Function<F, E> converter;
public MirroringList(ObservableList<? extends F> list, Function<F, E> converter) {
super(list);
this.converter = converter;
}
@Override
public int getSourceIndex(int index) {
return index;
}
@Override
public E get(int index) {
return converter.apply(getSource().get(index));
}
@Override
public int size() {
return getSource().size();
}
@Override
protected void sourceChanged(Change<? extends F> change) {
fireChange(new DelegatingChange(change, this));
}
/**
* An implementation of {@link Change} that delegates all methods to a specified change except {@link #getRemoved()}
*/
private class DelegatingChange extends Change<E> implements DelegatingChangeExcluded<E> {
@Delegate(excludes = DelegatingChangeExcluded.class)
private final Change<? extends F> change;
public DelegatingChange(Change<? extends F> change, MirroringList<E, F> list) {
super(list);
this.change = change;
}
@Override
protected int[] getPermutation() {
return new int[0];
}
@Override
public List<E> getRemoved() {
return change.getRemoved().stream()
.map(converter)
.collect(Collectors.toList());
}
}
/**
* This interface is only used to exclude some methods from delegated methods via Lombok's @{@link Delegate}
* so that the compiler doesn't complain.
*/
@SuppressWarnings("unused")
private interface DelegatingChangeExcluded<E> {
List<E> getRemoved();
ObservableList<? extends E> getList();
List<E> getAddedSubList();
}
}
在 JavaFX 中,我有一个
ObservableList
个对象,并且想要另一个ObservableList
来镜像第一个列表但包含每个对象的字符串表示形式。有什么比编写自定义ListChangeListener
来进行转换更简单的吗?我有一个StringConverter
可以提供镜像值。同样,给定一个
ObservableList<String>
,我如何创建第二个ObservableList<String>
,它在索引 0 处有一个常量条目,并镜像从索引 1 开始的第一个列表?
对于第一个问题,最简单的方法是使用EasyBind框架。那么就这么简单
ObservableList<String> stringList = EasyBind.map(myBaseList, myConverter::toString);
这是一个使用 EasyBind 的 SSCCE:
import org.fxmisc.easybind.EasyBind;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.collections.FXCollections;
import javafx.collections.ListChangeListener.Change;
import javafx.collections.ObservableList;
import javafx.util.StringConverter;
public class MappedAndTransformedListExample {
public static void main(String[] ags) {
ObservableList<Person> baseList = FXCollections.observableArrayList(
new Person("Jacob", "Smith", "jacob.smith@example.com"),
new Person("Isabella", "Johnson", "isabella.johnson@example.com"),
new Person("Ethan", "Williams", "ethan.williams@example.com"),
new Person("Emma", "Jones", "emma.jones@example.com")
);
StringConverter<Person> converter = new StringConverter<Person>() {
@Override
public String toString(Person person) {
return person.getFirstName() + " " + person.getLastName();
}
@Override
public Person fromString(String string) {
int indexOfDelimiter = string.indexOf(' ');
return new Person(string.substring(0, indexOfDelimiter),
string.substring(indexOfDelimiter+1),
"");
}
};
ObservableList<String> namesList = EasyBind.map(baseList, converter::toString);
namesList.forEach(System.out::println);
namesList.addListener((Change<? extends String> c) -> {
while (c.next()) {
if (c.wasAdded()) {
System.out.println("Added "+c.getAddedSubList());
}
}
});
System.out.println("\nAdding Michael to base list...\n");
baseList.add(new Person("Michael", "Brown", "michael.brown@example.com"));
namesList.forEach(System.out::println);
}
public static class Person {
private final StringProperty firstName = new SimpleStringProperty(this, "firstName");
private final StringProperty lastName = new SimpleStringProperty(this, "lastName");
private final StringProperty email = new SimpleStringProperty(this, "email");
public Person(String firstName, String lastName, String email) {
this.firstName.set(firstName);
this.lastName.set(lastName);
this.email.set(email);
}
public final StringProperty firstNameProperty() {
return this.firstName;
}
public final String getFirstName() {
return this.firstNameProperty().get();
}
public final void setFirstName(final String firstName) {
this.firstNameProperty().set(firstName);
}
public final StringProperty lastNameProperty() {
return this.lastName;
}
public final java.lang.String getLastName() {
return this.lastNameProperty().get();
}
public final void setLastName(final java.lang.String lastName) {
this.lastNameProperty().set(lastName);
}
public final StringProperty emailProperty() {
return this.email;
}
public final java.lang.String getEmail() {
return this.emailProperty().get();
}
public final void setEmail(final java.lang.String email) {
this.emailProperty().set(email);
}
}
}
如果您出于某种原因不想使用第三方框架,您可以使用 TransformationList
(这是 EasyBind 在幕后所做的:我从那里的源代码中复制了下面的代码并且修改了它)。
在上面,您将替换
ObservableList<String> namesList = EasyBind.map(baseList, converter::toString);
和
ObservableList<String> namesList = new TransformationList<String, Person>(baseList) {
@Override
public int getSourceIndex(int index) {
return index ;
}
@Override
public String get(int index) {
return converter.toString(getSource().get(index));
}
@Override
public int size() {
return getSource().size();
}
@Override
protected void sourceChanged(Change<? extends Person> c) {
fireChange(new Change<String>(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 int getPermutation(int i) {
return c.getPermutation(i);
}
@Override
protected int[] getPermutation() {
// This method is only called by the superclass methods
// wasPermutated() and getPermutation(int), which are
// both overriden by this class. There is no other way
// this method can be called.
throw new AssertionError("Unreachable code");
}
@Override
public List<String> getRemoved() {
ArrayList<String> res = new ArrayList<>(c.getRemovedSize());
for(Person removedPerson: c.getRemoved()) {
res.add(converter.toString(removedPerson));
}
return res;
}
@Override
public int getFrom() {
return c.getFrom();
}
@Override
public int getTo() {
return c.getTo();
}
@Override
public boolean next() {
return c.next();
}
@Override
public void reset() {
c.reset();
}
});
}
};
对于第二个问题,你必须使用转换列表。这是一个更新的 main(...)
方法,展示了如何执行此操作。 (它与第 1 部分的第二个版本一样有效。)
public static void main(String[] ags) {
ObservableList<Person> baseList = FXCollections.observableArrayList(
new Person("Jacob", "Smith", "jacob.smith@example.com"),
new Person("Isabella", "Johnson", "isabella.johnson@example.com"),
new Person("Ethan", "Williams", "ethan.williams@example.com"),
new Person("Emma", "Jones", "emma.jones@example.com")
);
StringConverter<Person> converter = new StringConverter<Person>() {
@Override
public String toString(Person person) {
return person.getFirstName() + " " + person.getLastName();
}
@Override
public Person fromString(String string) {
int indexOfDelimiter = string.indexOf(' ');
return new Person(string.substring(0, indexOfDelimiter),
string.substring(indexOfDelimiter+1),
"");
}
};
ObservableList<String> namesList = EasyBind.map(baseList, converter::toString);
ObservableList<String> namesListWithHeader = new TransformationList<String, String>(namesList) {
@Override
public int getSourceIndex(int index) {
return index - 1 ;
}
@Override
public String get(int index) {
if (index == 0) {
return "Contacts";
} else {
return getSource().get(index - 1);
}
}
@Override
public int size() {
return getSource().size() + 1 ;
}
@Override
protected void sourceChanged(Change<? extends String> c) {
fireChange(new Change<String>(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 int getPermutation(int i) {
return c.getPermutation(i - 1) + 1;
}
@Override
protected int[] getPermutation() {
// This method is only called by the superclass methods
// wasPermutated() and getPermutation(int), which are
// both overriden by this class. There is no other way
// this method can be called.
throw new AssertionError("Unreachable code");
}
@Override
public List<String> getRemoved() {
ArrayList<String> res = new ArrayList<>(c.getRemovedSize());
for(String removed: c.getRemoved()) {
res.add(removed);
}
return res;
}
@Override
public int getFrom() {
return c.getFrom() + 1;
}
@Override
public int getTo() {
return c.getTo() + 1;
}
@Override
public boolean next() {
return c.next();
}
@Override
public void reset() {
c.reset();
}
});
}
};
namesListWithHeader.forEach(System.out::println);
namesListWithHeader.addListener((Change<? extends String> c) -> {
while (c.next()) {
if (c.wasAdded()) {
System.out.println("Added "+c.getAddedSubList());
System.out.println("From: "+c.getFrom()+", To: "+c.getTo());
}
}
});
System.out.println("\nAdding Michael to base list...\n");
baseList.add(new Person("Michael", "Brown", "michael.brown@example.com"));
namesListWithHeader.forEach(System.out::println);
}
这里是 James_D 使用 Lombok 的 @Delegate 的答案的一个稍微简短的版本,以避免必须编写所有委托方法
/**
* A List that mirrors a base List by applying a converter on all items.
* @param <E> item type of the target List
* @param <F> item type of the base list
*/
public class MirroringList<E, F> extends TransformationList<E, F> implements ObservableList<E> {
/** mapping function from base list item type to target list item type */
private final Function<F, E> converter;
public MirroringList(ObservableList<? extends F> list, Function<F, E> converter) {
super(list);
this.converter = converter;
}
@Override
public int getSourceIndex(int index) {
return index;
}
@Override
public E get(int index) {
return converter.apply(getSource().get(index));
}
@Override
public int size() {
return getSource().size();
}
@Override
protected void sourceChanged(Change<? extends F> change) {
fireChange(new DelegatingChange(change, this));
}
/**
* An implementation of {@link Change} that delegates all methods to a specified change except {@link #getRemoved()}
*/
private class DelegatingChange extends Change<E> implements DelegatingChangeExcluded<E> {
@Delegate(excludes = DelegatingChangeExcluded.class)
private final Change<? extends F> change;
public DelegatingChange(Change<? extends F> change, MirroringList<E, F> list) {
super(list);
this.change = change;
}
@Override
protected int[] getPermutation() {
return new int[0];
}
@Override
public List<E> getRemoved() {
return change.getRemoved().stream()
.map(converter)
.collect(Collectors.toList());
}
}
/**
* This interface is only used to exclude some methods from delegated methods via Lombok's @{@link Delegate}
* so that the compiler doesn't complain.
*/
@SuppressWarnings("unused")
private interface DelegatingChangeExcluded<E> {
List<E> getRemoved();
ObservableList<? extends E> getList();
List<E> getAddedSubList();
}
}