在 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】:

不是DragAndDropUndoAndRedo 的粉丝

必须将数据加载到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 中撤消的主要内容,如果未能解决你的问题,请参考以下文章

Jtextfield setText() 根本不起作用

有没有办法在不触发 DocumentListener 的 removeupdate() 的情况下调用 JTextField.setText()?

创建后如何在 JTextField 中设置新文本?

java如何获取输入框内容并赋值

尝试仅允许将数字输入JTextField,将非数字设置为0

JAVA中对JTextField对象的值的获取操作,为啥注释的部分没有效果呢?难道有啥错误么?