在 JTextField 和 setText 中撤消
Posted
技术标签:
【中文标题】在 JTextField 和 setText 中撤消【英文标题】:Undo in JTextField and setText 【发布时间】:2012-10-02 09:51:00 【问题描述】:JTextField 具有开箱即用的撤消支持。它适用于用户交互,但不幸的是,如果调用 setText(String str) 方法,则会导致 两个 UndoableEdits 而不是一个。所以这段代码看起来和感觉都很好,但不起作用:
UndoManager undoManager = new UndoManager();
JTextField tf = new JTextField();
tf.setText("initial value");
tf.getDocument().addUndoableEditListener(undoManager);
tf.setText("new value");
undoManager.undo();
System.out.println(tf.getText()); // Prints empty string
undoManager.undo();
System.out.println(tf.getText()); // Prints "initial value" as expected
JTextField 能否以某种方式将 setText() 仅作为一个 UndoableEdit 处理?
【问题讨论】:
在以编程方式设置文本之后似乎是调用discardAllEdits()
的好时机。
@Andrew Thompson 不,实际上我有一个带有一些文本操作的弹出菜单,例如大写,反转字符串等。我希望这些操作可以在一个操作中撤消,否则用户大写是不合理的一键获取字符串,但需要两次撤销才能取消大写。
【参考方案1】:
另一个选项是覆盖 Document#replace(...)
import java.awt.*;
import java.awt.event.*;
import java.util.Date;
import javax.swing.*;
import javax.swing.text.*;
import javax.swing.undo.*;
import javax.swing.event.*;
public class ReplaceUndoableEditDemo
private final UndoManager um = new UndoManager();
private final JTextField tf = new JTextField(24);
private final UndoManager undoManager = new UndoManager();
private final JTextField field = new JTextField(24);
private final Document doc = new PlainDocument()
@Override public void replace(
int offset, int length, String text, AttributeSet attrs)
throws BadLocationException
undoManager.undoableEditHappened(new UndoableEditEvent(
this, new ReplaceUndoableEdit(offset, length, text)));
replaceIgnoringUndo(offset, length, text, attrs);
private void replaceIgnoringUndo(
int offset, int length, String text, AttributeSet attrs)
throws BadLocationException
for(UndoableEditListener uel: getUndoableEditListeners())
removeUndoableEditListener(uel);
super.replace(offset, length, text, attrs);
for(UndoableEditListener uel: getUndoableEditListeners())
addUndoableEditListener(uel);
class ReplaceUndoableEdit extends AbstractUndoableEdit
private final String oldValue;
private final String newValue;
private int offset;
public ReplaceUndoableEdit(int offset, int length, String newValue)
String txt;
try
txt = getText(offset, length);
catch(BadLocationException e)
txt = null;
this.oldValue = txt;
this.newValue = newValue;
this.offset = offset;
@Override public void undo() throws CannotUndoException
try
replaceIgnoringUndo(offset, newValue.length(), oldValue, null);
catch(BadLocationException ex)
throw new CannotUndoException();
@Override public void redo() throws CannotRedoException
try
replaceIgnoringUndo(offset, oldValue.length(), newValue, null);
catch(BadLocationException ex)
throw new CannotUndoException();
@Override public boolean canUndo()
return true;
@Override public boolean canRedo()
return true;
;
public JComponent makeUI()
tf.getDocument().addUndoableEditListener(um);
doc.addUndoableEditListener(undoManager);
field.setDocument(doc);
field.setText("aaaaaaaaa");
tf.setText("default");
JPanel p = new JPanel();
p.add(tf);
p.add(field);
p.add(new JButton(new AbstractAction("undo")
@Override public void actionPerformed(ActionEvent e)
try
undoManager.undo();
um.undo();
catch(Exception ex)
java.awt.Toolkit.getDefaultToolkit().beep();
));
p.add(new JButton(new AbstractAction("redo")
@Override public void actionPerformed(ActionEvent e)
try
undoManager.redo();
um.redo();
catch(Exception ex)
java.awt.Toolkit.getDefaultToolkit().beep();
));
p.add(new JButton(new AbstractAction("setText")
@Override public void actionPerformed(ActionEvent e)
String str = new Date().toString();
tf.setText(str);
field.setText(str);
));
return p;
public static void main(String[] args)
EventQueue.invokeLater(new Runnable()
@Override public void run()
createAndShowGUI();
);
public static void createAndShowGUI()
JFrame f = new JFrame();
f.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
f.getContentPane().add(new ReplaceUndoableEditDemo().makeUI());
f.setSize(320, 240);
f.setLocationRelativeTo(null);
f.setVisible(true);
【讨论】:
【参考方案2】:不是DragAndDrop
和UndoAndRedo
的粉丝
必须将数据加载到UndoManager
并定义UndoAction undoAction = new UndoAction();
,这对于简单的Graphics
或e.i. 也是可能的。
class UndoHandler implements UndoableEditListener
@Override
public void undoableEditHappened(UndoableEditEvent e)
undoManager.addEdit(e.getEdit());
undoAction.update();
并创建Swing Action
(添加到JButton)
以将内容刷新回JTextField
class UndoAction extends AbstractAction
private static final long serialVersionUID = 1L;
UndoAction()
super("Undo");
setEnabled(false);
@Override
public void actionPerformed(ActionEvent e)
try
undo.undo();
catch (CannotUndoException ex)
System.out.println("Unable to undo: " + ex);
ex.printStackTrace();
update();
protected void update()
if (undo.canUndo())
setEnabled(true);
putValue(Action.NAME, undo.getUndoPresentationName());
else
setEnabled(false);
putValue(Action.NAME, "Undo");
【讨论】:
没有真正了解您的代码,但您的意思是扩展 UndoableEdit 并在重载的 undo/redo 方法中制作 setText?【参考方案3】:找到解决方法:
public class SetTextEditUndo extends AbstractUndoableEdit
public JTextField src;
public String oldValue;
public String newValue;
public SetTextEditUndo(JTextField src, String oldValue, String newValue)
this.src = src;
this.oldValue = oldValue;
this.newValue = newValue;
@Override
public void undo() throws CannotUndoException
setTextIgnoringUndo(src, oldValue);
@Override
public void redo() throws CannotRedoException
setTextIgnoringUndo(src, newValue);
@Override
public boolean canUndo()
return true;
@Override
public boolean canRedo()
return true;
public static void setTextIgnoringUndo(JTextField tf, String str)
PlainDocument doc = (PlainDocument) tf.getDocument();
UndoableEditListener uel = doc.getUndoableEditListeners()[0];
doc.removeUndoableEditListener(uel);
tf.setText(str);
doc.addUndoableEditListener(uel);
【讨论】:
以上是关于在 JTextField 和 setText 中撤消的主要内容,如果未能解决你的问题,请参考以下文章
有没有办法在不触发 DocumentListener 的 removeupdate() 的情况下调用 JTextField.setText()?