Click here to Skip to main content
15,891,689 members
Articles / Desktop Programming / WPF
Tip/Trick

Automatic Undo/Redo

Rate me:
Please Sign up or sign in to vote.
4.75/5 (6 votes)
11 Feb 2015CPOL2 min read 16.6K   15   2
Here is a small library to help you have an automatic undo/redo feature without explicit dependency on Undo/Redo system.

Introduction

If you have already tried to implement your own Undo/Redo pattern or used a third party library, you've probably noticed that you have an explicit dependency with the Undo/Redo service.

Most of the time, the Undo/Redo service is changing a property of a model. What you generally do is tell this service how to modify or revert this property. But it might happen that you forget to use the service to modify the model, modifying the model directly, which might lead to annoying situations (or bugs).

To avoid forgetting the use of the Undo/Redo service, I built a small standalone framework capable of analyzing and recording model changes.

As there is no magic, you still have to implement and respect some rules to make it work, but I tried to concentrate on a system that requires as little code as possible.

Using the Code

Step 1

Create your model and make it implement INotifyPropertyChanged or inherit NotifyPropertyChangedImpl.

Mark each property with attribute [IsRecordable] if you want the property changes to be recorded.

C#
using System.Collections.ObjectModel;
using ElMariachi.WPF.Tools.Modelling;
using ElMariachi.WPF.Tools.Modelling.ModelRecording.Attributes;

namespace MyApplication.Models
{
    public class Person : NotifyPropertyChangedImpl
    {
        private int _age;
        private string _name;
        private ObservableCollection<Person> _children;

        [IsRecordable("Person's name")]
        public string Name
        {
            get { return _name; }
            set
            {
                _name = value;
                NotifyPropertyChanged("Name");
            }
        }

        [IsRecordable("Person's age")]
        public int Age
        {
            get { return _age; }
            set
            {
                _age = value;
                NotifyPropertyChanged("Age");
            }
        }

        [IsRecordable("Person's children")]
        public ObservableCollection<Person> Children
        {
            get { return _children; }
            set
            {
                _children = value;
                NotifyPropertyChanged("Children");
            }
        }
    }
}

Step 2

Create a new model recorder and give it the model to record.

C#
using ElMariachi.WPF.Tools.Modelling.ModelRecording;
using ElMariachi.WPF.Tools.UndoRedo;
using MyApplication.Models;

namespace MyApplication
{
    class App
    {
        static int Main(string[] args)
        {
            var person = new Person
            {
                Age = 39,
                Name = "Emiliano",
            };

            var modelRecorder = new ModelRecorder();
            var undoRedoService = new UndoRedoService();

            modelRecorder.Record(undoRedoService, person);

            person.Age++;
            person.Name = "Emiliano El Mariachi";
            person.Children.Add(new Person());

            undoRedoService.Undo(); //person.Children.Count = 0
            undoRedoService.Undo(); //person.Name = "Emiliano"
            undoRedoService.Undo(); //person.Age = 39

            return 0;
        }
    }
}

Advanced Features

Delayed Recording

What is this? You might have noticed that in text editors, when you write some text and then undo (Ctrl+Z), there is a bit of magic which makes that long written text parts are undone instead of each typed text letter.

Sometime, this behaviour makes me think the computer is reading in my mind :-).

Ok, let's dive into more details... The magic of this feature is based on the principle of a triggerable monoflop (for those who are familiar with electronic). The system is based on a delay specifying the time to wait a stable state of the property.

Let's take a small example, with a delay of 1 second. While you type in the editor faster than 1 second, no text change is recorded, but if the text does not changes after 1 second, then a new text record is done with all the changes that occurred between the first typed letter to the elapsed second.

The Model

C#
using System.Collections.ObjectModel;
using ElMariachi.WPF.Tools.Modelling;
using ElMariachi.WPF.Tools.Modelling.ModelRecording.Attributes;

namespace MyApplication.Models
{  
    public class PersonWithDescription : Person

        private string _description;

        [IsRecordableWithFilter("Person's description", 1000)]
        public string Description
        {
            get { return _description; }
            set
            {
                _description = value;
                NotifyPropertyChanged("Description");
            }
        }
    }
}

Effect

C#
using System.Threading
using ElMariachi.WPF.Tools.Modelling.ModelRecording;
using ElMariachi.WPF.Tools.UndoRedo;
using MyApplication.Models;

namespace MyApplication
{
    class App
    {
        static int Main(string[] args)
        {
            var person = new PersonWithDescription();
            person.Description = "Married";

            var modelRecorder = new ModelRecorder();
            var undoRedoService = new UndoRedoService();

            modelRecorder.Record(undoRedoService, person);

            person.Description = "Married with 0";
            person.Description = "Married with ";
            person.Description = "Married with 1 children";

            Thread.Sleep(1500);

            undoRedoService.Undo(); //person.Description = "Married"

            return 0;
        }
    }
}

Features to Come

  • Record groups (I'll explain in more detail later)

Try it!

Feel free to download, try and/or modify my library from here:

History

I'll try to keep README.md up-to-date at the root of the repository.

Note: The library also contains an "Automatic Dirty Model Detection", I'll create another article to explain this feature in detail later.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior)
France France
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
QuestionI checked out your code on GitHub and Pin
Sacha Barber12-Feb-15 0:39
Sacha Barber12-Feb-15 0:39 
AnswerRe: I checked out your code on GitHub and Pin
Emiliano El Mariachi12-Feb-15 1:08
Emiliano El Mariachi12-Feb-15 1:08 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.