I am making a text editor using Java swing. I am using JTextArea for the same. I want to know how I can use Undo and Redo functionality in JTextArea as I am not able to use it.
I created a simple class that can assign undo functionality to a JTextcomponent (JTextField, JTextArea, etc.) with a single method call:
UndoTool.addUndoFunctionality(area);
or alternatively construct a new JTextArea with the undo functionality pre-assigned:
UndoTool.createJTextFieldWithUndo();
Here is the implementation of the utility class:
public class UndoTool {
private static final String REDO_KEY = "redo";
private static final String UNDO_KEY = "undo";
private JTextComponent component;
private KeyStroke undo = KeyStroke.getKeyStroke("control Z");
private KeyStroke redo = KeyStroke.getKeyStroke("control Y");
public UndoTool(JTextComponent component) {
this.component = component;
}
public void setUndo(KeyStroke undo) {
this.undo = undo;
}
public void setRedo(KeyStroke redo) {
this.redo = redo;
}
public static void addUndoFunctionality(JTextComponent component) {
UndoTool tool = new UndoTool(component);
UndoManager undo = tool.createAndBindUndoManager();
tool.bindUndo(undo);
tool.bindRedo(undo);
}
public static JTextArea createJTextAreaWithUndo() {
JTextArea area = new JTextArea();
addUndoFunctionality(area);
return area;
}
public static JTextField createJTextFieldWithUndo() {
JTextField field = new JTextField();
addUndoFunctionality(field);
return field;
}
public UndoManager createAndBindUndoManager() {
Check.notNull(component);
UndoManager manager = new UndoManager();
Document document = component.getDocument();
document.addUndoableEditListener(event -> manager.addEdit(event.getEdit()));
return manager;
}
public void bindRedo(UndoManager manager) {
component.getActionMap().put(REDO_KEY, new AbstractAction(REDO_KEY) {
@Override
public void actionPerformed(ActionEvent evt) {
try {
if (manager.canRedo()) {
manager.redo();
}
} catch (CannotRedoException ignore) {
}
}
});
component.getInputMap().put(redo, REDO_KEY);
}
public void bindUndo(UndoManager manager) {
component.getActionMap().put(UNDO_KEY, new AbstractAction(UNDO_KEY) {
@Override
public void actionPerformed(ActionEvent evt) {
try {
if (manager.canUndo()) {
manager.undo();
}
} catch (CannotUndoException ignore) {
}
}
});
component.getInputMap().put(undo, UNDO_KEY);
}
}
You can do like this
UndoManager manager = new UndoManager();
textArea.getDocument().addUndoableEditListener(manager);
Once the manager is attached to the document of the JTextArea, it will monitor all changes to the contents of the text area.
After attaching the manager to the text component, you must provide some means to tell the manager to undo/redo an operation.
Call the public void undo() and public void redo() method of the UndoManager where necessary(Eg. actionPerformed() method of an actionlistener)
You can attach Action objects to a button in the following way instead of calling undo() and redo() methods which simplifies the task:
JButton undoButton = new JButton(UndoManagerHelper.getUndoAction(manager));
JButton redoButton = new JButton(UndoManagerHelper.getRedoAction(manager));
I had to go through multiple links just to get enough help. I'm adding here what I implemented successfully just to help future visitors. I implemented this using JTextPane but am assuming the same would apply for the JTextArea
JTextArea textArea = new JTextArea();
JButton undo = new JButton("Undo");
JButton redo = new JButton("Redo");
KeyStroke undoKeyStroke = KeyStroke.getKeyStroke(
KeyEvent.VK_Z, Event.CTRL_MASK);
KeyStroke redoKeyStroke = KeyStroke.getKeyStroke(
KeyEvent.VK_Y, Event.CTRL_MASK);
UndoManager undoManager = new UndoManager();
Document document = textArea.getDocument();
document.addUndoableEditListener(new UndoableEditListener() {
@Override
public void undoableEditHappened(UndoableEditEvent e) {
undoManager.addEdit(e.getEdit());
}
});
// Add ActionListeners
undo.addActionListener((ActionEvent e) -> {
try {
undoManager.undo();
} catch (CannotUndoException cue) {}
});
redo.addActionListener((ActionEvent e) -> {
try {
undoManager.redo();
} catch (CannotRedoException cre) {}
});
// Map undo action
textArea.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW)
.put(undoKeyStroke, "undoKeyStroke");
textArea.getActionMap().put("undoKeyStroke", new AbstractAction() {
@Override
public void actionPerformed(ActionEvent e) {
try {
undoManager.undo();
} catch (CannotUndoException cue) {}
}
});
// Map redo action
textArea.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW)
.put(redoKeyStroke, "redoKeyStroke");
textArea.getActionMap().put("redoKeyStroke", new AbstractAction() {
@Override
public void actionPerformed(ActionEvent e) {
try {
undoManager.redo();
} catch (CannotRedoException cre) {}
}
});
As I understand it, JTextArea has no inherent Undo/Redo functionality built in, but a Google search did find this article which might be helpful.
There apparently exists an Undo Manager in javax.swing
which you can hook up to the JTextArea's change events.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With