Add Undo/Redo functionality to a Java app

Add Undo/Redo functionality to a Java app

Recently I had to figure out a way to add undo/redo functionality to an existing Java application. Searching the web for some guidance I got a bit scared by finding posts that gave the impression that undo in Java is an overly complex thing that requires 10+ mysterious classes, best combined with reflection and modified GoF patterns.

I write this post to show you how simple undo in Java is and how it can be easily added to an existing application. You do not need to study Command patterns or rebuild half of your app, just follow the pragmatic approach described below and add a few simple classes to your application!

In our concrete case the requirement was to add undo functionality to an existing Excel-like spreadsheet application that had a horrible menu management and no patterns in it at all.

Step 1: Determine what to undo

To undo user actions, you absolutely need to know what actions you want to become undoable. In a spreadsheet application, you probably would like to undo cell modifications and row or column inserts, but not save or open operations. And it’s up to you to include visualization changes like modification of column width or sorting in the undoable operations. Every undoable action will add one small class to your project.

Let’s capture some examples of undoable operations for our spreadsheet:

– Cell Value change
– Row insert
– Row delete
– Change column width
– Sort rows

I will show how to implement the first of these undoable edits.

Step 2: Tools of trade

So now, from those 10+ magic classes that you can find in some descriptions on the net, where is the real magic?

It is the javax.swing.undo.UndoManager

UndoManager

The UndoManager class

This class is the heart of the undo functionality. It is responsible for managing the list of undoable operations and ‘manages’ everything you need to undo an action.

Please be aware that UndoManager is in the Swing package, but this does not mean that you must have a Swing GUI to use it!

You will need usually one UndoManager in your application (it is designed to be a singleton); it provides you with:

– the possibility to register information about the changes an user action produces, those are called ‘Edits’
– the ability to undo a previously registered Edit
– the ability to redo a previously undone Edit
– the functionality to invalidate all edits.

As you may expect, the UndoManager manages the undoable operation as ‘Edits’. An Edit is an object of a class you have to create, that implements the javax.swing.undo.UndoableEdit interface.

Interface UndoableEdit

Interface UndoableEdit

Step 3: Building your Edits

Now let’s build a class implementing the Edit for value changes in our Excel-like table. We base our Edit on the AbstractUndoableEdit class provided by the Java framework, so we need to override only a few methods.

Custom class TableCellEdit

Custom class TableCellEdit

TableCellEdit implements the UndoableEdit interface (actually AbstractUndoableEdit does). It’s operations are the following:

public TableCellEdit(IUndoableTableModel tableModel, Object oldValue, Object newValue, int row, int column)

This constructor takes all information required for the undo. We want to be able to restore the previous value of a table cell, so we need the TableModel, the old value, the new value (for redo), and the row/column position in the model where value goes.

public String getPresentationName()

This returns a String to be presented to the user in the edit menu, for example “Cell Edit”, which will be presented as “Undo Cell Edit” or “Redo Cell Edit” respectively.

public void undo() throws CannotUndoException

This operation actually performs the undo operation. For undoing a value change in a TableModel, will set the cell at position row/column the oldValue (given in the constructor).
The required housekeeping of the undo/redo stack will be done automatically by the UndoManager and UndoableEdit classes. Implementation of the undo usually will be something as simple as

 // Call the UndoableEdit class for housekeeping
 super.undo();
 // set the old value, excluding all undo activity
 tableModel.setValueAt(oldValue, row, column, false)
public void redo() throws CannotRedoException

Same as undo(), except that we would set the newValue to the cell.

We will see in a moment, how to merge the Edit-classes in your existing code. Stay tuned.

Extending the basic Edit

The TableCellEdit class illustrated above provides the required functionality for undoing and redoing edits. But wait – what about cursor movements? Shouldn’t they be treated as edits as well?
Without handling cursor movements as UndoableEdits, an undo of a cell value change would result in the substitution of the value, but leave the cursor in it’s current position. That would be quite irritating to the user since he was at the changed cell position when he entered the new value.
While cursor movements usually do not represent any information of value to the user and should not be undoable, the repositioning to the point where the undo happened is important because it is the behavior the user expects. Thus we move the cursor to the field/position where the undo/redo happens.

To implement this, we capture the on screen position for every cell that gets edited. Here we must pay attention to the Swing specific fact that TableModel and the shown JTable on screen may not be the same. Our solution was simply to convert the model position to the View position (JTable’s convertRowIndexToView()) and set the position in the undo and redo operations (editCellAt()). To do this, every TableCellEdit receives a reference to the edited Jtable (See below in the ‘Listeners’ paragraph how we inject this reference).

Step 4: Plugging it in

Now that you have seen, what the main parts of the undo/redo implementation are, we can complete the setup by adding the required ‘glue’ to the points where existing code needs to be modified for adding undo functionality.

UndoManager

You need at least one UndoManager class. We decided to have only one UndoManager in the whole application and to use a specialization of the Java-Provided UndoManager that updates our application menu and toolbar with every undo/redo operation.

TableModel

We use a form containing a Jtable in which we track edits. The JTable is backened by it’s TableModel. To capture cell value changes, we modify the TableModel’s setValueAt() method. For every new value set, a new TableCellEditObject is created and call to undoSupport.postEdit() is performed. UndoableEditSupport is provided by Swing and manages a list of undo event listeners (you will see below, why this is useful).

// Add undo support to TableModel
 private UndoableEditSupport undoSupport = new UndoableEditSupport();
 ...
// in setValueAt(): if anything changed -> announce edit
 if(oldValue!=null && !oldValue.equals(newValue)
      || newValue != null && !newValue.equals(oldValue)) {
    TableCellEdit cellEdit = new TableCellEdit(this, oldValue, newValue, modelRow, modelColumn);
    undoSupport.postEdit(cellEdit);
 }

You must pay attention here to the fact that setValueAt() is called when the user edits a cell and also when we restore an old value during undo. We implemented a second setValueAt() that does not create a TableCellEdit onject for the latter case.

MenuActions

To actually perform an undo when the user selects undo from the application menu, all you have to do is to call your UpdateManager’s undo() operation. The UpdateManager picks the last undoable Edit from it’s undo stack and executes it’s undo() operation.
With our global solution, we do just:

 PCSUndoManager manager = Globals.getInstance().getUndoManager();
 manager.undo();

Listeners

The Java undo facility provides an UndoableEditListener interface to capture notifications on every edit. You must define a listener that decides what to do with an UndoableEdit that gets advertised with the call to UpdateManager.postEdit().
Usually you just want to add it to the undo queue:

 public void undoableEditHappened(UndoableEditEvent e) {
    undoManager.addEdit(e.getEdit());
 }

You register a class implementing just this with the UndoableEditSupport (in our TableModel). Fortunally the  UndoManager class implements the UndoableEditListener interface and provides exactly this as a default implementation. We do not need any customization here, so we use it as the listener.

tableModel.getUndoSupport().addUndoableEditListener(undoManager);

As mentioned above, we have a special need to track on-screen cursor positions inside the table. To do this our edits need to know to which table they belong, because the table provides the model-to-view conversion. Since we create our edits in the TableMODEL and do not want to spoil the design by adding a dependency of the model on the table (Swing is designed vice versa), UndoableEditListeners come in handy. We used a listener to inject the current table edited into the TableCellEdit objects. Note that this is just a feature required by our design, you will do without, unless implementing undo in JTables.

Register our Form containing model and JTable as a listener:

// Form implements UndoableEditListener
 public class PEForm implements UndoableEditListener
 ...
// in the Form's setup code, install the listener
 tableModel.getUndoSupport().addUndoableEditListener(this);
 ...
// Inject reference to current table in every edit inside our TableModel
 public void undoableEditHappened(UndoableEditEvent e) {
     UndoableEdit edit = e.getEdit();
     IPEEdit tce = (IPEEdit) edit;
     tce.setTable(theTable);
 }

Note: The IPEEdit interface defines only the setTable(JTable) operation shown.

Step 5: Try it

Above I have illustrated in 4 simple steps how to add undo/redo functionality to your Java appplication. You are now prepared to apply it on your own application. They are

– add an UndoManager to your application (eventually derive your own class to include automatic menubar updates)

For each action you want to be undoable:
– add class implementing the UndoAbleEdit interface (derive it from AbstractUndoableEdit)
– where the to-be-undone operation is performed, create a new Edit object and call UndoManager’s postEdit(edit) method.

Below you find some sample classes attached, be aware that it is not a running example, but intended to give you a more complete context.

Now let’s have a final look on some details in the process.

How does this ‘magic’ stuff actually work?

You may wonder what exactly happens when you implement the mechanism described above. This is best explained looking at a sequence diagram.

Sequence diagram for a cell edit and a successive undo

Sequence diagram for a cell edit and a successive undo

the first part of the diagram shows the creation of a TableCellEdit, the second part  shows what happens when the user selects Undo from the menu.

How many UndoManagers do I need?

In the sample shown above I said that one UndoManager is good enough. Actually there are use cases where you would have more than one UndoManager. One scenario might be an application handling multiple open files; if you do not make file switching undoable, you will need one UndoManager per open file to make sure that undos are limited to the currently active file and do not impact invisible data.

How many undo steps are available?

The UpdateManager has a built in limit of 100 un-/redoable edits. You may change this value by calling setLimit(). There is also a discardAllEdits() operation available that you might want to call for example when a new file is opened.

Why are there two setValueAt() in the TableModel?

You typically will set the value changes you want to be undoable in some kind of model object. When you do an undo or redo, you have to set the old (or new) value the same place. A pitfall is to call the same set operation in the Edit’s undo() as the one used to set a new value given by the user. This would produce a new Edit for each undo() and leave the currently undone operation on top of the undo stack – you would be able do undo once, but never be able to get to the previous step.

Where to get more information?

As stated above, I did not find very much useful information on the topic. You may try Neil Weber’s Blog for an additional view on the topic. Oracle’s Java tutorial gives some very basic advice on how to implement undo with standard text components.

Read the Javadocs for the involved classes.

Can you provide complete source code please?

No I can’t. The described solution was implemented in a not very well designed legacy application and it would be a headache to extract a running example.

Yes I can. Please find the source files for the example given above in the downloadable JavaUndoRedoFiles.zip
It is not runnable code but it may help you to better understand what exacty you need and where.

Have fun!
Dominik

10 Comments

  1. “I write this post to show you how simple undo in Java is and how it can be easily added to an existing application.”

    Very true. This is one of the most helpful post on undo I came across. Thank you!

    Reply
  2. Thank you so much for this. It’s a brilliant little tutorial on undo/redo and it’s most helpful.

    Reply
  3. Great article and very useful !
    But I have a remark about the setValue() thing.
    Since every edits are done by the user himself (via an UI in general), I think it is better to register a new edit (via postEdit()) at the place where the “setValue()” method is actually called instead of the method itself.

    For example imagine you have a method called “enterKeyPressed()” that you would called after the user press the Enter key or whatever. In this method you would then do:

    undoSupport.postEdit(edit);
    setValue(…);

    This would avoid to post unecessary edits for example if you programmatically called the setValueAt() method.
    It would also avoid you to have several setValueAt() methods.

    Regards,
    GrassEh

    Reply
  4. This is extremely cool, and turned something difficult into something easy, but I think I need to take it up one level higher.

    What if one is working on a distributed web application, where it might be possible for multiple people to be working on the same object at the same time? With one undo stack, when Tom hits ‘undo’ it may undo an edit that Terry (who sits half way around the world) just made.

    Right now, I’m thinking I’ll just create a ‘History of Edits’ so people can see what has been done. Its either that or create one stack per user, let everyone editing the same object (in this case an online role play) know others are actively editing, etc.

    Reply
    • Skip, the solution shown was designed for a desktop application where objects are locked during modification.

      I think that in Your case, both approaches are valid (one or more UndoManagers), depending on the exact requirements.
      If I imagine me editing an object in an online role play, setting the color to ‘green’, I would be terribly disappointed finding my coloring undone after a minute. In this case I would prefer a versioning mechanism for the objects, and a private undo stack for the changes I do while editing.

      Sorry for the late reply, could You share what was Your solution?

      Reply
  5. Thanks for this tutorial which I am presently using to install a do undo on a JTable. I hava a simple question regarding the method setValuAtLogic. For me such method cannot be different than the method setValueAt and therefore appears to me to be useless.
    Daniel

    Reply
  6. Daniel,
    please see the “Why are there two setValueAt() in the TableModel?” paragraph on this topic.
    Regards,
    Dominik

    Reply
  7. I simply do not see the need to discriminate between normal undoable action (that could have been done directlty in a normal way) and an undo action. The undo and redo features allow the user to set actions by moving a pointer in a stack of actions as memorized bacwards or forwords and all would pass through undoable.
    DaAniel.

    Reply
  8. ok I tested and no doubt we need to skip the undoable setValueAt() in case of undo() or redo(). Thanks

    Reply
  9. I have included this in a small app with a popup menu on my Jtable with 2 items : UNDO and REDO, assiciated with undoAtction and redoAction respectively. All UNDO actions work fine as they are correctly displayed as updated in the corresponding menu item but I never collect any Redo action in my REDO menu item. In other words each time I do an undo() by clicking on the displayed menu item after if I do undoManager.canRedo() I get all the time false. Why a redo action is not automatically stacked up each time by the undoManager. Am I missing something here?
    Thanks.

    Reply

Submit a Comment

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>