Существуют значительные проблемы с использованием потоков в опубликованном вами коде, которые я не буду здесь рассматривать, поскольку они не являются главной c вопросом. Однако метод updateItem()
в подклассе ячейки всегда вызывается в потоке приложения FX, поэтому любое использование Platform.runLater()
в лучшем случае избыточно.
Вызов Platform.runLater()
из потока приложения FX приведет к предоставленный Runnable
в очередь для запуска в том же потоке в более позднее время (по сути, когда все ожидающие обработки потока приложения FX были завершены).
Метод updateItem()
может вызываться довольно часто , особенно когда впервые отображается ListView
и когда пользователь выполняет прокрутку. Не существует (преднамеренно) определенного порядка, в котором для определенных c ячеек вызываются их методы updateItem()
и с какими параметрами. Таким образом, ячейка может становиться пустой или непустой практически в произвольное время.
Если реализация ListView
решит временно сделать ячейку непустой, а затем немедленно сделать ее пустой, ваш метод updateItem()
будет вызывается дважды в быстрой последовательности в потоке приложения FX. Первый вызов будет планировать запуск исполняемого файла, который будет запущен позже, который устанавливает graphi c в содержимое файла F XML. Второй вызов установит для графа c значение null. Если второй вызов происходит до того, как запускаемый объект, помещенный в очередь, будет выполнен, в ячейке, которая должна быть пустой, будет отображаться содержимое, потому что вызовы setGraphic()
происходят в неправильном порядке.
Просто удалите Platform.runLater(...)
от updateItem()
.
@Override
protected void updateItem(Note note, boolean empty) {
super.updateItem(note,empty);
if (empty || note == null) {
setText(null);
setGraphic(null);
} else {
if (fxmlLoader == null) {
fxmlLoader = new FXMLLoader(getClass().getResource("/view/NoteCells.fxml"));
fxmlLoader.setController(this);
try {
fxmlLoader.load();
} catch (IOException e) {
e.printStackTrace();
logger.error("IOException: " + e);
}
}
cellNoteTitle.setText(note.getTitle());
cellNoteDescription.setText(note.getContent());
cellNoteDescription.setWrapText(true);
cellNoteDescription.maxWidth(394);
cellNoteDescription.minWidth(394);
cellNoteDate.setText(String.valueOf(note.getCreationDate()));
setText(null);
setGraphic(rootPane);
}
}
Текущая потоковая передача просто не работает: вы получаете доступ к общим данным из нескольких потоков и обновляете элементы пользовательского интерфейса из фоновых потоков. Я бы рекомендовал удалить все фоновые потоки; если действительно есть задачи, которые необходимо запускать в фоновом потоке, вам необходимо изучить некоторые материалы по параллелизму JavaFX. Прочтите этот пост и этот учебник для начала.
Вкратце, асинхронная реализация вашего метода toggleArchive()
может выглядеть примерно так:
// move this to an instance field:
private ExecutorService ex = Executors.newCachedThreadPool();
private void toggleArchive() {
final String choice = sortNoteListDropdown.getValue();
Task<List<Note>> getNotesTask = new Task<>() {
@Override
public List<Note> call() {
List<Note> notes = listManager.getNoteList()
.filter(n -> n.getState()==2)
.collect(Collectors.toList());
// sort notes here based on choice
// (note you could sort the stream after the filter
// operation instead)
return notes ;
}
};
getNotesTask.setOnSucceeded(e -> userNotes.setAll(getNotesTask.getValue()));
ex.submit(getNotesTask);
}
Здесь потенциально длительная задача извлечения и сортировки заметок выполняется в фоновом потоке и работает исключительно с отдельным списком, не затрагивая пользовательский интерфейс или какие-либо данные, на которые он опирается. Когда задача завершается, обработчик onSucceeded
, который вызывается в потоке приложения FX, обновляет элементы ListView
новыми данными.
Вам необходимо провести аналогичный рефакторинг для всех асинхронных вызовов . Также удалите все ключевые слова низкого уровня synchronized
, которые, по крайней мере, после этого рефакторинга, станут ненужными.