【问题标题】:JTextArea setText() & UndoManagerJTextArea setText() & UndoManager
【发布时间】:2014-08-17 10:59:22
【问题描述】:

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

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

如何重现:

  1. 运行以下代码
  2. 点击setText()按钮
  3. CTRL+Z 撤消(您会看到一个空的文本区域!)
  4. CTRL+Z 撤消(您将看到实际的先前文本)

我想跳过 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);
    }
}

【问题讨论】:

  • 为了尽快获得更好的帮助,请发布MCVE(最小完整且可验证的示例)。
  • @Andrew Thompson:添加了 MCVE 代码。
  • 如果问题总是出现为什么不在代码中调用.undo两次呢?
  • @Roan:只在设置文本时出现,手动编辑时不会出现。
  • hmm 也许撤消一次 做getText 看看该区域是否是空的,如果是的话,再撤消一次,否则什么都不做?

标签: java swing jtextarea undo-redo


【解决方案1】:

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

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

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

  • Swing GUI 对象应event dispatch thread 上构造和操作。

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);
            }
        });
    }
}

【讨论】:

    【解决方案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;
        }
      }
    }
    

    【讨论】:

    • 对。被覆盖的replace() 方法修复了它。
    【解决方案3】:

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

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

    这算作一次编辑,因此一步即可撤消。

    【讨论】:

    • 他的一个班轮帮了我大忙。我正在使用 org.fife.ui.rsyntaxtextarea.RSyntaxTextArea,它负责撤消管理器。
    【解决方案4】:

    我需要一个解决方案,将替换的删除/插入与单个撤消(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));
    

    【讨论】:

      【解决方案5】:
          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();
                      }
                  }
              });
      
      

      【讨论】:

      • 一些额外的解释或参考链接会为您的答案增添趣味。
      猜你喜欢
      • 2013-02-08
      • 2015-01-04
      • 1970-01-01
      • 1970-01-01
      • 2012-07-13
      • 2014-07-15
      • 2014-07-20
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多