Зеркально отразить один observableList другому
В JavaFX у меня есть
ObservableList
объектов, и хочу другогоObservableList
это будет зеркально отражать первый список, но будет содержать строковое представление каждого объекта. Есть ли что-нибудь проще, чем написать кастомListChangeListener
сделать преобразование? у меня естьStringConverter
который может обеспечить зеркальное отображение.Точно так же, учитывая
ObservableList<String>
как мне создать вторуюObservableList<String>
который имеет постоянную запись с индексом 0 и отражает первый список, начинающийся с индекса 1?
2 ответа
По первому вопросу, самый простой способ сделать это - использовать инфраструктуру EasyBind. Тогда это так же просто, как
ObservableList<String> stringList = EasyBind.map(myBaseList, myConverter::toString);
Вот SSCCE, использующий EasyBind:
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();
}
}