JTextArea setText()&的UndoManager

时间:2014-06-26 14:23:58

标签: java swing jtextarea undo-redo

我正在使用UndoManager来捕获JTextArea中的更改。

然而,方法setText()会删除所有内容,然后粘贴文本。当我撤消时,我首先看到一个空白区域,然后它会显示它之前的文本。

如何重现:

  1. 运行以下代码
  2. 点击setText()按钮
  3. CTRL + Z 撤消(你会看到一个空的textarea!)
  4. CTRL + Z 撤消(您将看到实际的先前文字)
  5. 我想跳过3)。

    import javax.swing.AbstractAction;
    import javax.swing.JFrame;
    import javax.swing.JTextArea;
    import javax.swing.KeyStroke;
    import javax.swing.event.UndoableEditEvent;
    import javax.swing.event.UndoableEditListener;
    import javax.swing.text.Document;
    import javax.swing.undo.CannotRedoException;
    import javax.swing.undo.CannotUndoException;
    import javax.swing.undo.UndoManager;
    
    import java.awt.event.ActionEvent;
    import javax.swing.JButton;
    import java.awt.event.ActionListener;
    
    @SuppressWarnings("serial")
    public class JTextComponentSetTextUndoEvent extends JFrame
    {
        JTextArea area = new JTextArea();
    
        public JTextComponentSetTextUndoEvent()
        {
            setSize(300, 300);
            setDefaultCloseOperation(EXIT_ON_CLOSE);
            getContentPane().setLayout(null);
    
            area.setText("Test");
            area.setBounds(0, 96, 146, 165);
            getContentPane().add(area);
    
            JButton btnSettext = new JButton("setText()");
            btnSettext.addActionListener(new ActionListener()
            {
                public void actionPerformed(ActionEvent arg0)
                {
                    area.setText("stackoverflow.com");
                }
            });
            btnSettext.setBounds(0, 28, 200, 50);
            getContentPane().add(btnSettext);
    
            final UndoManager undoManager = new UndoManager();
            Document doc = area.getDocument();
    
            doc.addUndoableEditListener(new UndoableEditListener()
            {
                public void undoableEditHappened(UndoableEditEvent evt)
                {
                    undoManager.addEdit(evt.getEdit());
                }
            });
    
            area.getActionMap().put("Undo", new AbstractAction("Undo")
            {
                public void actionPerformed(ActionEvent evt)
                {
                    try
                    {
                        if (undoManager.canUndo())
                        {
                            undoManager.undo();
                        }
                    } catch (CannotUndoException e)
                    {
                    }
                }
            });
    
            area.getInputMap().put(KeyStroke.getKeyStroke("control Z"), "Undo");
    
            area.getActionMap().put("Redo", new AbstractAction("Redo")
            {
                public void actionPerformed(ActionEvent evt)
                {
                    try
                    {
                        if (undoManager.canRedo())
                        {
                            undoManager.redo();
                        }
                    } catch (CannotRedoException e)
                    {
                    }
                }
            });
    
            area.getInputMap().put(KeyStroke.getKeyStroke("control Y"), "Redo");
        }
    
        public static void main(String[] args)
        {
            new JTextComponentSetTextUndoEvent().setVisible(true);
        }
    }
    

5 个答案:

答案 0 :(得分:5)

默认情况下,javax.swing.undo.UndoManager会保留每个可撤消的编辑内容,包括删除原始文本的内容(第3步)。个别修改无法访问,但您可以使用引用的here方法进行分组修改。关于您的示例的一些附加说明:

  • 要获得更好的跨平台搜索结果,请按照建议here使用getMenuShortcutKeyMask()

  • 使用layout;如有必要,请在 setSize()之后调用pack() ,如图here所示。

  • 应在event dispatch thread构建和操作Swing GUI对象。

Code

import java.awt.BorderLayout;
import java.awt.EventQueue;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import javax.swing.AbstractAction;
import javax.swing.JButton;
import javax.swing.JFrame;
import static javax.swing.JFrame.EXIT_ON_CLOSE;
import javax.swing.JTextArea;
import javax.swing.KeyStroke;
import javax.swing.event.UndoableEditEvent;
import javax.swing.event.UndoableEditListener;
import javax.swing.text.Document;
import javax.swing.undo.CannotRedoException;
import javax.swing.undo.CannotUndoException;
import javax.swing.undo.UndoManager;

@SuppressWarnings("serial")
public class JTextComponentSetTextUndoEvent extends JFrame {

    private static final int MASK
        = Toolkit.getDefaultToolkit().getMenuShortcutKeyMask();
    private JTextArea area = new JTextArea();
    private UndoManager undoManager = new UndoManager();

    public JTextComponentSetTextUndoEvent() {
        setDefaultCloseOperation(EXIT_ON_CLOSE);
        area.setText("Test");
        add(area);
        JButton btnSettext = new JButton("setText()");
        btnSettext.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent arg0) {
                area.setText("stackoverflow.com");
            }
        });
        add(btnSettext, BorderLayout.PAGE_END);
        Document doc = area.getDocument();
        doc.addUndoableEditListener(new UndoableEditListener() {
            @Override
            public void undoableEditHappened(UndoableEditEvent e) {
                undoManager.addEdit(e.getEdit());
                System.out.println(e);
            }
        });
        area.getActionMap().put("Undo", new AbstractAction("Undo") {
            @Override
            public void actionPerformed(ActionEvent evt) {
                try {
                    if (undoManager.canUndo()) {
                        undoManager.undo();
                    }
                } catch (CannotUndoException e) {
                    System.out.println(e);
                }
            }
        });
        area.getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_Z, MASK), "Undo");
        area.getActionMap().put("Redo", new AbstractAction("Redo") {
            @Override
            public void actionPerformed(ActionEvent evt) {
                try {
                    if (undoManager.canRedo()) {
                        undoManager.redo();
                    }
                } catch (CannotRedoException e) {
                    System.out.println(e);
                }
            }
        });
        area.getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_Y,MASK), "Redo");
        pack();
        setSize(320, 240);
        setLocationRelativeTo(null);
    }

    public static void main(String[] args) {
        EventQueue.invokeLater(new Runnable() {

            @Override
            public void run() {
                new JTextComponentSetTextUndoEvent().setVisible(true);
            }
        });
    }
}

答案 1 :(得分:2)

您可以尝试这样的事情:

//Works fine for me on Windows 7 x64 using JDK 1.7.0_60:
import java.awt.*;
import java.awt.event.*;
import java.util.*;
import javax.swing.*;
import javax.swing.event.*;
import javax.swing.text.*;
import javax.swing.undo.*;

public final class UndoManagerTest {
  private final JTextField textField0 = new JTextField("default");
  private final JTextField textField1 = new JTextField();
  private final UndoManager undoManager0 = new UndoManager();
  private final UndoManager undoManager1 = new UndoManager();

  public JComponent makeUI() {
    textField1.setDocument(new CustomUndoPlainDocument());
    textField1.setText("aaaaaaaaaaaaaaaaaaaaa");

    textField0.getDocument().addUndoableEditListener(undoManager0);
    textField1.getDocument().addUndoableEditListener(undoManager1);

    JPanel p = new JPanel();
    p.add(new JButton(new AbstractAction("undo") {
      @Override public void actionPerformed(ActionEvent e) {
        if (undoManager0.canUndo()) {
          undoManager0.undo();
        }
        if (undoManager1.canUndo()) {
          undoManager1.undo();
        }
      }
    }));
    p.add(new JButton(new AbstractAction("redo") {
      @Override public void actionPerformed(ActionEvent e) {
        if (undoManager0.canRedo()) {
          undoManager0.redo();
        }
        if (undoManager1.canRedo()) {
          undoManager1.redo();
        }
      }
    }));
    p.add(new JButton(new AbstractAction("setText(new Date())") {
      @Override public void actionPerformed(ActionEvent e) {
        String str = new Date().toString();
        textField0.setText(str);
        textField1.setText(str);
      }
    }));

    Box box = Box.createVerticalBox();
    box.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
    box.add(makePanel("Default", textField0));
    box.add(Box.createVerticalStrut(5));
    box.add(makePanel("replace ignoring undo", textField1));

    JPanel pp = new JPanel(new BorderLayout());
    pp.add(box, BorderLayout.NORTH);
    pp.add(p, BorderLayout.SOUTH);
    return pp;
  }
  private static JPanel makePanel(String title, JComponent c) {
    JPanel p = new JPanel(new BorderLayout());
    p.setBorder(BorderFactory.createTitledBorder(title));
    p.add(c);
    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 UndoManagerTest().makeUI());
    f.setSize(320, 240);
    f.setLocationRelativeTo(null);
    f.setVisible(true);
  }
}

class CustomUndoPlainDocument extends PlainDocument {
  private CompoundEdit compoundEdit;
  @Override protected void fireUndoableEditUpdate(UndoableEditEvent e) {
    if (compoundEdit == null) {
      super.fireUndoableEditUpdate(e);
    } else {
      compoundEdit.addEdit(e.getEdit());
    }
  }
  @Override public void replace(
      int offset, int length,
      String text, AttributeSet attrs) throws BadLocationException {
    if (length == 0) {
      System.out.println("insert");
      super.replace(offset, length, text, attrs);
    } else {
      System.out.println("replace");
      compoundEdit = new CompoundEdit();
      super.fireUndoableEditUpdate(new UndoableEditEvent(this, compoundEdit));
      super.replace(offset, length, text, attrs);
      compoundEdit.end();
      compoundEdit = null;
    }
  }
}

答案 2 :(得分:1)

一个简单的解决方法是使用replaceRange:

area.replaceRange(newText, 0, area.getText().length());

这只是一次编辑,因此只需一步撤消。

答案 3 :(得分:0)

我需要一个解决方案,将remove的remove / insert替换为单个undo(aterai的答案),并将单个字符的连续插入/删除视为单个撤消(类似于http://java-sl.com/tip_merge_undo_edits.html)。

组合代码为:

   /*##################*/
   /* TextCompoundEdit */
   /*##################*/
   class TextCompoundEdit extends CompoundEdit
     {
      private boolean isUnDone = false;

      /*************/
      /* getLength */
      /*************/
      public int getLength()
        {
         return edits.size();
        }

      /********/
      /* undo */
      /********/
      public void undo() throws CannotUndoException
        {
         super.undo();
         isUnDone = true;
        }

      /********/
      /* redo */
      /********/
      public void redo() throws CannotUndoException
        {
         super.redo();
         isUnDone = false;
        }

      /***********/
      /* canUndo */
      /***********/
      public boolean canUndo()
        {
         return (edits.size() > 0) && (! isUnDone);
        }

      /***********/
      /* canRedo */
      /***********/
      public boolean canRedo()
        {
         return (edits.size() > 0) && isUnDone;
        }
     }

   /*#################*/
   /* TextUndoManager */
   /*#################*/
   class TextUndoManager extends AbstractUndoableEdit 
                         implements UndoableEditListener
     {     
      private String lastEditName = null;
      private int lastStart = 0;
      private ArrayList<TextCompoundEdit> edits = new ArrayList<TextCompoundEdit>();
      private TextCompoundEdit current;
      private int pointer = -1;
      private int groupIndex = 0;
      private String groupName = null;

      /************************/
      /* undoableEditHappened */
      /************************/
      public void undoableEditHappened(
        UndoableEditEvent e)
        {
         boolean isNeedStart = false;
         UndoableEdit edit = e.getEdit();

         if (! (edit instanceof AbstractDocument.DefaultDocumentEvent))
           { return; }

         AbstractDocument.DefaultDocumentEvent event = (AbstractDocument.DefaultDocumentEvent) edit;

         int start = event.getOffset();

         String editName;

         /*============================================*/
         /* If an explicit group name is not present,  */
         /* use the INSERT/REMOVE name from the event. */
         /*============================================*/

         if (groupName != null)
           { editName = groupName; }
         else
           { editName = event.getType().toString(); }

         /*============================*/
         /* Create a new compound edit */
         /* for the very first edit.   */
         /*============================*/

         if (current == null)
           { isNeedStart = true; }

         /*============================*/
         /* Create a new compound edit */
         /* for a different operation. */
         /*============================*/

         else if ((lastEditName == null) || 
                  (! lastEditName.equals(editName)))
           { isNeedStart = true; }

         /*================================================*/
         /* Only group continuous single character inserts */
         /* and deletes. Create a new edit if the user has */
         /* moved the caret from its prior position.       */
         /*================================================*/

         else if (groupName == null)
           {            
            if ((event.getType() == DocumentEvent.EventType.INSERT) &&
                     (start != (lastStart + 1)))
              { isNeedStart = true; }
            else if ((event.getType() == DocumentEvent.EventType.REMOVE) &&
                     (start != (lastStart - 1)))
              { isNeedStart = true; }
           }

         /*=========================================*/
         /* Adding a new edit will clear all of the */
         /* redos forward of the current position.  */
         /*=========================================*/

         while (pointer < edits.size() - 1)
           {
            edits.remove(edits.size() - 1);
            isNeedStart = true;
           }

         /*===================*/
         /* Add the new edit. */
         /*===================*/

         if (isNeedStart)
           { createCompoundEdit(); }

         current.addEdit(edit);

         /*=====================================*/
         /* Remember prior state for next edit. */
         /*=====================================*/

         lastEditName = editName;
         lastStart = start;
        }

      /*********************/
      /* startEditGrouping */
      /*********************/
      public void startEditGrouping()
        {
         groupName = "Group-" + groupIndex++;
        }

      /********************/
      /* stopEditGrouping */
      /********************/
      public void stopEditGrouping()
        {
         groupName = null;
        }

      /**********************/
      /* createCompoundEdit */
      /**********************/
      private void createCompoundEdit()
        {
         if (current == null)
           { current = new TextCompoundEdit(); }
         else if (current.getLength() > 0)
           { current = new TextCompoundEdit(); }

         edits.add(current);
         pointer++;
        }

      /********/
      /* undo */
      /********/ 
      public void undo() throws CannotUndoException
        {
         if (! canUndo())
           { throw new CannotUndoException(); }

         TextCompoundEdit u = edits.get(pointer);
         u.undo();
         pointer--;
        }

      /********/
      /* redo */
      /********/
      public void redo() throws CannotUndoException
        {
         if (! canRedo())
           { throw new CannotUndoException(); }

         pointer++;
         TextCompoundEdit u = edits.get(pointer);
         u.redo();
        }

      /***********/
      /* canUndo */
      /***********/
      public boolean canUndo()
        { 
         return pointer >= 0; 
        }

      /***********/
      /* canRedo */
      /***********/
      public boolean canRedo()
        {
         return (edits.size() > 0) && (pointer < (edits.size() - 1));
        }
     }

   /*#######################*/
   /* TextUndoPlainDocument */
   /*#######################*/
   class TextUndoPlainDocument extends PlainDocument 
     {    
      private TextUndoManager undoManager;

      /*************************/
      /* TextUndoPlainDocument */
      /*************************/
      TextUndoPlainDocument(
        TextUndoManager theManager)
        {
         super();
         undoManager = theManager;
         this.addUndoableEditListener(undoManager);
        }

      /***********/
      /* replace */
      /***********/
      @Override 
      public void replace(
        int offset, 
        int length,
        String text, 
        AttributeSet attrs) throws BadLocationException
        {
         if (length == 0)
           { super.replace(offset,length,text,attrs); }
         else
           {
            undoManager.startEditGrouping();
            super.replace(offset,length,text,attrs); 
            undoManager.stopEditGrouping();
           }
        }
     }

我以这种方式调用它:

JTextArea textArea = new JTextArea(); 
TextUndoManager textAreaUndo = new TextUndoManager();
textArea.setDocument(new TextUndoPlainDocument(textAreaUndo));

答案 4 :(得分:0)

    JTextArea jTextArea = new JTextArea("");
    UndoManager jTextAreaUndoManager = new UndoManager();
    jTextArea.getDocument().addUndoableEditListener(jTextAreaUndoManager);

    jTextArea.addKeyListener(new KeyAdapter() {
            @Override
            public void keyTyped(KeyEvent e) {
                //ctrl+z
                if (e.getKeyChar() == 26) {
                    jTextAreaUndoManager.undo();
                }
                //ctrl+y
                if (e.getKeyChar() == 25) {
                    jTextAreaUndoManager.redo();
                }
            }
        });