Java记事本中,可通过实现撤销功能,如使用栈(Stack)数据结构保存
Java中实现一个具有撤销功能的记事本应用程序,可以通过使用数据结构(如栈)来存储每次操作的状态,并在需要时恢复到之前的状态,以下是详细的步骤和示例代码,帮助你理解如何设置撤销功能。
设计思路
要实现撤销功能,通常需要以下几个关键组件:
- 存储:用于显示和编辑文本的组件,例如
JTextArea。 - 操作历史记录:使用栈(
Stack)来存储每次操作前的文本状态,以便在撤销时恢复。 - 撤销操作:当用户执行撤销操作时,从栈中弹出最近的文本状态并恢复。
实现步骤
a. 创建主界面
创建一个基本的记事本界面,包括文本区域和菜单栏。
import javax.swing.;
import java.awt.;
import java.awt.event.;
import java.util.Stack;
public class UndoableNotepad extends JFrame {
private JTextArea textArea;
private Stack<String> undoStack = new Stack<>();
private Stack<String> redoStack = new Stack<>(); // 可选:实现重做功能
public UndoableNotepad() {
setTitle("Java记事本");
setSize(600, 400);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
textArea = new JTextArea();
JScrollPane scrollPane = new JScrollPane(textArea);
add(scrollPane, BorderLayout.CENTER);
// 创建菜单栏
JMenuBar menuBar = new JMenuBar();
JMenu editMenu = new JMenu("编辑");
JMenuItem undoItem = new JMenuItem("撤销");
JMenuItem redoItem = new JMenuItem("重做"); // 可选
editMenu.add(undoItem);
editMenu.add(redoItem); // 可选
menuBar.add(editMenu);
setJMenuBar(menuBar);
// 添加文本监听器,保存每次修改前的状态
textArea.getDocument().addUndoableEditListener(e -> {
undoStack.push(textArea.getText());
redoStack.clear(); // 可选:清除重做栈
});
// 添加撤销动作
undoItem.addActionListener(e -> undo());
// 添加重做动作(可选)
redoItem.addActionListener(e -> redo());
}
private void undo() {
if (!undoStack.isEmpty()) {
redoStack.push(textArea.getText()); // 将当前状态压入重做栈
String previousText = undoStack.pop();
textArea.setText(previousText);
}
}
private void redo() {
if (!redoStack.isEmpty()) {
String redoText = redoStack.pop();
undoStack.push(textArea.getText());
textArea.setText(redoText);
}
}
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> {
UndoableNotepad notepad = new UndoableNotepad();
notepad.setVisible(true);
});
}
}
b. 解释代码
-
界面组件:

JTextArea:用于显示和编辑文本。JScrollPane:为文本区域添加滚动条。JMenuBar、JMenu和JMenuItem:创建菜单栏和菜单项,包括“撤销”和“重做”选项。
-
操作历史记录:
undoStack:用于存储每次文本修改前的状态,实现撤销功能。redoStack:可选,用于存储被撤销的状态,实现重做功能。
-
文本监听器:
- 使用
addUndoableEditListener监听文本的每一次可撤销编辑,每当文本发生变化时,将当前文本内容压入undoStack,并清空redoStack。
- 使用
-
撤销与重做方法:

undo():检查undoStack是否为空,如果不为空,则弹出最近的文本状态,将其设置为当前文本,并将之前的文本状态压入redoStack。redo():检查redoStack是否为空,如果不为空,则弹出最近的文本状态,将其设置为当前文本,并将之前的文本状态压入undoStack。
c. 优化与扩展
上述实现是一个简单的撤销功能,适用于基本的文本编辑,如果需要更复杂的功能,可以考虑以下优化:
- 限制撤销栈的大小:防止内存占用过大,可以设置一个最大栈大小,超过后移除最早的记录。
- 支持多级撤销:当前的实现每次只撤销一次操作,可以扩展为支持多级撤销。
- 整合剪切、复制、粘贴等操作:将这些操作也纳入撤销历史中,确保全面的撤销能力。
- 使用模型-视图-控制器(MVC)架构:提高代码的可维护性和扩展性。
完整示例代码
以下是一个完整的Java记事本应用程序,包含撤销和重做功能:
import javax.swing.;
import javax.swing.event.UndoableEditEvent;
import javax.swing.event.UndoableEditListener;
import java.awt.;
import java.awt.event.;
import java.util.Stack;
public class UndoableNotepad extends JFrame {
private JTextArea textArea;
private Stack<String> undoStack = new Stack<>();
private Stack<String> redoStack = new Stack<>();
public UndoableNotepad() {
setTitle("Java记事本");
setSize(600, 400);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
textArea = new JTextArea();
JScrollPane scrollPane = new JScrollPane(textArea);
add(scrollPane, BorderLayout.CENTER);
// 创建菜单栏
JMenuBar menuBar = new JMenuBar();
JMenu fileMenu = new JMenu("文件");
JMenu editMenu = new JMenu("编辑");
JMenuItem undoItem = new JMenuItem("撤销");
JMenuItem redoItem = new JMenuItem("重做");
JMenuItem saveItem = new JMenuItem("保存");
JMenuItem openItem = new JMenuItem("打开");
editMenu.add(undoItem);
editMenu.add(redoItem);
fileMenu.add(saveItem);
fileMenu.add(openItem);
menuBar.add(fileMenu);
menuBar.add(editMenu);
setJMenuBar(menuBar);
// 添加文本监听器,保存每次修改前的状态
textArea.getDocument().addUndoableEditListener(new UndoableEditListener() {
@Override
public void undoableEditHappened(UndoableEditEvent e) {
undoStack.push(textArea.getText());
redoStack.clear();
}
});
// 添加撤销动作
undoItem.addActionListener(e -> undo());
// 添加重做动作
redoItem.addActionListener(e -> redo());
// 添加保存和打开功能(可选)
saveItem.addActionListener(e -> saveFile());
openItem.addActionListener(e -> openFile());
}
private void undo() {
if (!undoStack.isEmpty()) {
redoStack.push(textArea.getText());
String previousText = undoStack.pop();
textArea.setText(previousText);
} else {
JOptionPane.showMessageDialog(this, "无法撤销");
}
}
private void redo() {
if (!redoStack.isEmpty()) {
String redoText = redoStack.pop();
undoStack.push(textArea.getText());
textArea.setText(redoText);
} else {
JOptionPane.showMessageDialog(this, "无法重做");
}
}
private void saveFile() {
JFileChooser fileChooser = new JFileChooser();
int option = fileChooser.showSaveDialog(this);
if (option == JFileChooser.APPROVE_OPTION) {
// 这里可以添加文件保存逻辑
JOptionPane.showMessageDialog(this, "保存成功");
}
}
private void openFile() {
JFileChooser fileChooser = new JFileChooser();
int option = fileChooser.showOpenDialog(this);
if (option == JFileChooser.APPROVE_OPTION) {
// 这里可以添加文件打开逻辑
JOptionPane.showMessageDialog(this, "打开文件");
}
}
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> {
UndoableNotepad notepad = new UndoableNotepad();
notepad.setVisible(true);
});
}
}
相关问答FAQs
问题1:如何在Java记事本中实现多级撤销?
解答:要实现多级撤销,可以使用多个栈来分别存储每一级的文本状态,每当用户进行一次操作(如输入字符、删除字符等),就将当前的文本状态压入undoStack,撤销时,从undoStack中弹出最近的文本状态并恢复,为了支持多级撤销,可以在每次撤销后,将恢复的状态压入redoStack,以便用户可以进行重做操作,可以限制undoStack的大小,以防止内存溢出。

问题2:为什么在撤销操作后无法立即进行重做?
解答:如果在撤销操作后立即进行重做,可能是因为redoStack没有正确保存被撤销的状态,在撤销操作时,应将被撤销的文本状态压入redoStack,以便在重做时能够恢复,确保在文本发生变化时正确更新undoStack和redoStack。
