簡體   English   中英

JavaFX ListView 顯示舊單元格,不可點擊

[英]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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM