Wizard API updated!
Tim Boudreau has released a new version of the Swing Wizard library (version 0.997) that fixes the WizardException bug reported in JavaWorld's recent Open Source Java Project profile. The article's examples have been reworked to test out the new, improved WizardException. Thanks, Tim, for this helpful fix!
Open Source Java Projects: The Wizard API

Newsletter sign-up

Sign up for our technology specific newsletters.

Enterprise Java
View all newsletters

Email Address:

Add an undo/redo function to your Java apps with Swing

Find out how the Swing GUI component set utilizes the Command pattern for easy support of undo/redo

Not too long ago, Sun's Java division introduced the JFC, a comprehensive set of UI components and foundation services that give Java developers more flexibility to determine the look and feel of their applications. The UI component library, called Swing, is a whole lot more than a stripped-down set of lightweight UI components. Swing takes Java applications a step forward by allowing easy implementation of application services like undo -- the topic of our discussion today.

Historically, application frameworks (for example, MacApp framework) have based the design of the undo/redo mechanism around the Command pattern. And that's what we're going to do as well. We'll discuss the Command pattern and describe how it supports the design of undo/redo systems. We'll then examine Java's support for the pattern and see how Swing's undo package adds the missing functionality, providing you with a complete undo/redo mechanism.

Requirements from an undo/redo mechanism

Undo allows users to correct their mistakes and also to try out different aspects of the application without risk of repercussions.

At minimum, an undo/redo mechanism should provide users with the ability to:

  • Unexecute (undo) the last action they just performed
  • Re-execute (redo) the last undone action
  • Undo and redo several recent actions (preferable, but optional)


In order to design such a mechanism, we must treat the user's operations as individual atomic actions (self-contained actions that know how to undo/redo their effect on the application state) that should be stored for undo or redo later on. We can fulfill these requirements by using design patterns, specifically the Command pattern. If you are already familiar with the design patterns (specifically the Command pattern and how Java supports it), you can skip the next three sections and move right to "The undo/redo mechanism in Swing."

Design patterns

In a nutshell, design patterns encourage design reuse by providing established, successful design solutions for particular situations (also known as contexts).

You may be wondering why I'm discussing design patterns in an article devoted to undo/redo mechanisms. It's really quite simple. In addition to presenting you with the technical implementation issues, I think it's important that you understand the design essentials of an undo/redo systems.

According to Design Patterns: Elements of Reusable Object-Oriented Software by the now infamous Gang of Four (see Resources for more information), the essential parts of any pattern are:

  • Intent -- The design goal that this pattern addresses
  • Applicability -- In what situation the pattern can be applied
  • Structure -- The design solution to the design problem
  • Consequences -- The trade-off of the solution


A number of different patterns exist, but we're concerned only with the one that addresses undo/redo capabilities: the Command pattern.

The Command pattern
Once again, according to Design Patterns, the purpose of the Command pattern is to:

Encapsulate a request as an object, thereby letting you parameterize clients with different requests, queue or log requests, and support undoable operations.


Let's see how this works.

At the heart of the pattern is the Command interface, which defines the execute() method.

001 public interface Command {
002
003    public void execute();
004
005 }


All user actions (such as insert text, cut, paste; any operation that the user can perform) are encapsulated as command objects. A command object is a class that implements the function of performing the user's requested operation. For example, in a text editor when the user types in some text, we do not add this text directly to the document within the UI code; instead we create an InsertTextCommand object that we then apply to the text document.

All command objects must implement the Command interface. This interface declares an execute() method that should invoke the actual command operation on the target object. The following snippet shows a cut command for a text editor application.

001 public class CutCommand implements Command{
002
003     private TextArea target_;
004
005     public CutCommand(TextArea area) {
006          target_ = area;
007     }
008
009     public execute() {
010
011 //       target_.cut();
012          int startPos = target_.getSelectionStart();
013          int endPos = target_.getSelectionEnd ();
014          String text = target_.getText ();
015          target_.setText (text.substring (0, startPos) + text.substring (endPos));
016
017     }
018
019 }


When a CutCommand object is created, it's initialized with its target -- in this case, a text area. To build a text editor with cut capabilities, we attach this CutCommand to the editor's Cut menu item with the text area as its target. When the Cut menu item is selected, the execute() method of this command object is automatically invoked and the cut operation is performed as desired.

From a design perspective, the important thing about the Command pattern is that it treats each user operation as a first-class object. If we add unexecute() and reexecute() methods to these objects (that is, to the Command interface), we can then support unexecution (undo) or re-execution (redo) of operations, giving us a basic undo/redo mechanism.

Of course, adding undo and redo capabilities to this mechanism requires "smarter" commands. Not only should a command be able to invoke an operation on its target, but it should also be able to undo or redo that operation when prompted. For example, the cut command would store the selected string and location before the cut, allowing it to return the selection to the text area if its undo() method is invoked.

Encapsulating each operation as a separate first-level object means that we can also easily support multilevel undo/redo operations: We store each command operation that the user performs in a history list. When the user selects undo, we perform the undo operation of the current item in the list and then step backwards to the previous item. To redo an operation, we execute the redo operation of the next item in the list and step forward. If the user performs a new command after some undo operations, we clear the front of the list to disable further redos.

1 | 2 | 3 | 4 | 5 |  Next >
Resources
  • Download the complete source as a gzipped TAR file http://www.javaworld.com/jw-06-1998/undoredo/jw-06-undoredo.tar.gz
  • Download the complete source as a ZIP file http://www.javaworld.com/jw-06-1998/undoredo/jw-06-undoredo.zip
  • Read Sun's Swing applet page to find out how to run Swing applets on Netscape and Internet Explorer http://java.sun.com/products/jfc/swingdoc-current/applets.html
  • Add 1.1 support to Netscape Communicator 4.0x with these step-by-step instructions for applying the 1.1 support patch http://developer.netscape.com/software/jdk/download.html
  • Download the JDK 1.1 preview release for Communicator 4.05 http://developer.netscape.com/software/jdk/download.html
  • Find out more about design patterns at the Pattern Web site http://hillside.net/patterns/patterns.html
  • If you don't already have a copy, pick up Design Patterns Elements of Reusable Object-Oriented Software (Addison-Wesley, ISBN 0-201-63361-2) to improve your understanding of design patterns http://hillside.net/patterns/DPBook/DPBook.html
  • Find out more about Unified Modeling Language at Rational Software's UML Resource Center http://www.rational.com/uml