我可以让 UndoManager 考虑 DocumentFilter 吗?
Can I make UndoManager consider DocumentFilter?
运行 这个例子:
public class UndoRedoSSCCE extends JFrame {
public UndoRedoSSCCE() {
super("A");
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setLayout(new FlowLayout());
JComboBox<String> comboBox = new JComboBox<>(new String[] { "letters & digits", "only digits" });
JTextField textField = new JTextField(15);
PlainDocument doc = new PlainDocument();
textField.setDocument(doc);
installUndoRedo(textField);
comboBox.addActionListener(e -> {
textField.setText("");
if (comboBox.getSelectedIndex() == 0)
doc.setDocumentFilter(new LettersAndDigitsFilter());
else
doc.setDocumentFilter(new OnlyDigitsFilter());
});
doc.setDocumentFilter(new LettersAndDigitsFilter());
add(comboBox);
add(textField);
pack();
setLocationByPlatform(true);
}
private static class LettersAndDigitsFilter extends DocumentFilter {
@Override
public void insertString(FilterBypass fb, int offset, String string, AttributeSet attr)
throws BadLocationException {
StringBuilder sb = new StringBuilder();
string.chars().filter(c -> Character.isDigit(c) || Character.isLetter(c))
.forEach(value -> sb.append((char) value));
super.insertString(fb, offset, sb.toString(), attr);
}
@Override
public void replace(FilterBypass fb, int offset, int length, String text, AttributeSet attrs)
throws BadLocationException {
StringBuilder sb = new StringBuilder();
text.chars().filter(c -> Character.isDigit(c) || Character.isLetter(c))
.forEach(value -> sb.append((char) value));
super.replace(fb, offset, length, sb.toString(), attrs);
}
}
private static class OnlyDigitsFilter extends DocumentFilter {
@Override
public void insertString(FilterBypass fb, int offset, String string, AttributeSet attr)
throws BadLocationException {
StringBuilder sb = new StringBuilder();
string.chars().filter(Character::isDigit).forEach(value -> sb.append((char) value));
super.insertString(fb, offset, sb.toString(), attr);
}
@Override
public void replace(FilterBypass fb, int offset, int length, String text, AttributeSet attrs)
throws BadLocationException {
StringBuilder sb = new StringBuilder();
text.chars().filter(Character::isDigit).forEach(value -> sb.append((char) value));
super.replace(fb, offset, length, sb.toString(), attrs);
}
}
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");
am.put("Undo", new AbstractAction() {
private static final long serialVersionUID = 1L;
@Override
public void actionPerformed(ActionEvent e) {
if (undoManager.canUndo()) {
undoManager.undo();
}
}
});
am.put("Redo", new AbstractAction() {
private static final long serialVersionUID = 1L;
@Override
public void actionPerformed(ActionEvent e) {
if (undoManager.canRedo()) {
undoManager.redo();
}
}
});
}
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> {
new UndoRedoSSCCE().setVisible(true);
});
}
}
如果我在组合框“仅字母”中输入包含字母和数字的文本和 select,当我按下控制键 Z 时,这些字母和数字将出现在文本字段中。换句话说,UndoManager 忽略文档过滤器。
查看此 gif:
当我更改组合框 selection 时,文本变为空白。然后我按 CTRL+ Z 并且在 OnlyDigits 过滤器打开时出现包含字母的文本。
我知道我可以 undoManager.discardAllEdits
,但是有可能让它发挥作用吗?我的意思是在尝试 redo/undo 时应用过滤器?
我也尝试 @Override
来自 PlainDocument
的一些方法,但它们都没有被调用以覆盖有意义的东西。
Undo/Redo 应该恢复组件的状态而不是改变状态。
我建议您在更改过滤器时应该:
- 保存当前文本,
- 清除文本字段中的文本
- 在
UndoManager
上调用 discardAllEdits()
。
- 一次一个字符地遍历旧文本并将该字符插入回文档中。这将允许在重建 undo/redo 时过滤文本,就像使用当前过滤器输入文本一样。
运行 这个例子:
public class UndoRedoSSCCE extends JFrame {
public UndoRedoSSCCE() {
super("A");
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setLayout(new FlowLayout());
JComboBox<String> comboBox = new JComboBox<>(new String[] { "letters & digits", "only digits" });
JTextField textField = new JTextField(15);
PlainDocument doc = new PlainDocument();
textField.setDocument(doc);
installUndoRedo(textField);
comboBox.addActionListener(e -> {
textField.setText("");
if (comboBox.getSelectedIndex() == 0)
doc.setDocumentFilter(new LettersAndDigitsFilter());
else
doc.setDocumentFilter(new OnlyDigitsFilter());
});
doc.setDocumentFilter(new LettersAndDigitsFilter());
add(comboBox);
add(textField);
pack();
setLocationByPlatform(true);
}
private static class LettersAndDigitsFilter extends DocumentFilter {
@Override
public void insertString(FilterBypass fb, int offset, String string, AttributeSet attr)
throws BadLocationException {
StringBuilder sb = new StringBuilder();
string.chars().filter(c -> Character.isDigit(c) || Character.isLetter(c))
.forEach(value -> sb.append((char) value));
super.insertString(fb, offset, sb.toString(), attr);
}
@Override
public void replace(FilterBypass fb, int offset, int length, String text, AttributeSet attrs)
throws BadLocationException {
StringBuilder sb = new StringBuilder();
text.chars().filter(c -> Character.isDigit(c) || Character.isLetter(c))
.forEach(value -> sb.append((char) value));
super.replace(fb, offset, length, sb.toString(), attrs);
}
}
private static class OnlyDigitsFilter extends DocumentFilter {
@Override
public void insertString(FilterBypass fb, int offset, String string, AttributeSet attr)
throws BadLocationException {
StringBuilder sb = new StringBuilder();
string.chars().filter(Character::isDigit).forEach(value -> sb.append((char) value));
super.insertString(fb, offset, sb.toString(), attr);
}
@Override
public void replace(FilterBypass fb, int offset, int length, String text, AttributeSet attrs)
throws BadLocationException {
StringBuilder sb = new StringBuilder();
text.chars().filter(Character::isDigit).forEach(value -> sb.append((char) value));
super.replace(fb, offset, length, sb.toString(), attrs);
}
}
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");
am.put("Undo", new AbstractAction() {
private static final long serialVersionUID = 1L;
@Override
public void actionPerformed(ActionEvent e) {
if (undoManager.canUndo()) {
undoManager.undo();
}
}
});
am.put("Redo", new AbstractAction() {
private static final long serialVersionUID = 1L;
@Override
public void actionPerformed(ActionEvent e) {
if (undoManager.canRedo()) {
undoManager.redo();
}
}
});
}
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> {
new UndoRedoSSCCE().setVisible(true);
});
}
}
如果我在组合框“仅字母”中输入包含字母和数字的文本和 select,当我按下控制键 Z 时,这些字母和数字将出现在文本字段中。换句话说,UndoManager 忽略文档过滤器。
查看此 gif:
当我更改组合框 selection 时,文本变为空白。然后我按 CTRL+ Z 并且在 OnlyDigits 过滤器打开时出现包含字母的文本。
我知道我可以 undoManager.discardAllEdits
,但是有可能让它发挥作用吗?我的意思是在尝试 redo/undo 时应用过滤器?
我也尝试 @Override
来自 PlainDocument
的一些方法,但它们都没有被调用以覆盖有意义的东西。
Undo/Redo 应该恢复组件的状态而不是改变状态。
我建议您在更改过滤器时应该:
- 保存当前文本,
- 清除文本字段中的文本
- 在
UndoManager
上调用discardAllEdits()
。 - 一次一个字符地遍历旧文本并将该字符插入回文档中。这将允许在重建 undo/redo 时过滤文本,就像使用当前过滤器输入文本一样。