如何让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.
相关产品推荐
相关产品推荐

