我正在使用UndoManager
来捕获JTextArea
中的更改。
然而,方法setText()
会删除所有内容,然后粘贴文本。当我撤消时,我首先看到一个空白区域,然后它会显示它之前的文本。
如何重现:
setText()
按钮我想跳过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);
}
}
答案 0 :(得分:5)
默认情况下,javax.swing.undo.UndoManager
会保留每个可撤消的编辑内容,包括删除原始文本的内容(第3步)。个别修改无法访问,但您可以使用引用的here方法进行分组修改。关于您的示例的一些附加说明:
要获得更好的跨平台搜索结果,请按照建议here使用getMenuShortcutKeyMask()
。
应在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();
}
}
});