You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在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();
    }
}

核心修改说明

  1. 任务状态整合:不再单独维护已完成列表,直接在ToDoItem中添加完成状态标记,避免数据分散导致的同步问题
  2. 数据持久化支持:修改文件读写逻辑,保存和加载任务的完成状态
  3. UI视觉区分:已完成任务显示删除线和灰色文本,过期任务显示红色,直观区分任务状态
  4. 交互完善:新增“标记为未完成”选项,支持按Enter键快速切换任务状态
  5. 筛选逻辑优化:新增已完成/未完成任务的筛选谓词,统一处理筛选后的列表选中逻辑

内容的提问来源于stack exchange,提问作者zahra

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.24 23:15:33