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

如何让Swing的UndoManager执行撤销重做时应用DocumentFilter规则

问题根因

Swing 中 DocumentFilter 只会拦截外部主动调用 Document 提供的 insertString、replace、remove 公开方法。而 UndoManager 管理的撤销/重做操作是直接通过底层的 FilterBypass 接口修改文档数据,完全绕开了 DocumentFilter 的校验逻辑,所以才会出现过滤规则生效时,撤销操作仍能恢复不符合规则的内容。

你之前重写 PlainDocument 公开方法没有触发回调,也是因为同样的原因:撤销重做的修改没有走这些公开的API入口。


可行解决方案

不需要清空所有撤销记录,只要修改撤销/重做的执行逻辑,在每次撤销/重做完成后,主动用当前生效的 DocumentFilter 对文档内容做一次清洗即可,修改代码如下:

只需要调整 installUndoRedo 方法中 Undo 和 Redo 对应的 Action 实现:

public static void installUndoRedo(JTextComponent textComponent) {
    UndoManager undoManager = new UndoManager();
    textComponent.getDocument().addUndoableEditListener(undoManager);
    InputMap im = textComponent.getInputMap(JComponent.WHEN_FOCUSED);
    ActionMap am = textComponent.getActionMap();

    im.put(KeyStroke.getKeyStroke(KeyEvent.VK_Z, Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()), "Undo");
    im.put(KeyStroke.getKeyStroke(KeyEvent.VK_Y, Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()), "Redo");

    // 提取公共的内容清洗逻辑
    Runnable filterContent = () -> {
        AbstractDocument doc = (AbstractDocument) textComponent.getDocument();
        DocumentFilter filter = doc.getDocumentFilter();
        if (filter == null || doc.getLength() == 0) return;
        try {
            String rawContent = doc.getText(0, doc.getLength());
            // 构造FilterBypass,调用当前过滤器处理全量内容
            filter.replace(new DocumentFilter.FilterBypass() {
                @Override
                public Document getDocument() { return doc; }
                @Override
                public void remove(int offset, int length) throws BadLocationException { doc.remove(offset, length); }
                @Override
                public void insertString(int offset, String string, AttributeSet attr) throws BadLocationException { doc.insertString(offset, string, attr); }
                @Override
                public void replace(int offset, int length, String text, AttributeSet attrs) throws BadLocationException { doc.replace(offset, length, text, attrs); }
            }, 0, doc.getLength(), rawContent, null);
        } catch (BadLocationException e) {
            e.printStackTrace();
        }
    };

    am.put("Undo", new AbstractAction() {
        private static final long serialVersionUID = 1L;
        @Override
        public void actionPerformed(ActionEvent e) {
            if (undoManager.canUndo()) {
                undoManager.undo();
                filterContent.run();
            }
        }
    });
    am.put("Redo", new AbstractAction() {
        private static final long serialVersionUID = 1L;
        @Override
        public void actionPerformed(ActionEvent e) {
            if (undoManager.canRedo()) {
                undoManager.redo();
                filterContent.run();
            }
        }
    });
}

这个方案可以保留所有的撤销重做记录,同时保证任何操作后的内容都符合当前生效的过滤规则,不需要修改原有 DocumentFilter 或者 Document 的实现。


内容的提问来源于stack exchange,提问作者George Z.

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.17 13:06:03