0 people like it.

Undoable Commands

A simple implementation of an Undoable Command, with a Document to hold a stack of actions. 2 examples of UndoableCommand are given - 1 allows property changes to be remembered, and another which allows the user to execute an action with a corresponding undo. Further examples could include CompositeUndoableCommands where the command is itself a list of commands.

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
15: 
16: 
17: 
18: 
19: 
20: 
21: 
22: 
23: 
24: 
25: 
26: 
27: 
28: 
29: 
30: 
31: 
32: 
33: 
34: 
35: 
36: 
37: 
38: 
39: 
40: 
41: 
42: 
43: 
44: 
45: 
46: 
47: 
48: 
49: 
50: 
51: 
52: 
53: 
54: 
55: 
56: 
57: 
58: 
59: 
60: 
61: 
62: 
63: 
64: 
65: 
66: 
67: 
68: 
69: 
70: 
71: 
72: 
73: 
74: 
75: 
76: 
77: 
78: 
79: 
80: 
81: 
82: 
83: 
84: 
85: 
86: 
87: 
88: 
open System.Collections.Generic

// Base class for all UndoableCommand Objects
[<AbstractClass>]
type UndoableCommand(description:string) = 
    member this.Description = description
    abstract Execute : unit->unit
    abstract Undo : unit->unit

// Class that can handle property changes. Note we use reference cells to directly 
// manipulate the underlying values
type PropertyChangedUndoableCommand<'a>(description:string, fieldRef, newValue:'a) =
    inherit UndoableCommand(description) 
    let oldValue = !fieldRef
    override this.Execute() = 
        fieldRef:=newValue
    override this.Undo() = 
        fieldRef:=oldValue
        
// Class that executes actions to "do" and "undo"
// Obviously undo should actually undo what "do" does, but we cant enforce it
type DelegateUndoableCommand(description, doAction, undoAction) = 
    inherit UndoableCommand(description)
    override this.Execute() = doAction()
    override this.Undo() = undoAction()

//Document contains an example undo/redo stack
type Document() =
    let undoStack = Stack()
    let redoStack = Stack()

    let execute (command : UndoableCommand) =
        redoStack.Clear() //as we are executing a command any existing redo is invalidated
        undoStack.Push(command)
        command.Execute()

    let undo() = 
        if undoStack.Count > 0 then
            let command = undoStack.Pop()
            redoStack.Push(command)
            command.Undo()
            
    let redo() = 
        if redoStack.Count> 0 then
            let command = redoStack.Pop()
            undoStack.Push(command)
            command.Execute()
            
    member this.ExecuteCommand command = execute command
    member this.Undo() = undo()
    member this.Redo() = redo()
    member this.CanUndo = undoStack.Count > 0
    member this.CanRedo = redoStack.Count > 0
    
//Example implementation
type SomeObject(document:Document) = 
    let undoableProperty = ref 50 //Because of the command we cant use mutable

    member this.UndoableProperty with get() = !undoableProperty
                                 and set(value) =
                                    //instead of directly setting the property we create a command and 
                                    //execute it on the document.   
                                    let command = PropertyChangedUndoableCommand("Changed", undoableProperty, value)
                                    document.ExecuteCommand(command)
                           
let doc = Document() //Document that will hold our doings and undoings
let someObject = SomeObject(doc)

printf "Initial Value %d\n" someObject.UndoableProperty //50
someObject.UndoableProperty <- 100
printf "Updated Value %d\n" someObject.UndoableProperty //100
someObject.UndoableProperty <- 1000
printf "Updated Value %d\n" someObject.UndoableProperty //1000
doc.Undo()
printf "Undone Value %d\n" someObject.UndoableProperty // 100
doc.Undo()
printf "Undone Value %d\n" someObject.UndoableProperty // 50
doc.Undo()
printf "Undone Value %d\n" someObject.UndoableProperty // 50
doc.Undo()
printf "Undone Value %d\n" someObject.UndoableProperty // 50
doc.Redo()
printf "Redone Value %d\n" someObject.UndoableProperty // 100
doc.Redo()
printf "Redone Value %d\n" someObject.UndoableProperty // 1000
doc.Redo()
printf "Redone Value %d\n" someObject.UndoableProperty // 1000
System.Console.ReadLine()|>ignore
namespace System
namespace System.Collections
namespace System.Collections.Generic
Multiple items
type AbstractClassAttribute =
  inherit Attribute
  new : unit -> AbstractClassAttribute

Full name: Microsoft.FSharp.Core.AbstractClassAttribute

--------------------
new : unit -> AbstractClassAttribute
Multiple items
type UndoableCommand =
  new : description:string -> UndoableCommand
  abstract member Execute : unit -> unit
  abstract member Undo : unit -> unit
  member Description : string

Full name: Script.UndoableCommand

--------------------
new : description:string -> UndoableCommand
val description : string
Multiple items
val string : value:'T -> string

Full name: Microsoft.FSharp.Core.Operators.string

--------------------
type string = System.String

Full name: Microsoft.FSharp.Core.string
val this : UndoableCommand
member UndoableCommand.Description : string

Full name: Script.UndoableCommand.Description
abstract member UndoableCommand.Execute : unit -> unit

Full name: Script.UndoableCommand.Execute
type unit = Unit

Full name: Microsoft.FSharp.Core.unit
abstract member UndoableCommand.Undo : unit -> unit

Full name: Script.UndoableCommand.Undo
Multiple items
type PropertyChangedUndoableCommand<'a> =
  inherit UndoableCommand
  new : description:string * fieldRef:'a ref * newValue:'a -> PropertyChangedUndoableCommand<'a>
  override Execute : unit -> unit
  override Undo : unit -> unit

Full name: Script.PropertyChangedUndoableCommand<_>

--------------------
new : description:string * fieldRef:'a ref * newValue:'a -> PropertyChangedUndoableCommand<'a>
val fieldRef : 'a ref
val newValue : 'a
val oldValue : 'a
val this : PropertyChangedUndoableCommand<'a>
override PropertyChangedUndoableCommand.Execute : unit -> unit

Full name: Script.PropertyChangedUndoableCommand`1.Execute
override PropertyChangedUndoableCommand.Undo : unit -> unit

Full name: Script.PropertyChangedUndoableCommand`1.Undo
Multiple items
type DelegateUndoableCommand =
  inherit UndoableCommand
  new : description:string * doAction:(unit -> unit) * undoAction:(unit -> unit) -> DelegateUndoableCommand
  override Execute : unit -> unit
  override Undo : unit -> unit

Full name: Script.DelegateUndoableCommand

--------------------
new : description:string * doAction:(unit -> unit) * undoAction:(unit -> unit) -> DelegateUndoableCommand
val doAction : (unit -> unit)
val undoAction : (unit -> unit)
val this : DelegateUndoableCommand
override DelegateUndoableCommand.Execute : unit -> unit

Full name: Script.DelegateUndoableCommand.Execute
override DelegateUndoableCommand.Undo : unit -> unit

Full name: Script.DelegateUndoableCommand.Undo
Multiple items
type Document =
  new : unit -> Document
  member ExecuteCommand : command:UndoableCommand -> unit
  member Redo : unit -> unit
  member Undo : unit -> unit
  member CanRedo : bool
  member CanUndo : bool

Full name: Script.Document

--------------------
new : unit -> Document
val undoStack : Stack<UndoableCommand>
Multiple items
type Stack<'T> =
  new : unit -> Stack<'T> + 2 overloads
  member Clear : unit -> unit
  member Contains : item:'T -> bool
  member CopyTo : array:'T[] * arrayIndex:int -> unit
  member Count : int
  member GetEnumerator : unit -> Enumerator<'T>
  member Peek : unit -> 'T
  member Pop : unit -> 'T
  member Push : item:'T -> unit
  member ToArray : unit -> 'T[]
  ...
  nested type Enumerator

Full name: System.Collections.Generic.Stack<_>

--------------------
Stack() : unit
Stack(capacity: int) : unit
Stack(collection: IEnumerable<'T>) : unit
val redoStack : Stack<UndoableCommand>
val execute : (UndoableCommand -> unit)
val command : UndoableCommand
Stack.Clear() : unit
Stack.Push(item: UndoableCommand) : unit
abstract member UndoableCommand.Execute : unit -> unit
val undo : (unit -> unit)
property Stack.Count: int
Stack.Pop() : UndoableCommand
abstract member UndoableCommand.Undo : unit -> unit
val redo : (unit -> unit)
val this : Document
member Document.ExecuteCommand : command:UndoableCommand -> unit

Full name: Script.Document.ExecuteCommand
member Document.Undo : unit -> unit

Full name: Script.Document.Undo
member Document.Redo : unit -> unit

Full name: Script.Document.Redo
member Document.CanUndo : bool

Full name: Script.Document.CanUndo
member Document.CanRedo : bool

Full name: Script.Document.CanRedo
Multiple items
type SomeObject =
  new : document:Document -> SomeObject
  member UndoableProperty : int
  member UndoableProperty : int with set

Full name: Script.SomeObject

--------------------
new : document:Document -> SomeObject
val document : Document
val undoableProperty : int ref
Multiple items
val ref : value:'T -> 'T ref

Full name: Microsoft.FSharp.Core.Operators.ref

--------------------
type 'T ref = Ref<'T>

Full name: Microsoft.FSharp.Core.ref<_>
val this : SomeObject
member SomeObject.UndoableProperty : int with set

Full name: Script.SomeObject.UndoableProperty
val set : elements:seq<'T> -> Set<'T> (requires comparison)

Full name: Microsoft.FSharp.Core.ExtraTopLevelOperators.set
val value : int
val command : PropertyChangedUndoableCommand<int>
member Document.ExecuteCommand : command:UndoableCommand -> unit
val doc : Document

Full name: Script.doc
val someObject : SomeObject

Full name: Script.someObject
val printf : format:Printf.TextWriterFormat<'T> -> 'T

Full name: Microsoft.FSharp.Core.ExtraTopLevelOperators.printf
property SomeObject.UndoableProperty: int
member Document.Undo : unit -> unit
member Document.Redo : unit -> unit
type Console =
  static member BackgroundColor : ConsoleColor with get, set
  static member Beep : unit -> unit + 1 overload
  static member BufferHeight : int with get, set
  static member BufferWidth : int with get, set
  static member CapsLock : bool
  static member Clear : unit -> unit
  static member CursorLeft : int with get, set
  static member CursorSize : int with get, set
  static member CursorTop : int with get, set
  static member CursorVisible : bool with get, set
  ...

Full name: System.Console
System.Console.ReadLine() : string
val ignore : value:'T -> unit

Full name: Microsoft.FSharp.Core.Operators.ignore
Next Version Raw view Test code New version

More information

Link:http://fssnip.net/4w
Posted:13 years ago
Author:Neil Danson
Tags: commands