Skip to content

Latest commit

 

History

110 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 

Repository files navigation

EditingSystem

Biaui NuGet package Build status MIT License

Easy to use undo/redo system for .NET

Install

PM> Install-Package Jewelry.EditingSystem

CommunityToolkit.Mvvm integration

Install the integration package when observable properties generated by CommunityToolkit.Mvvm should also participate in the editing history.

PM> Install-Package Jewelry.EditingSystem.CommunityToolkit.Mvvm

Configure the history once on the partial view model and mark each observable property that should be undoable. Field declarations and C# 13 partial property declarations are supported.

using CommunityToolkit.Mvvm.ComponentModel;
using Jewelry.EditingSystem;
using Jewelry.EditingSystem.CommunityToolkit.Mvvm;

[EditingHistory(nameof(_history))]
public sealed partial class SampleViewModel : ObservableObject
{
    private readonly History _history;

    public SampleViewModel(History history)
    {
        _history = history;
    }

    [ObservableProperty]
    [Undoable]
    private string? name;

    [ObservableProperty]
    [Undoable]
    public partial int Count { get; set; }
}

The one-parameter OnXChanging(T value) hook is reserved by the integration. The two-parameter changing hook and both changed hooks remain available to application code.

Example

using Jewelry.EditingSystem;

public class TestModel : EditableModelBase
{
    public TestModel(History history) : base(history)
    {
    }

    #region IntValue

    private int _IntValue;

    public int IntValue
    {
        get => _IntValue;
        set => SetEditableProperty(v => _IntValue = v, _IntValue, value);
    }

    #endregion


    #region IntCollection

    private ObservableCollection<int> _IntCollection = new();

    public ObservableCollection<int> IntCollection
    {
        get => _IntCollection;
        set => SetEditableProperty(v => _IntCollection = v, _IntCollection, value);
    }

    #endregion
}

public void Basic()
{
    using var history = new History();
    var model = new TestModel(history);



    model.IntValue = 123;
    model.IntValue = 456;
    model.IntValue = 789;



    history.Undo();
    Assert.Equal(456, model.IntValue);

    history.Undo();
    Assert.Equal(123, model.IntValue);

    history.Undo();
    Assert.Equal(0, model.IntValue);



    history.Redo();
    Assert.Equal(123, model.IntValue);

    history.Redo();
    Assert.Equal(456, model.IntValue);

    history.Redo();
    Assert.Equal(789, model.IntValue);
}

public void Collection()
{
    using var history = new History();
    var model = new TestModel(history);

    model.IntCollection = new ObservableCollection<int>();



    model.IntCollection.Add(100);
    model.IntCollection.Add(101);
    model.IntCollection.Add(102);
    model.IntCollection.Add(103);



    model.IntCollection.RemoveAt(3);
    Assert.True(model.IntCollection.SequenceEqual(new[] {100, 101, 102}));



    history.Undo();
    Assert.True(model.IntCollection.SequenceEqual(new[] {100, 101, 102, 103}));



    history.Redo();
    Assert.True(model.IntCollection.SequenceEqual(new[] {100, 101, 102}));
}

Support plane INotifyPropertyChanged object

Can be implemented in any way for INotifyPropertyChanged objects.

public sealed class TestModel : INotifyPropertyChanged
{
    private readonly History _history;

    public TestModel(History history)
    {
        _history = history;
    }
    
    #region IntValue

    private int _IntValue;

    public int IntValue
    {
        get => _IntValue;
        set => this.SetEditableProperty(_history, v => SetField(ref _IntValue, v), _IntValue, value);
    }

    #endregion

    #region IntCollection

    private ObservableCollection<int> _IntCollection = new();

    public ObservableCollection<int> IntCollection
    {
        get => _IntCollection;
        set => this.SetEditableProperty(_history, v => SetField(ref _IntCollection, v), _IntCollection, value);
    }

    #endregion


    public event PropertyChangedEventHandler? PropertyChanged;

    private void OnPropertyChanged([CallerMemberName] string? propertyName = null)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }

    private bool SetField<T>(ref T field, T value, [CallerMemberName] string? propertyName = null)
    {
        if (EqualityComparer<T>.Default.Equals(field, value)) return false;
        field = value;
        OnPropertyChanged(propertyName);
        return true;
    }
}

public void Basic()
{
    using var history = new History();
    var model = new TestModel(history);



    model.IntValue = 123;
    model.IntValue = 456;
    model.IntValue = 789;



    history.Undo();
    Assert.Equal(456, model.IntValue);

    history.Undo();
    Assert.Equal(123, model.IntValue);

    history.Undo();
    Assert.Equal(0, model.IntValue);



    history.Redo();
    Assert.Equal(123, model.IntValue);

    history.Redo();
    Assert.Equal(456, model.IntValue);

    history.Redo();
    Assert.Equal(789, model.IntValue);
}

public void Collection()
{
    using var history = new History();
    var model = new TestModel(history);

    model.IntCollection = new ObservableCollection<int>();



    model.IntCollection.Add(100);
    model.IntCollection.Add(101);
    model.IntCollection.Add(102);
    model.IntCollection.Add(103);



    model.IntCollection.RemoveAt(3);
    Assert.True(model.IntCollection.SequenceEqual(new[] {100, 101, 102}));



    history.Undo();
    Assert.True(model.IntCollection.SequenceEqual(new[] {100, 101, 102, 103}));



    history.Redo();
    Assert.True(model.IntCollection.SequenceEqual(new[] {100, 101, 102}));
}

Author

Yoshihiro Ito
Twitter: https://twitter.com/yoiyoi322
Email: yo.i.jewelry.bab@gmail.com

License

MIT

About

Easy to use undo/redo system for .NET Standard.

Topics

Resources

Stars

10 stars

Watchers

1 watching

Forks

Releases

Sponsor this project

Packages

Used by

Contributors

Languages