The Command Pattern for Undo/Redo
An Introduction with Examples in Java |
Prof. David Bernstein |
Computer Science Department |
bernstdh@jmu.edu |
Action
class that
implements javax.swing.undo.UndoableEditListener
and handles actionPerformed()
messages as
desiredAction
UndoableEditSupport
objectAction
to
the UndoableEditSupport
UndoableEditSupport
to post the editimport java.awt.*; import javax.swing.*; /** * A simple yard marker for a football scoreboard * * @version 1.0 * @author Prof. David Benrstein, James Madison University */ public class YardMarker extends JPanel { private int down, toGo, yard; private JLabel downL, sideL, toGoL, yardL; private String side; /** * Default Constructor */ public YardMarker() { super(); downL = new JLabel(); sideL = new JLabel(); toGoL = new JLabel(); yardL = new JLabel(); performLayout(); } /** * Returns the current down */ public int getDown() { return down; } /** * Returns the current side of the field the * ball is on */ public String getSide() { return side; } /** * Returns the current yards to go for a first down */ public int getToGo() { return toGo; } /** * Returns the current position of the ball */ public int getYard() { return yard; } /** * Layout this component */ private void performLayout() { setLayout(new GridLayout(1,7)); add(new JLabel("Down: ")); downL.setForeground(Color.BLUE); add(downL); add(new JLabel("To Go: ")); toGoL.setForeground(Color.BLUE); add(toGoL); add(new JLabel("On: ")); sideL.setForeground(Color.BLUE); add(sideL); yardL.setForeground(Color.BLUE); add(yardL); } /** * Set the current values */ public void setValues(int down, int toGo, String side, int yard) { this.down = down; this.toGo = toGo; this.side = side; this.yard = yard; downL.setText(""+down); toGoL.setText(""+toGo); sideL.setText(side); yardL.setText(""+yard); } }
import javax.swing.undo.*; /** * An (undoable) change to a YardMarker * * @version 1.0 * @author Prof. David Benrstein, James Madison University */ public class YardMarkerEdit extends AbstractUndoableEdit { private int down, toGo, yard; private int oldDown, oldToGo, oldYard; private String side; private String oldSide; private YardMarker ym; /** * Explicit Value Constructor */ public YardMarkerEdit(YardMarker ym, int down, int toGo, String side, int yard) { super(); this.ym = ym; this.down = down; this.toGo = toGo; this.side = side; this.yard = yard; oldDown = ym.getDown(); oldToGo = ym.getToGo(); oldSide = ym.getSide(); oldYard = ym.getYard(); ym.setValues(down, toGo, side, yard); } /** * Get a description of this edit */ public String getPresentationName() { return ""+down+" and "+toGo+" on "+side+" "+yard; } /** * Redo this edit */ public void redo() throws CannotUndoException { super.redo(); // Sets hasBeenDone to true ym.setValues(down, toGo, side, yard); } /** * Redo this edit */ public void undo() throws CannotUndoException { super.undo(); // Sets hasBeenDone to false ym.setValues(oldDown, oldToGo, oldSide, oldYard); } }
import java.awt.event.*; import javax.swing.*; import javax.swing.event.*; import javax.swing.undo.*; /** * A fairly generic undo/redo action that can be added to * menus and toolbars. * * It changes its state when an edit occurs and when * an undo/redo occurs. * * @version 1.0 * @author Prof. David Bernstein, James Madison University */ public class UndoRedoAction extends AbstractAction implements UndoableEditListener { private UndoableEdit lastEdit; /** * Default Constructor */ public UndoRedoAction() { putValue(Action.NAME, "Undo/Redo"); setEnabled(false); } /** * Perform the action */ public void actionPerformed(ActionEvent evt) { if (lastEdit != null) { if (lastEdit.canUndo()) { lastEdit.undo(); putValue(Action.NAME, lastEdit.getRedoPresentationName()); } else if (lastEdit.canRedo()) { lastEdit.redo(); putValue(Action.NAME, lastEdit.getUndoPresentationName()); } else { putValue(Action.NAME, "Undo/Redo"); setEnabled(false); } } } /** * Handle UndoableEidtHappened events * (Required by UndoableEditListener) */ public void undoableEditHappened(UndoableEditEvent evt) { lastEdit = evt.getEdit(); if (lastEdit.canUndo()) { putValue(Action.NAME, lastEdit.getUndoPresentationName()); setEnabled(true); } } }
import java.awt.*; import javax.swing.*; import javax.swing.undo.*; /** * An example that demonstrates undo/redo. * * @version 1.0 * @author Prof. David Bernstein, James Madison University */ public class Driver implements Runnable { private UndoableEditSupport support; private YardMarker ym; /** * The entry point of the application. * * @param args The command line arguments (ignored) */ public static void main(String[] args) throws Exception { Driver driver = new Driver(); SwingUtilities.invokeAndWait(driver); // Make a change YardMarkerEdit edit = new YardMarkerEdit(driver.ym, 1, 10, "Own", 35); driver.support.postEdit(edit); // Sleep to give time to see the scoreboard // before the change try { Thread.sleep(5000); } catch (InterruptedException ie) { } // Make a change edit = new YardMarkerEdit(driver.ym, 2, 8, "Own", 37); driver.support.postEdit(edit); } /** * The code to run in the event dispatch thread. */ public void run() { // Layout the frame JFrame f = new JFrame(); f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); f.setSize(400,400); Container contentPane = f.getContentPane(); contentPane.setLayout(new BorderLayout()); contentPane.add(new JLabel("Bernstein Stadium"), BorderLayout.NORTH); ym = new YardMarker(); contentPane.add(ym, BorderLayout.SOUTH); contentPane.add(new JLabel("Your ad here!"), BorderLayout.CENTER); JMenuBar menuBar = new JMenuBar(); JMenu editMenu = new JMenu("Edit"); UndoRedoAction urAction = new UndoRedoAction(); editMenu.add(urAction); menuBar.add(editMenu); f.setJMenuBar(menuBar); // Add undo/redo support support = new UndoableEditSupport(); support.addUndoableEditListener(urAction); f.setVisible(true); } }