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

能否将Java Swing Actions存入ArrayList?如何用其实现撤销重做功能?

当然可以!Swing的Action本质是个接口,所有实现它的类(比如常用的AbstractAction)都是普通Java对象,完全可以存入ArrayList或者其他集合类中。不过要实现撤销/重做功能,直接存Action本身还不够——你需要的是记录操作的历史状态,而不是单纯保存Action实例。下面我来一步步拆解实现思路:

核心思路:用历史记录对象管理可逆操作

你不能只存Action,因为执行过的操作需要能被“回滚”或“重演”。正确的做法是定义一个操作历史条目类,用来存储操作执行前后的关键状态,再把这些条目存入ArrayList中配合索引实现撤销/重做。

1. 定义操作历史条目类

这个类要记录足够的信息来恢复操作前的状态,以及操作的描述(方便下拉菜单展示)。比如针对文本修改场景:

class ActionHistoryEntry {
    private final String beforeState;  // 操作前的状态(比如文本框旧内容)
    private final String afterState;   // 操作后的状态(用于重做)
    private final String description;  // 操作描述(比如"修改文本为:Hello World")

    public ActionHistoryEntry(String beforeState, String afterState, String description) {
        this.beforeState = beforeState;
        this.afterState = afterState;
        this.description = description;
    }

    // Getter方法
    public String getBeforeState() { return beforeState; }
    public String getAfterState() { return afterState; }
    public String getDescription() { return description; }
}

2. 管理历史记录与当前位置

用一个ArrayList<ActionHistoryEntry>存储所有操作历史,再加一个currentIndex变量标记当前所处的操作位置:

  • 执行新操作时:如果currentIndex不是列表末尾(说明之前撤销过操作后又执行了新操作),要先删除currentIndex之后的记录(避免历史分支混乱),再添加新条目,最后currentIndex++。
  • 撤销操作时:如果currentIndex > 0,取出currentIndex-1的条目恢复到操作前状态,然后currentIndex--。
  • 重做操作时:如果currentIndex < 历史列表大小,取出currentIndex的条目恢复到操作后状态,然后currentIndex++。

3. 关联按钮与下拉菜单

  • 撤销/重做按钮的启用状态要动态更新:比如currentIndex == 0时撤销按钮禁用,currentIndex == 历史列表大小时重做按钮禁用。
  • 历史下拉菜单可以遍历历史条目生成选项,点击某一项时通过循环执行撤销/重做,跳转到对应的操作位置。
完整代码示例(文本修改场景)
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.util.ArrayList;
import java.util.List;

public class UndoRedoDemo extends JFrame {
    private final JTextField textField = new JTextField(20);
    private final List<ActionHistoryEntry> history = new ArrayList<>();
    private int currentIndex = 0;

    // 修改文本的核心操作Action
    private final Action modifyTextAction = new AbstractAction("Append Text") {
        @Override
        public void actionPerformed(ActionEvent e) {
            String beforeText = textField.getText();
            String afterText = beforeText + " [Edited]";
            textField.setText(afterText);

            // 处理历史记录:清除当前位置之后的旧分支
            if (currentIndex < history.size()) {
                history.subList(currentIndex, history.size()).clear();
            }
            // 添加新的操作记录
            history.add(new ActionHistoryEntry(beforeText, afterText, "修改文本为:" + afterText));
            currentIndex++;
            updateButtonStates();
            updateHistoryMenu();
        }
    };

    // 撤销Action
    private final Action undoAction = new AbstractAction("Undo") {
        @Override
        public void actionPerformed(ActionEvent e) {
            if (currentIndex > 0) {
                currentIndex--;
                ActionHistoryEntry entry = history.get(currentIndex);
                textField.setText(entry.getBeforeState());
                updateButtonStates();
                updateHistoryMenu();
            }
        }
    };

    // 重做Action
    private final Action redoAction = new AbstractAction("Redo") {
        @Override
        public void actionPerformed(ActionEvent e) {
            if (currentIndex < history.size()) {
                ActionHistoryEntry entry = history.get(currentIndex);
                textField.setText(entry.getAfterState());
                currentIndex++;
                updateButtonStates();
                updateHistoryMenu();
            }
        }
    };

    // 更新按钮启用状态
    private void updateButtonStates() {
        undoAction.setEnabled(currentIndex > 0);
        redoAction.setEnabled(currentIndex < history.size());
    }

    // 更新历史下拉菜单
    private void updateHistoryMenu() {
        JMenu historyMenu = new JMenu("操作历史");
        historyMenu.removeAll();
        for (int i = 0; i < history.size(); i++) {
            int targetIndex = i;
            JMenuItem item = new JMenuItem(history.get(i).getDescription());
            item.addActionListener(e -> {
                // 跳转到指定操作位置:通过循环撤销/重做实现
                while (currentIndex > targetIndex + 1) {
                    undoAction.actionPerformed(null);
                }
                while (currentIndex < targetIndex + 1) {
                    redoAction.actionPerformed(null);
                }
            });
            historyMenu.add(item);
        }
        // 替换菜单栏中的历史菜单
        JMenuBar menuBar = getJMenuBar();
        menuBar.remove(menuBar.getMenuCount() - 1);
        menuBar.add(historyMenu);
    }

    public UndoRedoDemo() {
        setTitle("撤销/重做示例");
        setSize(400, 200);
        setDefaultCloseOperation(EXIT_ON_CLOSE);
        setLayout(new FlowLayout());

        // 添加组件
        add(textField);
        add(new JButton(modifyTextAction));
        add(new JButton(undoAction));
        add(new JButton(redoAction));

        // 初始化菜单栏
        JMenuBar menuBar = new JMenuBar();
        menuBar.add(new JMenu("文件"));
        setJMenuBar(menuBar);
        updateHistoryMenu();

        // 初始按钮状态
        updateButtonStates();
    }

    public static void main(String[] args) {
        SwingUtilities.invokeLater(() -> new UndoRedoDemo().setVisible(true));
    }
}

// 操作历史条目类
class ActionHistoryEntry {
    private final String beforeState;
    private final String afterState;
    private final String description;

    public ActionHistoryEntry(String beforeState, String afterState, String description) {
        this.beforeState = beforeState;
        this.afterState = afterState;
        this.description = description;
    }

    public String getBeforeState() { return beforeState; }
    public String getAfterState() { return afterState; }
    public String getDescription() { return description; }
}
关键注意事项
  • 状态完整性:如果操作涉及多个组件的状态变化,历史条目要记录所有相关状态,确保恢复后界面一致。
  • 内存优化:如果操作数量很大,可以设置历史记录的最大条数,超过后自动删除最早的条目。
  • 复杂操作处理:对于不可逆操作(比如文件删除),需要额外的逻辑(比如临时备份)来支持撤销。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.20 11:25:39