UndoService 2.3.2

Simple undo/redo service based on the memento pattern. It uses delegates to get and set state. You can use different services to track different parts of the application state. The main advantage of this is that you don't have to store the whole of the application state on every change.

Install-Package UndoService -Version 2.3.2
dotnet add package UndoService --version 2.3.2
<PackageReference Include="UndoService" Version="2.3.2" />
For projects that support PackageReference, copy this XML node into the project file to reference the package.
paket add UndoService --version 2.3.2
The NuGet Team does not provide support for this client. Please contact its maintainers for support.
#r "nuget: UndoService, 2.3.2"
#r directive can be used in F# Interactive, C# scripting and .NET Interactive. Copy this into the interactive tool or source code of the script to reference the package.
// Install UndoService as a Cake Addin
#addin nuget:?package=UndoService&version=2.3.2

// Install UndoService as a Cake Tool
#tool nuget:?package=UndoService&version=2.3.2
The NuGet Team does not provide support for this client. Please contact its maintainers for support.

UndoService

This is a simple undo/redo service based on the memento pattern. It uses delegates to get and set state. You can use different services to track different parts of the application state. The main advantage of this is that you don't have to store the whole of the application state on every change.

Features

  • Multiple undo/redo stacks can be used concurrently, reducing memory imprint.
  • Undo/Redo can be performed interchangeably on the state of the application whole or on specific parts.
  • No requirement to write custom classes or methods as it uses generic stacks and delegate methods.
  • Optional cap on the size of Undo stacks.
  • Recorded states can be tagged. Tag values are present in the arguments of the StateSet event, which is raised on Undo or Redo.

Usage

myString = "my original string";

var undoService = new UndoService<string>(GetStringState, SetStringState, null);

myString = "Something different";

undoService.RecordState();

undoService.Undo();     //myString = "my original string"

The simplest approach is to use a single UndoService for application state. Alternatively you can use separate UndoServices for different sections in conjunction with an UndoServiceAggregate. This means that the whole of the application state does not need to be recorded on each change.

To create an UndoService, pass the delegate methods that are used to get and set the state. To use it, invoke RecordState() after making changes to the state. Note that the initial state is recorded automatically when the UndoService is initialized. Reset() will clear the undo and the redo stack. Use the service's CanUndo and CanRedo properties to enable/disable Undo/Redo commands.

You can use the IsStateChanged flag to keep track of any unsaved changes to your application state (if applicable). The flag is set to true when RecordState() is invoked. ClearIsStateChangedFlag() and Reset() both clear it.

Single UndoService Example


        // We will demonstrate change tracking on this string. 
        private string _statefulString;     
        
        // This is the method to get the state of the tracked object.
        // (If you have an existing method, you can just put it in a wrapper to match the delegate signature.)
        private void GetStringState(out string state)
        {
            state = _statefulString;
        }

        // Method to set the state.
        private void SetStringState(string value)
        {
            _statefulString = value;
        }

        public void UndoRedoTest()
        {
            var undoServiceForString = new UndoService<string>(GetStringState, SetStringState, null);

            _statefulString = "One";
            undoServiceForString.RecordState();
            _statefulString = "Two";
            undoServiceForString.RecordState();

            undoServiceForString.Undo();
            Assert.IsTrue(_statefulString.Equals("One"));

            undoServiceForString.Redo();
            Assert.IsTrue(_statefulString.Equals("Two"));
        }

To create an UndoServiceAggregate, pass it an IUndoService array. To use it, invoke RecordState() in the child UndoServices to record changes. Generally undo and redo would be done via the UndoServiceAggregate. However, you can also do so in the child UndoServices directly to undo the last changes to specific elements.

UndoServiceAggregate Example


        // We will use an UndoService to track changes to this
        private string _statefulString;     

        // We will use a second UndoService to track changes to this.
        private int _statefulInt;

        public void AggregateUndoServiceUndoRedoTest()
        {
            // Create the UndoServiceAggregate by passing an IUndoService array
            var undoServiceForInt = new UndoService<int>(GetIntState, SetIntState, null);
            var undoServiceForString = new UndoService<string>(GetStringState, SetStringState, null);
            IUndoService[] subservices = { undoServiceForInt, undoServiceForString };
            var serviceAggregate = new UndoServiceAggregate(subservices);


           // Use the Undo services to track changes to their associated objects.
            _statefulInt = 1;
            undoServiceForInt.RecordState();
            _statefulString = "One";
            undoServiceForString.RecordState();
            _statefulInt = 2;
            undoServiceForInt.RecordState();
            _statefulInt = 3;
            undoServiceForInt.RecordState();
            _statefulString = "Two";
            undoServiceForString.RecordState();


            // Use the Service Aggregate to undo/redo the most recent changes.
            serviceAggregate.Undo();
            Assert.IsTrue(_statefulString.Equals("One"));
            Assert.IsTrue(_statefulInt == 3);

            serviceAggregate.Undo();
            Assert.IsTrue(_statefulString.Equals("One"));
            Assert.IsTrue(_statefulInt == 2);

            serviceAggregate.Undo();
            Assert.IsTrue(_statefulString.Equals("One"));
            Assert.IsTrue(_statefulInt == 1);

            serviceAggregate.Redo();
            Assert.IsTrue(_statefulString.Equals("One"));
            Assert.IsTrue(_statefulInt == 2);

            serviceAggregate.Redo();
            Assert.IsTrue(_statefulString.Equals("One"));
            Assert.IsTrue(_statefulInt == 3);

            serviceAggregate.Redo();
            Assert.IsTrue(_statefulString.Equals("Two"));
            Assert.IsTrue(_statefulInt == 3);
        }
        
        
        // These are the methods to get/access state, used by the UndoServices above.

        private void GetStringState(out string state)
        {
            state = _statefulString;
        }

        private void SetStringState(string value)
        {
            _statefulString = value;
        }

        private void GetIntState(out int state)
        {
            state = _statefulInt;
        }

        private void SetIntState(int value)
        {
            _statefulInt = value;
        }

Refer to the unit test project in the source repository for examples of other features.

Checklist

If you run into problems, check the following:

  • Make sure you invoke RecordState() after making changes, not before.
  • Make sure you don't invoke RecordState() in the delegate method that sets the state. Otherwise it will be invoked during a Redo() operation and extra states will be added incorrectly.

If you run into problems that aren't resolved by the above, please raise an issue or send an email.

Public Interfaces

  • IStateTracker is used to record changes to state. It is implemented by UndoService.
  • IUndoRedo is used to execute Undo and Redo operations. It is implemented by UndoService and UndoServiceAggregate.
  • IUndoService is used to both record state and perform undo/redo. It is implemented by UndoService.

Links


Copyright 2020 Peter Dongan. Licence: MIT

UndoService

This is a simple undo/redo service based on the memento pattern. It uses delegates to get and set state. You can use different services to track different parts of the application state. The main advantage of this is that you don't have to store the whole of the application state on every change.

Features

  • Multiple undo/redo stacks can be used concurrently, reducing memory imprint.
  • Undo/Redo can be performed interchangeably on the state of the application whole or on specific parts.
  • No requirement to write custom classes or methods as it uses generic stacks and delegate methods.
  • Optional cap on the size of Undo stacks.
  • Recorded states can be tagged. Tag values are present in the arguments of the StateSet event, which is raised on Undo or Redo.

Usage

myString = "my original string";

var undoService = new UndoService<string>(GetStringState, SetStringState, null);

myString = "Something different";

undoService.RecordState();

undoService.Undo();     //myString = "my original string"

The simplest approach is to use a single UndoService for application state. Alternatively you can use separate UndoServices for different sections in conjunction with an UndoServiceAggregate. This means that the whole of the application state does not need to be recorded on each change.

To create an UndoService, pass the delegate methods that are used to get and set the state. To use it, invoke RecordState() after making changes to the state. Note that the initial state is recorded automatically when the UndoService is initialized. Reset() will clear the undo and the redo stack. Use the service's CanUndo and CanRedo properties to enable/disable Undo/Redo commands.

You can use the IsStateChanged flag to keep track of any unsaved changes to your application state (if applicable). The flag is set to true when RecordState() is invoked. ClearIsStateChangedFlag() and Reset() both clear it.

Single UndoService Example


        // We will demonstrate change tracking on this string. 
        private string _statefulString;     
        
        // This is the method to get the state of the tracked object.
        // (If you have an existing method, you can just put it in a wrapper to match the delegate signature.)
        private void GetStringState(out string state)
        {
            state = _statefulString;
        }

        // Method to set the state.
        private void SetStringState(string value)
        {
            _statefulString = value;
        }

        public void UndoRedoTest()
        {
            var undoServiceForString = new UndoService<string>(GetStringState, SetStringState, null);

            _statefulString = "One";
            undoServiceForString.RecordState();
            _statefulString = "Two";
            undoServiceForString.RecordState();

            undoServiceForString.Undo();
            Assert.IsTrue(_statefulString.Equals("One"));

            undoServiceForString.Redo();
            Assert.IsTrue(_statefulString.Equals("Two"));
        }

To create an UndoServiceAggregate, pass it an IUndoService array. To use it, invoke RecordState() in the child UndoServices to record changes. Generally undo and redo would be done via the UndoServiceAggregate. However, you can also do so in the child UndoServices directly to undo the last changes to specific elements.

UndoServiceAggregate Example


        // We will use an UndoService to track changes to this
        private string _statefulString;     

        // We will use a second UndoService to track changes to this.
        private int _statefulInt;

        public void AggregateUndoServiceUndoRedoTest()
        {
            // Create the UndoServiceAggregate by passing an IUndoService array
            var undoServiceForInt = new UndoService<int>(GetIntState, SetIntState, null);
            var undoServiceForString = new UndoService<string>(GetStringState, SetStringState, null);
            IUndoService[] subservices = { undoServiceForInt, undoServiceForString };
            var serviceAggregate = new UndoServiceAggregate(subservices);


           // Use the Undo services to track changes to their associated objects.
            _statefulInt = 1;
            undoServiceForInt.RecordState();
            _statefulString = "One";
            undoServiceForString.RecordState();
            _statefulInt = 2;
            undoServiceForInt.RecordState();
            _statefulInt = 3;
            undoServiceForInt.RecordState();
            _statefulString = "Two";
            undoServiceForString.RecordState();


            // Use the Service Aggregate to undo/redo the most recent changes.
            serviceAggregate.Undo();
            Assert.IsTrue(_statefulString.Equals("One"));
            Assert.IsTrue(_statefulInt == 3);

            serviceAggregate.Undo();
            Assert.IsTrue(_statefulString.Equals("One"));
            Assert.IsTrue(_statefulInt == 2);

            serviceAggregate.Undo();
            Assert.IsTrue(_statefulString.Equals("One"));
            Assert.IsTrue(_statefulInt == 1);

            serviceAggregate.Redo();
            Assert.IsTrue(_statefulString.Equals("One"));
            Assert.IsTrue(_statefulInt == 2);

            serviceAggregate.Redo();
            Assert.IsTrue(_statefulString.Equals("One"));
            Assert.IsTrue(_statefulInt == 3);

            serviceAggregate.Redo();
            Assert.IsTrue(_statefulString.Equals("Two"));
            Assert.IsTrue(_statefulInt == 3);
        }
        
        
        // These are the methods to get/access state, used by the UndoServices above.

        private void GetStringState(out string state)
        {
            state = _statefulString;
        }

        private void SetStringState(string value)
        {
            _statefulString = value;
        }

        private void GetIntState(out int state)
        {
            state = _statefulInt;
        }

        private void SetIntState(int value)
        {
            _statefulInt = value;
        }

Refer to the unit test project in the source repository for examples of other features.

Checklist

If you run into problems, check the following:

  • Make sure you invoke RecordState() after making changes, not before.
  • Make sure you don't invoke RecordState() in the delegate method that sets the state. Otherwise it will be invoked during a Redo() operation and extra states will be added incorrectly.

If you run into problems that aren't resolved by the above, please raise an issue or send an email.

Public Interfaces

  • IStateTracker is used to record changes to state. It is implemented by UndoService.
  • IUndoRedo is used to execute Undo and Redo operations. It is implemented by UndoService and UndoServiceAggregate.
  • IUndoService is used to both record state and perform undo/redo. It is implemented by UndoService.

Links


Copyright 2020 Peter Dongan. Licence: MIT

Release Notes

Changes to readme, description, comments, variable names..

  • .NETStandard 2.0

    • No dependencies.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version History

Version Downloads Last updated
2.3.2 310 11/17/2020
2.3.1 151 7/22/2020
2.3.1-alpha 172 7/21/2020
2.3.0-alpha 169 7/21/2020
2.2.3-alpha 143 7/15/2020
2.2.2-alpha 179 6/22/2020
2.2.0-alpha 158 6/3/2020
2.1.11 177 6/2/2020
2.1.11-alpha 190 5/28/2020
2.1.10-alpha 188 5/28/2020
2.1.9-alpha 153 5/28/2020
2.1.8-alpha 180 5/28/2020
2.1.7-alpha 186 5/27/2020
2.1.6-alpha 166 5/26/2020
2.1.5-alpha 157 5/26/2020
2.1.4-alpha 174 5/26/2020
2.1.3-alpha 184 5/25/2020
2.1.2-alpha 195 5/25/2020
2.0.2 170 5/21/2020
1.1.0 188 3/27/2020
1.0.0 205 3/16/2020