在JTextArea中使用撤销和重做功能

13

我正在使用Java Swing制作一个文本编辑器,使用的组件是JTextArea。我想知道如何在JTextArea中实现撤销 (Undo) 和重做 (Redo) 功能,因为我目前无法使用它们。

5个回答

13
据我所知,JTextArea没有内置的撤销/重做功能,但通过Google搜索可以找到这篇文章,可能会有所帮助。
显然,在javax.swing中存在一个Undo Manager,你可以将其连接到JTextArea的更改事件上。

1
这里有一个更完整的示例链接:http://www.java-forums.org/javax-swing/9570-undo-redo-jtextarea.html - Petar Minchev
@Petar,你链接后面的代码没有处理当所有文本被选中并替换为剪贴板内容时会发生什么。结果是文本区域被清空而不是被替换为其以前的内容。复现步骤:(1)将某些内容放入剪贴板,(2)选择文本区域中的所有字符,(3)按^V粘贴剪贴板覆盖所选内容。我还不知道发生了什么,但我打算找出来。同样适用于此答案中发布的链接后面的代码。我有一种感觉,“以前的内容”的定义可能是问题所在。 - Jeff Holt
问题在于当您选择所有文本并按下^V(粘贴)时,会记录两个事件而不是一个。第一个事件是删除所有事件,第二个事件是插入事件。 - Jeff Holt
1
@PetarMinchev 答案在这里,我们可以看到如何在PlainDocument中覆盖replace方法。 - Jeff Holt

6
您可以这样做:
UndoManager manager = new UndoManager();
textArea.getDocument().addUndoableEditListener(manager);

一旦将管理器附加到JTextArea文档上,它将监视文本区域内容的所有更改。

在将管理器附加到文本组件之后,您必须提供某些手段告诉管理器撤消/重做操作。

在需要的地方(例如actionlistener的actionPerformed()方法)调用UndoManager的public void undo()和public void redo()方法。

您可以通过以下方式将Action对象附加到按钮,而不是调用undo()和redo()方法,从而简化任务:

JButton undoButton = new JButton(UndoManagerHelper.getUndoAction(manager));
JButton redoButton = new JButton(UndoManagerHelper.getRedoAction(manager));

UndoManagerHelper类是用户自定义的吗?我在Eclipse中没有得到它的自动完成。我也搜索了Oracle Java文档,但无法找到该类。我正在使用Java 8。 - Omkar76

5

4
谁给这个点赞踩了一下?这个教程有一个可行的例子并解释了正在发生的事情,这很明确地回答了问题。现在,由于该教程已经提供了答案,因此取决于问题发布者是否阅读了该教程。事实上,问题发布者应该在发布问题之前先阅读教程。如果用户以前不知道Swing教程,那么现在他们可以使用这个宝贵的参考资料来帮助以后的问题。这是回答所有问题的方式。 - camickr
感谢您的讽刺言辞,camickr。感谢您帮助Marshall。 - Logan

4

我创建了一个简单的类,可以通过一个方法调用为JTextcomponent(JTextField、JTextArea等)分配撤消功能:

UndoTool.addUndoFunctionality(area);

或者,您可以使用预先分配了撤销功能的新JTextArea:

UndoTool.createJTextFieldWithUndo();

这是实用类的实现代码:
public class UndoTool {
    private static final String REDO_KEY = "redo";
    private static final String UNDO_KEY = "undo";

    private JTextComponent component;
    private KeyStroke undo = KeyStroke.getKeyStroke("control Z");
    private KeyStroke redo = KeyStroke.getKeyStroke("control Y");

    public UndoTool(JTextComponent component) {
        this.component = component;
    }

    public void setUndo(KeyStroke undo) {
        this.undo = undo;
    }

    public void setRedo(KeyStroke redo) {
        this.redo = redo;
    }

    public static void addUndoFunctionality(JTextComponent component) {
        UndoTool tool = new UndoTool(component);
        UndoManager undo = tool.createAndBindUndoManager();
        tool.bindUndo(undo);
        tool.bindRedo(undo);
    }

    public static JTextArea createJTextAreaWithUndo() {
        JTextArea area = new JTextArea();
        addUndoFunctionality(area);
        return area;
    }

    public static JTextField createJTextFieldWithUndo() {
        JTextField field = new JTextField();
        addUndoFunctionality(field);
        return field;
    }

    public UndoManager createAndBindUndoManager() {
        Check.notNull(component);

        UndoManager manager = new UndoManager();
        Document document = component.getDocument();
        document.addUndoableEditListener(event -> manager.addEdit(event.getEdit()));
        return manager;
    }

    public void bindRedo(UndoManager manager) {
        component.getActionMap().put(REDO_KEY, new AbstractAction(REDO_KEY) {
            @Override
            public void actionPerformed(ActionEvent evt) {
                try {
                    if (manager.canRedo()) {
                        manager.redo();
                    }
                } catch (CannotRedoException ignore) {
                }
            }
        });
        component.getInputMap().put(redo, REDO_KEY);
    }

    public void bindUndo(UndoManager manager) {
        component.getActionMap().put(UNDO_KEY, new AbstractAction(UNDO_KEY) {
            @Override
            public void actionPerformed(ActionEvent evt) {
                try {
                    if (manager.canUndo()) {
                        manager.undo();
                    }
                } catch (CannotUndoException ignore) {
                }
            }
        });
        component.getInputMap().put(undo, UNDO_KEY);
    }
}

3

我不得不浏览多个链接才能获得足够的帮助。我在这里添加了我成功实现的内容,以帮助未来的访问者。我使用JTextPane实现了这个功能,但是我认为对于JTextArea也适用。

    JTextArea textArea = new JTextArea();
    JButton undo = new JButton("Undo");
    JButton redo = new JButton("Redo");
    KeyStroke undoKeyStroke = KeyStroke.getKeyStroke(
            KeyEvent.VK_Z, Event.CTRL_MASK);
    KeyStroke redoKeyStroke = KeyStroke.getKeyStroke(
            KeyEvent.VK_Y, Event.CTRL_MASK);

    UndoManager undoManager = new UndoManager();

    Document document = textArea.getDocument();
    document.addUndoableEditListener(new UndoableEditListener() {
        @Override
        public void undoableEditHappened(UndoableEditEvent e) {
            undoManager.addEdit(e.getEdit());
        }
    });

    // Add ActionListeners
    undo.addActionListener((ActionEvent e) -> {
        try {
            undoManager.undo();
        } catch (CannotUndoException cue) {}
    });
    redo.addActionListener((ActionEvent e) -> {
        try {
            undoManager.redo();
        } catch (CannotRedoException cre) {}
    });

    // Map undo action
    textArea.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW)
            .put(undoKeyStroke, "undoKeyStroke");
    textArea.getActionMap().put("undoKeyStroke", new AbstractAction() {
        @Override
        public void actionPerformed(ActionEvent e) {
            try {
                undoManager.undo();
             } catch (CannotUndoException cue) {}
        }
    });
    // Map redo action
    textArea.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW)
            .put(redoKeyStroke, "redoKeyStroke");
    textArea.getActionMap().put("redoKeyStroke", new AbstractAction() {
        @Override
        public void actionPerformed(ActionEvent e) {
            try {
                undoManager.redo();
             } catch (CannotRedoException cre) {}
        }
    });

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接