Мне нужно объяснить, почему он так себя ведет, и найти решение, как лучше всего это решить.
У меня есть пользовательский элемент управления JavaFX.Модель имеет список данных, который используется для построения элемента управления в представлении.У меня также есть Поведение, которое удалит переполнение списка, основываясь на ограничении, установленном на то, сколько значений должно быть в списке и показано в элементе управления.Когда элементы в списке удалены, он должен удалить их из представления.В MVC элемент управления - это модель, скин - это вид, а поведение - контроллер.
public class CustomControl
public class CustomControlSkin
public class CustomControlBehavior
У меня добавлен ListChangeListener к данным модели, который прослушивает добавления и удаления.Новые значения добавляются в элемент управления, а удаления должны быть удалены из него.
Я создал SSCCE, который иллюстрирует проблему.Сначала я использовал таймер в этом примере для имитации получения данных, которые поступают в другом потоке, чем поток JavaFX.В моем приложении у меня есть асинхронный сервис, который доставляет эти данные.Однако этот пример иллюстрирует проблему даже без таймера, но я хотел оставить его там.
Для простоты в примере просто создается метка для каждого значения в модели, и сначала создается 30 значений.Поскольку ограничение установлено на 10, элемент управления должен отображать только 10, но на самом деле отображает 10 + 30.
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Timer;
import java.util.TimerTask;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.collections.FXCollections;
import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class TestListRemovals extends Application {
private VBox root = new VBox();
private ObservableList<String> values = FXCollections.observableList(new LinkedList<String>());
public static void main(String[] args) {
TestListRemovals.launch(args);
}
/* (non-Javadoc)
* @see javafx.application.Application#start(javafx.stage.Stage)
*/
@Override
public void start(Stage primaryStage) throws Exception {
Scene scene = new Scene(root, 400, 600);
primaryStage.setScene(scene);
primaryStage.show();
values.addListener(new ListChangeListener<String>() {
@Override
public void onChanged(Change<? extends String> change) {
System.out.println("onChanged");
while (change.next()) {
System.out.println("change wasAdded " + change.wasAdded());
System.out.println("change wasPermutated " + change.wasPermutated());
System.out.println("change wasRemoved " + change.wasRemoved());
System.out.println("change wasReplaced " + change.wasReplaced());
System.out.println("change wasUpdated " + change.wasUpdated());
if (change.wasAdded()) {
System.out.println("Added " + change.getAddedSize());
addValues(change.getAddedSubList());
}
if (change.wasRemoved()) {
System.out.println("Removed " + change.getRemovedSize());
removeValues(change.getRemoved());
}
removeOverflow();
}
}
});
Timer delay = new Timer();
delay.schedule(new TimerTask() {
@Override
public void run() {
List<String> newValues = new LinkedList<>();
for (int i = 1; i < 31; i++) {
final String value = "Value " + i;
newValues.add(value);
}
Platform.runLater(() -> {
values.addAll(newValues);
});
}
}, 2000);
}
public void removeOverflow() {
System.out.println("removeOverflow");
final int limit = 10;
final int size = values.size();
final int overflow = size - limit;
if (overflow > 0) {
System.out.println("Remove from 0 to " + overflow);
values.remove(0, overflow);
}
}
public void addValues(List<? extends String> values) {
values.forEach(value -> {
final Label label = new Label(value);
root.getChildren().add(0, label);
});
}
public void removeValues(List<? extends String> values) {
values.forEach(value -> {
for (Iterator<Node> itr = root.getChildren().iterator(); itr.hasNext();) {
final Node node = itr.next();
if (node instanceof Label) {
if (((Label) node).getText().equals(value)) {
itr.remove();
}
}
}
});
}
}
Удаление фактически вызывает добавление оставшихся в списке,не удаленные элементы.
Я могу исправить это, поместив удаление в задачу JavaFX для выполнения в другом потоке.Однако это не похоже на правильное решение проблемы.Кроме того, почему удаление фактически вызывает второе добавление, не удаленных элементов, а оставшихся элементов?
Редактировать: кажется, что не рекомендуется изменять список из ListChangeListener.Любое предложение, как я могу изменить список за пределами ListChangeListener, который вызывается из дополнения?Я пытался с помощью Задачи JavaFX вызвать удаление, и это работает, но кажется излишним иметь поток в пользовательском элементе управления для каждого обновления своих данных.