如何在Java FXML待办清单中实现已完成任务功能?
待办清单已完成任务功能实现方案
我正在用Java FXML、文件读写和Scene Builder制作待办清单,一直没法成功添加已完成任务功能。试过单独维护已完成任务列表,也尝试用FilteredList的谓词做筛选转换,都没效果。代码其他部分运行正常,以下是调整后的完整实现代码:
ToDoItem类(新增完成状态属性)
package todolist; import java.time.LocalDate; public class ToDoItem { private String shortDescription; private String details; private LocalDate deadline; // 新增任务完成状态标记 private boolean isCompleted; public ToDoItem(String shortDescription, String details, LocalDate deadline) { this.shortDescription = shortDescription; this.details = details; this.deadline = deadline; this.isCompleted = false; // 默认未完成 } public String getShortDescription() { return shortDescription; } public void setShortDescription(String shortDescription) { this.shortDescription = shortDescription; } public String getDetails() { return details; } public void setDetails(String details) { this.details = details; } public LocalDate getDeadline() { return deadline; } public void setDeadline(LocalDate deadline) { this.deadline = deadline; } // 完成状态的getter和setter public boolean isCompleted() { return isCompleted; } public void setCompleted(boolean completed) { isCompleted = completed; } }
ToDoData类(整合状态管理与数据读写)
package todolist; import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.time.LocalDate; import java.time.format.DateTimeFormatter; import java.util.Iterator; import javafx.collections.FXCollections; import javafx.collections.ObservableList; public class ToDoData { private static ToDoData instance = new ToDoData(); private static String filename = "TodoListItems.txt"; private ObservableList<ToDoItem> todoItems; private DateTimeFormatter formatter; public static ToDoData getInstance() { return instance; } private ToDoData() { formatter = DateTimeFormatter.ofPattern("dd-MM-yyyy"); } public ObservableList<ToDoItem> getTodoItems() { return todoItems; } public void addTodoItem(ToDoItem item) { todoItems.add(item); } public void loadTodoItems() throws IOException { todoItems = FXCollections.observableArrayList(); Path path = Paths.get(filename); BufferedReader br = Files.newBufferedReader(path); String input; try { while ((input = br.readLine()) != null) { String[] itemPieces = input.split("\t"); String shortDescription = itemPieces[0]; String details = itemPieces[1]; String dateString = itemPieces[2]; // 兼容旧数据,无完成状态则默认未完成 boolean isCompleted = itemPieces.length > 3 && Boolean.parseBoolean(itemPieces[3]); LocalDate date = LocalDate.parse(dateString, formatter); ToDoItem todoItem = new ToDoItem(shortDescription, details, date); todoItem.setCompleted(isCompleted); todoItems.add(todoItem); } } finally { if(br != null) br.close(); } } public void storeTodoItems() throws IOException { Path path = Paths.get(filename); BufferedWriter bw = Files.newBufferedWriter(path); try { Iterator<ToDoItem> iter = todoItems.iterator(); while(iter.hasNext()) { ToDoItem item = iter.next(); // 写入完成状态到文件 bw.write(String.format("%s\t%s\t%s\t%s", item.getShortDescription(), item.getDetails(), item.getDeadline().format(formatter), item.isCompleted())); bw.newLine(); } } finally { if(bw != null) bw.close(); } } public void deleteTodoItem(ToDoItem item) { todoItems.remove(item); } // 标记任务为已完成 public void markAsCompleted(ToDoItem item){ item.setCompleted(true); // 通知列表更新视图 todoItems.set(todoItems.indexOf(item), item); } // 标记任务为未完成 public void markAsUncompleted(ToDoItem item){ item.setCompleted(false); todoItems.set(todoItems.indexOf(item), item); } }
FXMLDocumentController类(完善UI交互与筛选逻辑)
package todolist; import java.io.IOException; import java.net.URL; import java.time.LocalDate; import java.time.format.DateTimeFormatter; import java.util.Comparator; import java.util.List; import java.util.Optional; import java.util.ResourceBundle; import java.util.function.Predicate; import javafx.application.Platform; import javafx.beans.value.ChangeListener; import javafx.beans.value.ObservableValue; import javafx.collections.ObservableList; import javafx.collections.transformation.FilteredList; import javafx.collections.transformation.SortedList; import javafx.event.ActionEvent; import javafx.event.EventHandler; import javafx.fxml.FXML; import javafx.fxml.FXMLLoader; import javafx.fxml.Initializable; import javafx.scene.control.Alert; import javafx.scene.control.Button; import javafx.scene.control.ButtonType; import javafx.scene.control.ContextMenu; import javafx.scene.control.Dialog; import javafx.scene.control.Label; import javafx.scene.control.ListCell; import javafx.scene.control.ListView; import javafx.scene.control.MenuItem; import javafx.scene.control.SelectionMode; import javafx.scene.control.TextArea; import javafx.scene.control.ToggleButton; import javafx.scene.input.KeyCode; import javafx.scene.input.KeyEvent; import javafx.scene.input.MouseEvent; import javafx.scene.layout.BorderPane; import javafx.scene.paint.Color; import javafx.util.Callback; public class FXMLDocumentController { private List<ToDoItem> todoItems; @FXML private ListView<ToDoItem> todoListView; @FXML private TextArea itemDetailsTextArea; @FXML private Label deadlineLabel; @FXML private BorderPane mainBorderPane; @FXML private ContextMenu listContextMenu; @FXML private ToggleButton filterToggleButton; @FXML private Button exit,ADD; private FilteredList<ToDoItem> filteredList; private Predicate<ToDoItem> wantAllItems; private Predicate<ToDoItem> wantTodaysItems; private Predicate<ToDoItem> wantCompletedItems; private Predicate<ToDoItem> wantUncompletedItems; public void initialize() { // 初始化上下文菜单 listContextMenu = new ContextMenu(); MenuItem deleteMenuItem = new MenuItem("Delete"); deleteMenuItem.setOnAction(event -> { ToDoItem item = todoListView.getSelectionModel().getSelectedItem(); deleteItem(item); }); MenuItem completedMenuItem = new MenuItem("Mark as complete"); completedMenuItem.setOnAction(event -> { ToDoItem item = todoListView.getSelectionModel().getSelectedItem(); completedItem(item); }); MenuItem uncompletedMenuItem = new MenuItem("Mark as uncomplete"); uncompletedMenuItem.setOnAction(event -> { ToDoItem item = todoListView.getSelectionModel().getSelectedItem(); uncompletedItem(item); }); listContextMenu.getItems().addAll(deleteMenuItem, completedMenuItem, uncompletedMenuItem); // 列表选中事件监听 todoListView.getSelectionModel().selectedItemProperty().addListener((observable, oldValue, newValue) -> { if(newValue != null) { itemDetailsTextArea.setText(newValue.getDetails()); DateTimeFormatter df = DateTimeFormatter.ofPattern("MMMM d, yyyy"); deadlineLabel.setText(df.format(newValue.getDeadline())); } }); // 初始化筛选谓词 wantAllItems = todoItem -> true; wantTodaysItems = todoItem -> todoItem.getDeadline().equals(LocalDate.now()); wantCompletedItems = todoItem -> todoItem.isCompleted(); wantUncompletedItems = todoItem -> !todoItem.isCompleted(); filteredList = new FilteredList<>(ToDoData.getInstance().getTodoItems(), wantAllItems); // 列表按截止日期排序 SortedList<ToDoItem> sortedList = new SortedList<>(filteredList, Comparator.comparing(ToDoItem::getDeadline)); todoListView.setItems(sortedList); todoListView.getSelectionModel().setSelectionMode(SelectionMode.SINGLE); todoListView.getSelectionModel().selectFirst(); // 自定义列表单元格,区分完成/未完成状态 todoListView.setCellFactory(param -> { ListCell<ToDoItem> cell = new ListCell<ToDoItem>() { @Override protected void updateItem(ToDoItem item, boolean empty) { super.updateItem(item, empty); if(empty) { setText(null); setStyle(""); } else { String text = item.getShortDescription(); // 已完成任务显示删除线和灰色文本 if(item.isCompleted()) { setText(String.format("~~%s~~", text)); setTextFill(Color.GRAY); } else { setText(text); // 过期任务显示红色 if(item.getDeadline().isBefore(LocalDate.now().plusDays(1))) { setTextFill(Color.RED); } else { setTextFill(Color.GREEN); } } } } }; // 空单元格不显示上下文菜单 cell.emptyProperty().addListener((obs, wasEmpty, isNowEmpty) -> { if(isNowEmpty) { cell.setContextMenu(null); } else { cell.setContextMenu(listContextMenu); } }); return cell; }); } @FXML public void showNewItemDialog() { Dialog<ButtonType> dialog = new Dialog<>(); dialog.initOwner(mainBorderPane.getScene().getWindow()); dialog.setTitle("Add New Todo Item"); dialog.setHeaderText("Use this dialog to create a new todo item"); FXMLLoader fxmlLoader = new FXMLLoader(); fxmlLoader.setLocation(getClass().getResource("todoitemdialog.fxml")); try { dialog.getDialogPane().setContent(fxmlLoader.load()); } catch(IOException e) { System.out.println("Couldn't load the dialog"); e.printStackTrace(); return; } dialog.getDialogPane().getButtonTypes().add(ButtonType.OK); dialog.getDialogPane().getButtonTypes().add(ButtonType.CANCEL); Optional<ButtonType> result = dialog.showAndWait(); if(result.isPresent() && result.get() == ButtonType.OK) { DialogController controller = fxmlLoader.getController(); ToDoItem newItem = controller.processResults(); todoListView.getSelectionModel().select(newItem); } } // 显示已完成任务 @FXML public void showCompletedTasks(ActionEvent event){ ToDoItem selectedItem = todoListView.getSelectionModel().getSelectedItem(); filteredList.setPredicate(wantCompletedItems); updateSelection(selectedItem); } // 显示未完成任务 @FXML public void showUncompletedTasks(ActionEvent event){ ToDoItem selectedItem = todoListView.getSelectionModel().getSelectedItem(); filteredList.setPredicate(wantUncompletedItems); updateSelection(selectedItem); } @FXML public void handleKeyPressed(KeyEvent keyEvent) { ToDoItem selectedItem = todoListView.getSelectionModel().getSelectedItem(); if(selectedItem != null) { if(keyEvent.getCode().equals(KeyCode.DELETE)) { deleteItem(selectedItem); } else if(keyEvent.getCode().equals(KeyCode.ENTER)) { // 按Enter键切换完成状态 if(selectedItem.isCompleted()) { uncompletedItem(selectedItem); } else { completedItem(selectedItem); } } } } public void handleClickListView() { ToDoItem item = todoListView.getSelectionModel().getSelectedItem(); if(item != null) { itemDetailsTextArea.setText(item.getDetails()); deadlineLabel.setText(item.getDeadline().toString()); } } public void deleteItem(ToDoItem item) { Alert alert = new Alert(Alert.AlertType.CONFIRMATION); alert.setTitle("Delete Todo Item"); alert.setHeaderText("Delete item: " + item.getShortDescription()); alert.setContentText("Are you sure? Press OK to confirm or cancel to back out."); Optional<ButtonType> result = alert.showAndWait(); if(result.isPresent() && (result.get() == ButtonType.OK)) { ToDoData.getInstance().deleteTodoItem(item); } } public void completedItem(ToDoItem item) { Alert alert = new Alert(Alert.AlertType.CONFIRMATION); alert.setTitle("Mark as Completed"); alert.setHeaderText("Mark task as completed: " + item.getShortDescription()); alert.setContentText("Are you sure? Press OK to confirm or cancel to back out."); Optional<ButtonType> result = alert.showAndWait(); if(result.isPresent() && (result.get() == ButtonType.OK)) { ToDoData.getInstance().markAsCompleted(item); todoListView.refresh(); } } public void uncompletedItem(ToDoItem item) { Alert alert = new Alert(Alert.AlertType.CONFIRMATION); alert.setTitle("Mark as Uncompleted"); alert.setHeaderText("Mark task as uncompleted: " + item.getShortDescription()); alert.setContentText("Are you sure? Press OK to confirm or cancel to back out."); Optional<ButtonType> result = alert.showAndWait(); if(result.isPresent() && (result.get() == ButtonType.OK)) { ToDoData.getInstance().markAsUncompleted(item); todoListView.refresh(); } } @FXML public void wantTodaysItems(){ ToDoItem selectedItem = todoListView.getSelectionModel().getSelectedItem(); filteredList.setPredicate(wantTodaysItems); updateSelection(selectedItem); } @FXML public void wantAllItems(){ ToDoItem selectedItem = todoListView.getSelectionModel().getSelectedItem(); filteredList.setPredicate(wantAllItems); updateSelection(selectedItem); } // 筛选后更新选中状态的通用方法 private void updateSelection(ToDoItem selectedItem) { if(filteredList.isEmpty()) { itemDetailsTextArea.clear(); deadlineLabel.setText(""); } else if(filteredList.contains(selectedItem)) { todoListView.getSelectionModel().select(selectedItem); } else { todoListView.getSelectionModel().selectFirst(); } } @FXML public void handleExit() { Platform.exit(); } }
核心修改说明
- 任务状态整合:不再单独维护已完成列表,直接在
ToDoItem中添加完成状态标记,避免数据分散导致的同步问题 - 数据持久化支持:修改文件读写逻辑,保存和加载任务的完成状态
- UI视觉区分:已完成任务显示删除线和灰色文本,过期任务显示红色,直观区分任务状态
- 交互完善:新增“标记为未完成”选项,支持按Enter键快速切换任务状态
- 筛选逻辑优化:新增已完成/未完成任务的筛选谓词,统一处理筛选后的列表选中逻辑
内容的提问来源于stack exchange,提问作者zahra
相关产品推荐
相关产品推荐

