[英]JavaFX ListView showing old Cells, not clickable
我目前正在編寫一個待辦事項應用程序。
其中一部分是在 ListView 中顯示注釋和待辦事項,用戶可以在其中與它們進行交互。 但是,我有一個切換來確定存檔筆記和活動筆記之間的關系。 當切換 ObservableList 和 ListView 單元格時也會更新,但不知何故,最終會出現一些不再可交互的舊注釋。
前兩個音符是正確的,底部是剩下的,不可點擊。
我擴展了從 ListCell<> 顯示在 ListView 中的 NoteCell
import javafx.scene.control.*;
public class NoteCell extends ListCell<Note> {
@FXML
void initialize() { //Event Handlers}
@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);
}
}
Platform.runLater(new Runnable() {
@Override
public void run() {
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);
}
});
}
}
我正在考慮線程問題,但我沒有得到任何線索。 當更改為存檔視圖時,我正在獲取筆記的並行 stream,所以我認為它們應該同步,但我不知道如何實現這一點。 所以我嘗試了一個普通的 stream 但仍然是同樣的問題。
任何幫助,將不勝感激
我的概述 controller 的代碼,其中有 ListView。 主要有趣的應該是 toggleArchive 方法。
package mainpackage.controller;
import com.jfoenix.controls.*;
import javafx.application.Platform;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.control.ListView;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.AnchorPane;
import javafx.scene.text.Font;
import javafx.stage.FileChooser;
import javafx.stage.Stage;
import mainpackage.ListManager;
import mainpackage.Main;
import mainpackage.animation.FadeIn;
import mainpackage.exceptions.UnsupportedCellType;
import mainpackage.model.Note;
import mainpackage.model.Task;
import mainpackage.threads.ClockThread;
import mainpackage.threads.SaveThread;
import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.*;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
/**
* Main view after log in. Shows three different views of the created tasks.
*/
public class Overview {
@FXML
private ResourceBundle resources;
@FXML
private URL location;
@FXML
private AnchorPane rootPane;
@FXML
private Label dateLabel;
@FXML
private Label timeLabel;
@FXML
private ImageView overviewAddItemImage;
@FXML
private ImageView overviewAddNoteImage;
@FXML
private ImageView overviewCalendarImage;
@FXML
private ListView<Note> noteListView;
@FXML
private ImageView overviewExport;
@FXML
private JFXTextField noteListSearchField;
@FXML
private JFXComboBox<String> sortNoteListDropdown;
@FXML
private JFXToggleButton toggleArchiveButton;
@FXML
private ListView<Task> taskListView;
@FXML
private JFXTextField taskListSearchField;
@FXML
private JFXComboBox<String> sortTaskListDropdown;
private static final Logger logger = LogManager.getLogger(Main.class.getName());
private final ListManager listManager = new ListManager();
private final ObservableList<Task> usersTasks = FXCollections.synchronizedObservableList(FXCollections.observableArrayList());
private final ObservableList<Task> usersTasksSearch = FXCollections.synchronizedObservableList(FXCollections.observableArrayList());
private final ObservableList<Note> usersNotes = FXCollections.synchronizedObservableList(FXCollections.observableArrayList());
private final ObservableList<Note> usersNotesSearch = FXCollections.synchronizedObservableList(FXCollections.observableArrayList());
private final ClockThread clock = new ClockThread();
// private static final Logger log = LogManager.getLogger(Overview.class);
@FXML
synchronized void initialize() {
logger.info("Overview initializing");
//listManager.getNoteList().forEach(usersNotes::add);
//listManager.getTaskList().forEach(usersTasks::add);
//setLists();
overviewCalendarImage.setOnMouseClicked(mouseEvent -> loadCalendar());
overviewAddItemImage.setOnMouseClicked(mouseEvent -> loadAddTask());
overviewAddNoteImage.setOnMouseClicked(mouseEvent -> loadAddNote());
overviewExport.setOnMouseClicked(mouseEvent -> export());
toggleArchiveButton.selectedProperty().addListener((arg0, arg1, arg2) -> {
if(toggleArchiveButton.isSelected()) {
noteListSearchField.clear();
toggleArchive();
}
else {
noteListSearchField.clear();
toggleActive();
}
});
noteListSearchField.textProperty().addListener((observable, oldValue, newValue) -> {
//debugLogger.debug("Value Changed from: " + oldValue + " to " + newValue);
if (!newValue.trim().isEmpty() && usersNotes.size() > 0) {
usersNotesSearch.setAll(search(noteListSearchField.getText(), usersNotes));
noteListView.setItems(usersNotesSearch);
} else {
noteListView.setItems(usersNotes);
}
//debugLogger.debug("Search");
//debugLogger.info("TaskListView Size: " + todolistTaskList.getItems().size());
//debugLogger.info("TaskList Size: " + taskListView.size());
//debugLogger.info("tasks Arraylist Size: " + tasks.getTasks().size());
});
sortNoteListDropdown.setOnAction(event -> sortNotes(sortNoteListDropdown.getValue()));
sortNoteListDropdown.setValue("Sort by date (newest to oldest)");
//Initializing clock
clock.setLabels(timeLabel, dateLabel);
clock.setDaemon(true);
clock.start();
ExecutorService ex = Executors.newCachedThreadPool();
ex.execute(this::setNotes);
ex.execute(this::setTasks);
ex.shutdown();
sortNotes(sortNoteListDropdown.getValue());
}
/**
* Sorting notes depending on selected String in sortNoteListDropdown (dropdown menu to sort notes in overview)
* @param choice selected String in DropDown
*/
private void sortNotes(String choice) {
switch (choice) {
case "Sort by date (newest to oldest)":
sortDateDesc(usersNotes);
break;
case "Sort by date (oldest to newest)":
sortDateAsc(usersNotes);
break;
case "Sort alphabetically (A-Z)":
sortTitleAsc(usersNotes);
break;
case "Sort alphabetically (Z-A)":
sortTitleDesc(usersNotes);
break;
}
}
/**
* Clearing list of user's note and adding only archived notes.
* Result: only archived notes are shown when toggleArchiveButton is selected
*/
private synchronized void toggleArchive() {
usersNotes.clear();
listManager.getNoteList().filter(n -> n.getState()==2).forEach(usersNotes::add);
sortNotes(sortNoteListDropdown.getValue());
}
/**
* Clearing list of user's note and adding only archived notes.
* Result: only active notes are shown when toggleArchiveButton is not selected
*/
private void toggleActive() {
usersNotes.clear();
listManager.getNoteList().forEach((n) -> {
if (n.getState() == 0) {
usersNotes.add(n);
}
});
sortNotes(sortNoteListDropdown.getValue());
}
/**
* Sorting list of user's notes by date (descending)
* @param usersNotes list of user's notes
*/
private void sortDateDesc(ObservableList<Note> usersNotes) {
usersNotes.sort((t1, t2) -> t2.getCreationDate().compareTo(t1.getCreationDate()));
//debugLogger.info("List " + list.toString() + " sorted by takdates in descending order.");
}
/**
* Sorting list of user's notes by date (ascending)
* @param usersNotes list of user's notes
*/
private void sortDateAsc(ObservableList<Note> usersNotes) {
usersNotes.sort(Comparator.comparing(Note::getCreationDate));
//debugLogger.info("List " + list.toString() + " sorted by takdates in descending order.");
}
/**
* Sorting list of user's notes alphabetically (ascending)
* @param usersNotes list of user's notes
*/
private void sortTitleAsc(ObservableList<Note> usersNotes) {
usersNotes.sort(Comparator.comparing(n -> n.getTitle().toUpperCase()));
//debugLogger.info("List " + list.toString() + " sorted by title in ascending order.");
}
/**
* Sorting list of user's notes alphabetically (descending)
* @param usersNotes list of user's notes
*/
private void sortTitleDesc(ObservableList<Note> usersNotes) {
usersNotes.sort((n1, n2) -> n2.getTitle().toUpperCase().compareTo(n1.getTitle().toUpperCase()));
//debugLogger.info("List " + list.toString() + " sorted by title in descending order.");
}
/**
* Exporting notes and tasks into a .txt file on user's computer
*/
private void export() {
FileChooser fileChooser = new FileChooser();
FileChooser.ExtensionFilter extFilter = new FileChooser.ExtensionFilter("TXT files (*.txt)", "*.txt");
fileChooser.getExtensionFilters().add(extFilter);
fileChooser.setInitialFileName("Orga-Exports.txt");
File file = fileChooser.showSaveDialog(rootPane.getScene().getWindow());
if (file != null) {
SaveThread save = new SaveThread(file);
save.setDaemon(true);
save.start();
}
}
public synchronized void setNotes() {
// Placeholder if user has no notes
Label noNotes = new Label("No notes yet!");
noNotes.setFont(new Font(20));
noteListView.setPlaceholder(noNotes);
usersNotes.clear();
listManager.getNoteList().filter(t->t.getState()==0).forEach(usersNotes::add);
CellFactory cellFactory = new CellFactory();
noteListView.setCellFactory(NoteCell -> {
try {
return cellFactory.createCell("note");
} catch (UnsupportedCellType unsupportedCellType) {
unsupportedCellType.printStackTrace();
return new JFXListCell<>();
}
});
noteListView.setItems(usersNotes);
logger.info("Notes loaded to overview listview");
}
public synchronized void setTasks() {
// Placeholder if user has no tasks
Label noTasks = new Label("No tasks yet!");
noTasks.setFont(new Font(20));
taskListView.setPlaceholder(noTasks);
usersTasks.clear();
listManager.getTaskList().filter(t->t.getState()==0||t.getState()==1).forEach(usersTasks::add);
CellFactory cellFactory = new CellFactory();
taskListView.setCellFactory(TaskCell -> {
try {
return cellFactory.createCell("task");
} catch (UnsupportedCellType unsupportedCellType) {
unsupportedCellType.printStackTrace();
return new JFXListCell<>();
}
});
taskListView.setItems(usersTasks);
logger.info("Tasks loaded to overview listview");
}
private void loadAddNote() {
FXMLLoader loader = new FXMLLoader();
loader.setLocation(getClass().getResource("/view/CreateNotes.fxml"));
try {
loader.load();
} catch (IOException e) {
e.printStackTrace();
}
Parent root = loader.getRoot();
Stage stage = new Stage();
stage.setScene(new Scene(root));
stage.setResizable(false);
stage.getIcons().add(new Image("icon/Logo organizingTool 75x75 blue.png"));
overviewAddNoteImage.setDisable(true);
stage.showAndWait();
if (!toggleArchiveButton.isSelected()) {
usersNotes.add(listManager.getLatestNote());
}
sortNotes(sortNoteListDropdown.getValue());
overviewAddNoteImage.setDisable(false);
}
private void loadAddTask() {
FXMLLoader loader = new FXMLLoader();
loader.setLocation(getClass().getResource("/view/CreateTask.fxml"));
loader.setController(new CreateTask());
try {
loader.load();
} catch (IOException e) {
e.printStackTrace();
}
Parent root = loader.getRoot();
Stage stage = new Stage();
stage.setScene(new Scene(root));
stage.setResizable(false);
stage.getIcons().add(new Image("icon/Logo organizingTool 75x75 blue.png"));
overviewAddItemImage.setDisable(true);
stage.showAndWait();
overviewAddItemImage.setDisable(false);
}
private void loadCalendar() {
Stage stage = (Stage) rootPane.getScene().getWindow();
stage.setTitle("Calendar");
AnchorPane calendar = null;
FXMLLoader loader = new FXMLLoader();
loader.setLocation(getClass().getResource("/view/Calendar.fxml"));
try {
calendar = loader.load();
} catch (IOException e) {
e.printStackTrace();
}
new FadeIn(rootPane).play();
rootPane.getChildren().clear();
rootPane.getChildren().setAll(calendar);
}
private ArrayList<Note> search(String filter, ObservableList<Note> list) {
//debugLogger.info("Searching for the filter : " + filter + "in list " + list.toString());
ArrayList<Note> searchResult = new ArrayList<>();
if (!filter.isEmpty() && !filter.trim().equals("")) {
//debugLogger.info("Searching for a task containing the filter: '" + filter + "'.");
for (Note t : list) {
if (t.getTitle().toLowerCase().contains(filter.toLowerCase()) || t.getContent().toLowerCase().contains(filter.toLowerCase()) || t.getCreationDate().toString().contains(filter.toLowerCase())) {
searchResult.add(t);
}
}
return searchResult;
} else if (searchResult.isEmpty()) {
// debugLogger.info("No task found containing the filter: '" + filter + "'.");
} else {
searchResult.addAll(list);
}
return searchResult;
}
@FXML
void logout(ActionEvent event) {
ListManager.wipe();
AnchorPane login = null;
try {
login = FXMLLoader.load(getClass().getResource("/view/Login.fxml"));
} catch (IOException e) {
e.printStackTrace();
}
rootPane.getChildren().setAll(login);
new FadeIn(login).play();
}
}
您在發布的代碼中使用線程存在重大問題,我不會在這里解決,因為它們不是問題的主題。 但是,單元子類中的updateItem()
方法始終在 FX 應用程序線程上調用,因此任何使用Platform.runLater()
充其量都是多余的。
從 FX 應用程序線程調用Platform.runLater()
會將提供的Runnable
放入隊列中,以便稍后在同一線程上運行(基本上是當 FX 應用程序線程的所有待處理的事情都已完成時)。
updateItem()
方法可以非常頻繁地調用,尤其是當ListView
第一次顯示時,以及用戶正在滾動時。 (故意)沒有定義特定單元格調用其updateItem()
方法的順序以及使用哪些參數。 因此,一個單元格可能在基本上任意時間變為空或非空。
如果ListView
實現選擇暫時將單元格設為非空,然后立即將其設為空,則您的updateItem()
方法將在 FX 應用程序線程上快速連續調用兩次。 第一次調用將安排一個稍后運行的 runnable,它將圖形設置為 FXML 文件的內容。 第二次調用會將圖形設置為 null。 如果第二次調用發生在發布到隊列的可運行對象執行之前,則應該為空的單元格將顯示內容,因為對setGraphic()
的調用以錯誤的順序發生。
只需從updateItem()
中刪除Platform.runLater(...)
) 。
@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);
}
}
您當前的線程根本不起作用:您正在從多個線程訪問共享數據並從后台線程更新 UI 元素。 我建議刪除所有后台線程; 如果真的有任務需要在后台線程中運行,就需要學習一些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);
}
在這里,檢索和排序筆記的潛在長時間運行任務在后台線程中完成,並且僅在單獨的列表上運行,不會影響 UI 或它所依賴的任何數據。 任務完成后,在 FX 應用程序線程上調用的onSucceeded
處理程序使用新數據更新ListView
的項目。
您需要對所有異步調用進行類似的重構。 還要刪除所有低級synchronized
關鍵字,至少在此重構之后,這些關鍵字將是不必要的。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.