In this exercise, we'll update the previous sample application so that all the commands are undoable. This requires the following changes:
Making Undoable CommandsFirst, all the command classes must now implement IUndoableCommand. We'll start with RotateClockwiseCommand: package com.peachpit.aas3wdp.commandexample.commands { import com.peachpit.aas3wdp.commands.IUndoableCommand; import flash.display.DisplayObject; public class RotateClockwiseCommand implements IUndoableCommand{ private var _receiver:DisplayObject; public function RotateClockwiseCommand(receiver:DisplayObject) { _receiver = receiver; } public function execute():void { _receiver.rotation += 20; } public function undo():void { _receiver.rotation -= 20; } } } Next we'll make a similar edit to RotateCounterclockwiseCommand: package com.peachpit.aas3wdp.commandexample.commands { import com.peachpit.aas3wdp.commands.IUndoableCommand import flash.display.DisplayObject; public class RotateCounterclockwiseCommand implements IUndoableCommand { private var _receiver:DisplayObject; public function RotateCounterclockwiseCommand(receiver:DisplayObject) { _receiver = receiver; } public function execute():void { _receiver.rotation -= 20; } public function undo():void { _receiver.rotation += 20; } } } Likewise we'll edit ScaleUpCommand: package com.peachpit.aas3wdp.commandexample.commands { import com.peachpit.aas3wdp.commands.IUndoableCommand; import flash.display.DisplayObject; public class ScaleUpCommand implements IUndoableCommand { private var _receiver:DisplayObject; public function ScaleUpCommand(receiver:DisplayObject) { _receiver = receiver; } public function execute():void { _receiver.scaleX += .1; _receiver.scaleY += .1; } public function undo():void { _receiver.scaleX -= .1; _receiver.scaleY -= .1; } } } And then we'll edit ScaleDownCommand: package com.peachpit.aas3wdp.commandexample.commands { import com.peachpit.aas3wdp.commands.IUndoableCommand; import flash.display.DisplayObject; public class ScaleDownCommand implements IUndoableCommand { private var _receiver:DisplayObject; public function ScaleDownCommand(receiver:DisplayObject) { _receiver = receiver; } public function execute():void { _receiver.scaleX -= .1; _receiver.scaleY -= .1; } public function undo():void { _receiver.scaleX += .1; _receiver.scaleY += .1; } } } Each of the changes in the command classes amounts to the same thing: implement IUndoableCommand rather than ICommand, and add the undo() method so that it reverses the effect of the execute() method. Recording Command HistoryWe can next modify the command button so that it records the command history. We'll accomplish this by using CommandStack. Each time the command button calls the execute() method of a command object, it will also add the command object to the stack. package com.peachpit.aas3wdp.commandexample.controls { import com.peachpit.aas3wdp.controls.BasicButton; import com.peachpit.aas3wdp.commands.ICommand; import flash.events.MouseEvent; import com.peachpit.aas3wdp.commands.CommandStack; public class CommandButton extends BasicButton { private var _command:ICommand; public function set command(value:ICommand):void { _command = value; } public function CommandButton(label:String) { super(label); addEventListener(MouseEvent.CLICK, onClick); } private function onClick(event:MouseEvent):void { if(_command != null) { _command.execute(); CommandStack.getInstance().putCommand(_command); } } } } With this change, we now have a history of the commands that have been executed. Adding an Undo ButtonNext we add an undo button to the main class. The undo button-click event handler retrieves the last-run command. It then tests to see whether it is an undoable command. If so, it calls undo(). package { import flash.display.Sprite; import com.peachpit.aas3wdp.commandexample.commandcontainers.CommandContainer; import com.peachpit.aas3wdp.commandexample.shapes.Rectangle; import com.peachpit.aas3wdp.commandexample.commands.RotateClockwiseCommand; import com.peachpit.aas3wdp.commandexample.commands.RotateCounterclockwiseCommand import com.peachpit.aas3wdp.commandexample.commands.ScaleUpCommand; import com.peachpit.aas3wdp.commandexample.controls.CommandButton; import com.peachpit.aas3wdp.controls.BasicButton; import com.peachpit.aas3wdp.commandexample.commands.ScaleDownCommand; import flash.events.MouseEvent; import com.peachpit.aas3wdp.commands.CommandStack; import com.peachpit.aas3wdp.commands.ICommand; import com.peachpit.aas3wdp.commands.IUndoableCommand; public class CommandExample extends Sprite { public function CommandExample() { var rectangle:Rectangle = new Rectangle(0xFFFFFF, 50); rectangle.x = 200; rectangle.y = 200; addChild(rectangle); var button:CommandButton = new CommandButton("apply command"); addChild(button); button.y = 250; var container:CommandContainer = new CommandContainer(new RotateClockwiseCommand(rectangle), "rotate clockwise", 0, 0); addChild(container); container = new CommandContainer(new RotateCounterclockwiseCommand (rectangle), "rotate counter-clockwise", 0, 55); addChild(container); container = new CommandContainer(new ScaleUpCommand(rectangle), "scale up", 0, 110); addChild(container); container = new CommandContainer(new ScaleDownCommand(rectangle), "scale down", 0, 165); addChild(container); var undoButton:BasicButton = new BasicButton("undo"); addChild(undoButton); undoButton.y = 280; undoButton.addEventListener(MouseEvent.CLICK, onUndo); } private function onUndo(event:MouseEvent):void { var stack:CommandStack = CommandStack.getInstance(); if(stack.hasPreviousCommands()) { var command:ICommand = stack.previous(); if(command is IUndoableCommand) { IUndoableCommand(command).undo(); } } } } } With this revision, you can now test the application. As you run commands, you can also undo them by clicking the undo button. |