撤销重作功能实现

前言

大佬走过,小菜留下。html

该文讲述我如何把撤销重作功能作到让我本身满意。编程

这篇随笔起于公司项目须要一个撤销重写功能,由于是图形设计。数组

第一想法

起初第一想法是保存整个操做对象,而后撤销就从新换整个对象就ok了。在群里讨论的时候也只是说这种方式,可能隐藏大佬没出现多线程

这种方法大佬群里直接丢出一个demo,我以为挺好的,若是是小的对象的话,这样作彻底没问题,下面我给出大佬的代码架构

public interface IUndoable<T>
    {
        bool CanRedo { get; }
        bool CanUndo { get; }
        T Value { get; set; }
        void SaveState();
        void Undo();
        void Redo();
    }
internal interface IUndoState<T>
    {
        T State { get; }
    }
public class Undoable<T> : IUndoable<T>
    {
        Stack<IUndoState<T>> _redoStack;
        Stack<IUndoState<T>> _undoStack;
        T _value;

        public Undoable(T value)
        {
            _value = value;
            _redoStack = new Stack<IUndoState<T>>();
            _undoStack = new Stack<IUndoState<T>>();
        }

        public T Value
        {
            get { return _value; }
            set { _value = value; }
        }

        public bool CanRedo
        {
            get { return _redoStack.Count != 0; }
        }

        public bool CanUndo
        {
            get { return _undoStack.Count != 0; }
        }

        public void SaveState()
        {
            _redoStack.Clear();
            _undoStack.Push(GenerateUndoState());
        }

        public void Undo()
        {
            if (_undoStack.Count == 0) throw new InvalidOperationException("Undo history exhausted");

            _redoStack.Push(GenerateUndoState());
            _value = _undoStack.Pop().State;
        }

        private UndoState<T> GenerateUndoState()
        {
            return new UndoState<T>(Value);
        }

        public void Redo()
        {
            if (_redoStack.Count == 0) throw new InvalidOperationException("Redo history exhausted");

            _undoStack.Push(GenerateUndoState());
            _value = _redoStack.Pop().State;
        }
    }
internal class UndoState<T> : IUndoState<T>
    {
        BinaryFormatter _formatter;
        byte[] _stateData;

        internal UndoState(T state)
        {
            _formatter = new BinaryFormatter();
            using (MemoryStream stream = new MemoryStream())
            {
                _formatter.Serialize(stream, state);
                _stateData = stream.ToArray();
            }
        }

        public T State
        {
            get
            {
                using (MemoryStream stream = new MemoryStream(_stateData))
                {
                    return (T)_formatter.Deserialize(stream);
                }
            }
        }
    }
class Program
    {
        static void Main(string[] args)
        {
            IUndoable<string> stuff = new Undoable<string>("State One");
            stuff.SaveState();
            stuff.Value = "State Two";
            stuff.SaveState();
            stuff.Value = "State Three";

            stuff.Undo();   // State Two
            stuff.Undo();   // State One
            stuff.Redo();   // State Two
            stuff.Redo();   // State Three
        }
    }

上面是大佬的所有代码,使用字节流来记录整个对象,撤销和重写就是把整个对象建立一遍,这种能够用到一些状况。框架

可是不适用个人项目中,由于每一次更改一点东西就须要把整个对象记下来,并且wpf项目中以前绑定的都会失效,由于不是原来的对象了。函数

初版本

既然是撤销重写,应该只需记录下改变的东西,其余不须要记录,因此我须要两个栈,一个记录历史栈(撤销),一个重作栈,和压入栈的数据类。post

数据类以下:学习

public class UnRedoInfo
    {
        /// <summary>
        /// 插入的对象
        /// </summary>
        public object Item { get; set; }
        /// <summary>
        /// 记录对象更改的属性和属性值
        /// </summary>
        public Dictionary<string, object> PropValueDry { get; set; }
    }
Item是更改的属性所属的对象,PropValueDry是key:属性名,value:属性值

撤销重作功能类以下
public class UnRedoHelp
    {
        //撤销和重作栈。
        public static Stack<UnRedoInfo> UndoStack;
        public static Stack<UnRedoInfo> RedoStack;

        static UnRedoHelp()
        {
            UndoStack = new Stack<UnRedoInfo>();
            RedoStack = new Stack<UnRedoInfo>();
        }
        //添加撤销命令
        /// <summary>
        /// 添加撤销命令
        /// </summary>
        /// <param name="item"></param>
        /// <param name="propValueDry"></param>
        public static void Add(object item, Dictionary<string, object> propValueDry)
        {
            UnRedoInfo info = new UnRedoInfo();
            info.Item = item;
            info.PropValueDry = propValueDry;
            //将命令参数压到栈顶。
            UndoStack.Push(info);
        }

        /// <summary>
        /// 添加撤销命令
        /// </summary>
        /// <param name="item"></param>
        /// <param name="propNames">记录的属性名更改数组</param>
        public static void Add(object item, params string[] propNames)
        {
            UnRedoInfo info = new UnRedoInfo();
            info.Item = item;

            //添加属性和属性值
            Dictionary<string, object> propValueDry = new Dictionary<string, object>();
            for (int i = 0; i < propNames.Length; i++)
            {
                var obj = GetPropertyValue(item, propNames[i]);
                if (!propValueDry.ContainsKey(propNames[i]))
                {
                    propValueDry.Add(propNames[i],obj);
                }
            }

            info.PropValueDry = propValueDry;
            //将命令参数压到栈顶。
            UndoStack.Push(info);
        }

        /// <summary>
        /// 撤销
        /// </summary>
        public static void Undo()
        {
            if (UndoStack.Count == 0)
            {
                return;
            }

            UnRedoInfo info = UndoStack.Pop();
            //设置属性值
            foreach (var item in info.PropValueDry)
            {
                SetPropertyValue(info.Item,item.Key,item.Value);
            }
            //将撤销的命令从新压到重作栈顶,重作时可恢复。
            RedoStack.Push(info);
        }
        /// <summary>
        /// 重作
        /// </summary>
        public static void Redo()
        {
            if (RedoStack.Count == 0)
            {
                return;
            }

            UnRedoInfo info = RedoStack.Pop();
            //设置属性值
            foreach (var item in info.PropValueDry)
            {
                SetPropertyValue(info.Item, item.Key, item.Value);
            }
            //将撤销的命令从新压到重作栈顶,重作时可恢复。
            UndoStack.Push(info);
        }
        /// <summary>
        /// 获取属性值
        /// </summary>
        /// <param name="obj"></param>
        /// <param name="name"></param>
        /// <returns></returns>
        public static object GetPropertyValue(object obj, string name)
        {
            PropertyInfo property = obj.GetType().GetProperty(name);
            if (property != null)
            {
                object drv1 = property.GetValue(obj, null);
                return drv1;
            }
            else
            {
                return null;
            }
        }
        /// <summary>
        /// 设置属性值
        /// </summary>
        /// <param name="obj"></param>
        /// <param name="name"></param>
        /// <param name="value"></param>
        public static void SetPropertyValue(object obj, string name,object value)
        {
            PropertyInfo property = obj.GetType().GetProperty(name);
            if (property != null)
            {
                property.SetValue(obj, value);
            }
        }
    }

上面用了反射获取属性的值和设置属性,这个功能类逻辑是有问题,由于我那时候心思没在哪方面,是我写到了最后面才发现的,并在新版里改正了,可是这个版本并无 ,测试

并且这个代码是我后面版本撤回来才有的代码,不保证没有任何错误。

若是你也正好须要这样的功能,那为什么不往下再看看呢

我觉得上面的能够解决个人问题,然并卵,若是属性是集合,那根本就没用,由于栈的数据对象保存的属性值是对象,就是外面添加减小,栈里的也会改变。因此有了下一个版本

第二版本

我想用字节流来保存属性的值,因此

public class UnRedoHelp
    {
        static UnRedoHelp()
        {
            UndoStack = new Stack<UnRedoInfo>();
            RedoStack = new Stack<UnRedoInfo>();
            _formatter = new BinaryFormatter();
        }
        //撤销和重作栈。
        public static Stack<UnRedoInfo> UndoStack;
        public static Stack<UnRedoInfo> RedoStack;
        static BinaryFormatter _formatter;
        //添加撤销命令
        /// <summary>
        /// 添加撤销命令
        /// </summary>
        /// <param name="item"></param>
        /// <param name="propValueDry"></param>
        public static void Add(object item, Dictionary<string, object> propValueDry)
        {
            UnRedoInfo info = new UnRedoInfo();
            info.Item = item;
            info.PropValueDry = propValueDry;
            //将命令参数压到栈顶。
            UndoStack.Push(info);
        }

        /// <summary>
        /// 添加撤销命令
        /// </summary>
        /// <param name="item"></param>
        /// <param name="propNames">记录的属性名更改数组</param>
        public static void Add(object item, params string[] propNames)
        {
            UnRedoInfo info = new UnRedoInfo();
            info.Item = item;

            //添加属性和属性值
            Dictionary<string, object> propValueDry = new Dictionary<string, object>();
            for (int i = 0; i < propNames.Length; i++)
            {

                if (!propValueDry.ContainsKey(propNames[i]))
                {
                    var obj = GetPropertyValue(item, propNames[i]);
                    //将属性值,序列化成字节流
                    using (MemoryStream stream = new MemoryStream())
                    {
                        _formatter.Serialize(stream, obj);
                        var bt = stream.ToArray();
                        propValueDry.Add(propNames[i], bt);
                    }

                }
            }

            info.PropValueDry = propValueDry;
            //将命令参数压到栈顶。
            UndoStack.Push(info);
        }

        /// <summary>
        /// 撤销
        /// </summary>
        public static void Undo()
        {
            if (UndoStack.Count == 0)
            {
                return;
            }

            UnRedoInfo info = UndoStack.Pop();
            //设置属性值
            foreach (var item in info.PropValueDry)
            {
                object value = GetPropBytes(item.Value);
                SetPropertyValue(info.Item, item.Key, value);
            }
            //将撤销的命令从新压到重作栈顶,重作时可恢复。
            RedoStack.Push(info);
        }
        /// <summary>
        /// 重作
        /// </summary>
        public static void Redo()
        {
            if (RedoStack.Count == 0)
            {
                return;
            }

            UnRedoInfo info = RedoStack.Pop();
            //设置属性值
            foreach (var item in info.PropValueDry)
            {
                object value = GetPropBytes(item.Value);
                SetPropertyValue(info.Item, item.Key, value);
            }
            //将撤销的命令从新压到重作栈顶,重作时可恢复。
            UndoStack.Push(info);
        }
        /// <summary>
        /// 转换字节流获取属性的值
        /// </summary>
        /// <param name="value"></param>
        /// <returns></returns>
        private static object GetPropBytes(object value)
        {
            var bts = (byte[])value;
            using (MemoryStream stream = new MemoryStream(bts))
            {
                return _formatter.Deserialize(stream);
            }
        }

        /// <summary>
        /// 获取属性值
        /// </summary>
        /// <param name="obj"></param>
        /// <param name="name"></param>
        /// <returns></returns>
        public static object GetPropertyValue(object obj, string name)
        {
            PropertyInfo property = obj.GetType().GetProperty(name);
            if (property != null)
            {
                object drv1 = property.GetValue(obj, null);
                return drv1;
            }
            else
            {
                return null;
            }
        }
        /// <summary>
        /// 设置属性值
        /// </summary>
        /// <param name="obj"></param>
        /// <param name="name"></param>
        /// <param name="value"></param>
        public static void SetPropertyValue(object obj, string name, object value)
        {
            PropertyInfo property = obj.GetType().GetProperty(name);

            if (property != null)
            {
                property.SetValue(obj, value);
            }
        }
    }

上面这个版本很快就被我否认了,它是和大佬的字节流保存相结合的产儿,为何用字节流保存属性值不行呢,

举个栗子,如今保存的是列表子项对象的属性,而后再保存列表,撤销,列表回到原先的值,可是里面的子项对象已经不是原来的对象,虽然值都同样,再撤销,是反应不到列表里的子项的。

船新版本

对第一个版本进行改造,由于属性要么是对象,要么就是对象集合,什么,你说int不是对象(你当我什么都没说)

咱们须要记录属性更详细的信息

保存属性的类型(对象仍是集合)

/// <summary>
    /// 保存属性的类型(对象,集合)
    /// </summary>
    public enum PropInfoType
    {
        /// <summary>
        /// 单个对象属性
        /// </summary>
        Object,
        /// <summary>
        /// 列表属性
        /// </summary>
        IList
    }
/// <summary>
    /// 撤销重作的属性信息
    /// </summary>
    public class PropInfo
    {
        /// <summary>
        /// 属性类型
        /// </summary>
        public PropInfoType InfoType { get; set; }
        /// <summary>
        /// 单对象属性的值
        /// </summary>
        public object PropValue { get; set; }
        /// <summary>
        /// 列表对象属性的值,记录当前列表属性的全部子项
        /// </summary>
        public List<object> PropValueLst { get; set; }
        /// <summary>
        /// 属性名称
        /// </summary>
        public string PropName { get; set; }
    }
/// <summary>
    /// 撤销重作信息
    /// </summary>
    public class UnRedoInfo
    {
        /// <summary>
        /// 插入的对象
        /// </summary>
        public object Item { get; set; }
        
        /// <summary>
        /// 记录对象更改的多个属性和属性值
        /// </summary>
        public Dictionary<string, PropInfo> PropValueDry { get; set; }
    }

这三个类连着看,根据注释,应该没什么问题,不要问我为何key已是属性名了,为何PropInfo中还有?

public class UnRedoHelp
    {
        static UnRedoHelp()
        {
            UndoStack = new Stack<UnRedoInfo>();
            RedoStack = new Stack<UnRedoInfo>();
        }
        //撤销和重作栈。
        public static Stack<UnRedoInfo> UndoStack;
        public static Stack<UnRedoInfo> RedoStack;

        /// <summary>
        /// 说明功能是否在撤销或者重作,true正在进行操做
        /// </summary>
        public static bool IsUnRedo = false;
        //添加撤销命令
        /// <summary>
        /// 添加撤销命令
        /// </summary>
        /// <param name="item"></param>
        /// <param name="propValueDry"></param>
        public static void Add(object item, Dictionary<string, PropInfo> propValueDry)
        {
            if (IsUnRedo) return;
            UnRedoInfo info = new UnRedoInfo();
            info.Item = item;
            info.PropValueDry = propValueDry;
            //将命令参数压到栈顶。
            UndoStack.Push(info);
        }

        /// <summary>
        /// 添加撤销命令,普通对象属性
        /// </summary>
        /// <param name="item"></param>
        /// <param name="propNames">记录的属性名更改数组</param>
        public static void Add(object item, params string[] propNames)
        {
            if (IsUnRedo) return;

            if (RedoStack.Count != 0)
            {
                //添加要把重作清空
                RedoStack.Clear();
            }
            UnRedoInfo info = GetPropertyValue(item,propNames);
            //将命令参数压到栈顶。
            UndoStack.Push(info);
        }


        /// <summary>
        /// 撤销
        /// </summary>
        public static void Undo()
        {
            if (UndoStack.Count == 0)
            {
                return;
            }
            IsUnRedo = true;
            try
            {
                UnRedoInfo info = UndoStack.Pop();
                //先压到重作栈,再改变值 重作时可恢复
                UnRedoInfo oldinfo = GetPropertyValue(info.Item, info.PropValueDry.Keys.ToArray());
                RedoStack.Push(oldinfo);

                SetPropertyValue(info);
            }
            catch (Exception e)
            {
                Console.WriteLine(e);
            }
            finally
            {
                IsUnRedo = false;
            }
            
            
        }
        /// <summary>
        /// 重作
        /// </summary>
        public static void Redo()
        {
            if (RedoStack.Count == 0)
            {
                return;
            }
            IsUnRedo = true;
            try
            {
                UnRedoInfo info = RedoStack.Pop();
                //先压到撤销栈,再改变值 撤销时可恢复
                UnRedoInfo oldinfo = GetPropertyValue(info.Item, info.PropValueDry.Keys.ToArray());
                UndoStack.Push(oldinfo);
                //设置属性值
                SetPropertyValue(info);
            }
            catch (Exception e)
            {
                Console.WriteLine(e);
            }
            finally
            {
                IsUnRedo = false;
            }

        }
        /// <summary>
        /// 获取属性值
        /// </summary>
        /// <param name="obj"></param>
        /// <param name="name"></param>
        /// <returns></returns>
        public static UnRedoInfo GetPropertyValue(object obj, string[] propNames)
        {
            UnRedoInfo info = new UnRedoInfo();
            info.Item = obj;

            //添加属性和属性值
            Dictionary<string, PropInfo> propValueDry = new Dictionary<string, PropInfo>();
            for (int i = 0; i < propNames.Length; i++)
            {
                //对象属性名
                string name = propNames[i];
                //获取属性相关信息
                PropertyInfo property = obj.GetType().GetProperty(name);

                if (property != null)
                {
                    #region 设置撤销重作的属性信息
                    //设置撤销重作的属性信息
                    PropInfo propInfo = new PropInfo();
                    propInfo.PropName = name;
                    //获取属性值
                    var prop = property.GetValue(obj);
                    if (prop is System.Collections.IList)
                    {
                        //列表
                        propInfo.InfoType = PropInfoType.IList;
                        propInfo.PropValueLst = new List<object>();
                        var lst = (IList)prop;
                        foreach (var item in lst)
                        {
                            propInfo.PropValueLst.Add(item);
                        }

                    }
                    else
                    {
                        //不是列表,单个对象
                        propInfo.InfoType = PropInfoType.Object;
                        propInfo.PropValue = prop;
                    }

                    if (!propValueDry.ContainsKey(propNames[i]))
                    {
                        propValueDry.Add(propNames[i], propInfo);
                    }
                    #endregion
                }

            }
            //设置对象
            info.PropValueDry = propValueDry;
            return info;
        }
        /// <summary>
        /// 设置属性值
        /// </summary>
        /// <param name="obj"></param>
        /// <param name="name"></param>
        /// <param name="value"></param>
        public static void SetPropertyValue(UnRedoInfo info)
        {
            //设置属性值
            foreach (var item in info.PropValueDry)
            {
                PropertyInfo property = info.Item.GetType().GetProperty(item.Key);

                if (property != null)
                {
                    if (item.Value.InfoType == PropInfoType.Object)
                    {
                        //单个对象值的,直接赋值
                        property.SetValue(info.Item, item.Value.PropValue);
                    }
                    else if (item.Value.InfoType == PropInfoType.IList)
                    {
                        //列表对象值,先清除该列表对象的子项,而后从新添加子项
                        var lst = (IList)property.GetValue(info.Item);
                        lst.Clear();
                        foreach (var x in item.Value.PropValueLst)
                        {
                            lst.Add(x);
                        }
                    }
                }
            }
            
        }
    }
}

上面这个是船新版本,测试过,符合个人要求,下面是main函数里的使用代码,栗子

static void Main(string[] args)
        {
            TestC testC = new TestC();
            TestD testD = new TestD();
            testD.W = 5;

            testC.TestD = testD;
            testC.Name = "name1";
            testC.Count = 2;

            testC.TestDs = new List<TestD>();
            for (int i = 0; i < 3; i++)
            {
                testC.TestDs.Add(new TestD() { W = i });
            }
            //添加历史记录 须要记录的属性名
            UnRedoHelp.Add(testC, nameof(testC.Name), nameof(testC.Count), nameof(testC.TestDs));

            testC.TestDs[0].W = -2;

            UnRedoHelp.Add(testC.TestDs[0], nameof(testD.W));

            for (int i = 0; i < 3; i++)
            {
                testC.TestDs.Add(new TestD() { W = i + 3 });
            }

            testC.Name = "name2";
            testC.Count = 3;
            testC.TestDs[0].W = -9;

            UnRedoHelp.Add(testC, nameof(testC.Name), nameof(testC.Count), nameof(testC.TestDs));

            testC.Name = "name3";
            testC.Count = 4;

            for (int i = 0; i < 3; i++)
            {
                testC.TestDs.Add(new TestD() { W = i + 6 });
            }
            UnRedoHelp.Undo();
            UnRedoHelp.Undo();
            UnRedoHelp.Redo();
            UnRedoHelp.Redo();
        }

好了,这样总该能够了叭,什么?还不行?你不想写一堆的UnRedoHelp.Add(testC, nameof(testC.Name), nameof(testC.Count), nameof(testC.TestDs));

一群乌鸦飞过。。。。怎么办?

AOP,之前有看过一点这东西,因此脑子有这个印象,不过一直没用过。

因此说这么个小小的功能,一点点代码,我几年累计下来的知识彻底不够用。下面是资料广告时间:

利用C#实现AOP常见的几种方法详解

【原创】颠覆C#王权的“魔比斯环” — 实现AOP框架的终极利器(这个让我很兴奋)

使用 RealProxy 类进行面向方面的编程

推荐个很是简单好用的AOP -- MrAdvice

C#语法——反射,架构师的入门基础(很差意思,打扰了)

上面就是我找的,以为有用的,能够学到点东西的资料,第一个资料我试了两个就是第二三种方式,而后我以为很差用,而后群里推荐了

(大佬:AOP框架-动态代理和IL
微软企业库的PIAB Postsharp
Mr.Advice castle dynamicproxy sheepAspect PropertyChanged.Fody

大佬:你去找找,我用的是Mr.Advice)

嗯,大佬让我用Mr.Advice,而后我找了第四个资料,确实符合个人需求。

安装Mr.Advice,写UnRedoAttribute

[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property | AttributeTargets.Method)]
    public class UnRedoAttribute :  Attribute, IMethodAdvice
    {
        public void Advise(MethodAdviceContext context)
        {
            //必须是属性改变,并且不是由于撤销重作引发的
            if (context.TargetName.Length > 4 && context.TargetName.StartsWith("set_") )
            {
                //属性改变
                //添加历史记录 须要记录的属性名 去掉get_和set_
                string prop = context.TargetName.Remove(0, 4);
                UnRedoHelp.Add(context.Target, prop);
            }
            //Console.WriteLine("test");
            // do things you want here
            context.Proceed(); // this calls the original method
            // do other things here
        }
    }

测试,使用

public interface ITest
    {
    }

public class TestC:ITest
    {
        public virtual string CName { get; set; }
        [UnRedo]
        public virtual string Name { get; set; }
        [UnRedo]
        public virtual int Count { get; set; }
        [UnRedo]
        public virtual TestD TestD { get; set; }
        [UnRedo]
        public virtual List<TestD> TestDs { get;set; }
    }
    [Serializable]
    public class TestD
    {
        [UnRedo]
        public int W { get; set; }
    }
static void Main(string[] args)
        {
            //ProxyGenerator generator = new ProxyGenerator();

            //var testC = generator.CreateClassProxy<TestC>(new TestInterceptor());
            //TestC testC = (TestC)RepositoryFactory.Create();
            TestC testC = new TestC();
            TestD testD = new TestD();
            testD.W = 5;

            testC.TestD = testD;
            testC.Name = "name1";
            testC.Count = 2;
            UnRedoHelp.Undo();
            UnRedoHelp.Undo();
            UnRedoHelp.Undo();
            UnRedoHelp.Undo();

            testC.TestDs = new List<TestD>();
            for (int i = 0; i < 3; i++)
            {
                testC.TestDs.Add(new TestD() { W = i });
            }
           
            testC.TestDs[0].W = -2;

            for (int i = 0; i < 3; i++)
            {
                testC.TestDs.Add(new TestD() { W = i + 3 });
            }

            testC.Name = "name2";
            testC.Count = 3;
            testC.TestDs[0].W = -9;

            testC.Name = "name3";
            testC.Count = 4;

            for (int i = 0; i < 3; i++)
            {
                testC.TestDs.Add(new TestD() { W = i + 6 });
            }
            UnRedoHelp.Undo();
            UnRedoHelp.Undo();
            UnRedoHelp.Redo();
            UnRedoHelp.Redo();
            Console.ReadKey();
        }

这终因而实现我想要的效果,只须要在撤销的属性加UnRedo特征就好了,如今回头看也就那么回事,捣鼓一天就弄了个这么点东西、

补充注意:

使用MrAdvice过程当中,全部须要用到UnRedoAttribute的项目都要引用MrAdvice,否则拦截无效,我已经标红了,不要看成看不见哈

 再次补充:

船新版本之最新版本

其实上面的还不是最新版本,后面需求要保存操做,这个怎么办?

第一步确定得先改保存到栈里的数据,不要管怎么其余,数据最重要

保存到栈的数据类型,加入了保存方法

/// <summary>
    /// 撤销重作信息
    /// </summary>
    public class UnRedoInfo
    {
        /// <summary>
        /// 插入的对象
        /// </summary>
        public object Target { get; set; }
        /// <summary>
        /// 命令集合,key:命令名
        /// </summary>
        public Dictionary<string, CmdInfo> CmdDry { get; set; }
        /// <summary>
        /// 信息类型
        /// </summary>
        public UnRedoInfoType InfoType { get; set; } = UnRedoInfoType.Prop;
        /// <summary>
        /// 记录对象更改的多个属性和属性值
        /// </summary>
        public Dictionary<string, PropInfo> PropValueDry { get; set; }
    }

    #region 对象
    /// <summary>
    /// 撤销重作的属性信息
    /// </summary>
    public class PropInfo
    {
        /// <summary>
        /// 属性类型
        /// </summary>
        public PropInfoType InfoType { get; set; }
        /// <summary>
        /// 单对象属性的值
        /// </summary>
        public object PropValue { get; set; }
        /// <summary>
        /// 列表对象属性的值,记录当前列表属性的全部子项
        /// </summary>
        public List<object> PropValueLst { get; set; }
        /// <summary>
        /// 属性名称
        /// </summary>
        public string PropName { get; set; }
    }
    /// <summary>
    /// 保存属性的类型(对象,集合)
    /// </summary>
    public enum PropInfoType
    {
        /// <summary>
        /// 单个对象属性
        /// </summary>
        Object,
        /// <summary>
        /// 列表属性
        /// </summary>
        IList
    }
    #endregion

    #region 命令
    /// <summary>
    /// 撤销重作的属性信息
    /// </summary>
    public class CmdInfo
    {
        /// <summary>
        /// 命令名称
        /// </summary>
        public string Name { get; set; }
        /// <summary>
        /// 相反命令名称
        /// </summary>
        public string UnName { get; set; }
        /// <summary>
        /// 命令的参数列表
        /// </summary>
        public object[] Paras { get; set; }
    }

    /// <summary>
    /// 保存属性的类型(对象,集合)
    /// </summary>
    public enum UnRedoInfoType
    {
        /// <summary>
        /// 对象属性
        /// </summary>
        Prop,
        /// <summary>
        /// 命令
        /// </summary>
        Cmd
    }
    #endregion

不就是填加了一个分辨方法和属性的类型吗?不就是记录方法的集合,确实简单,这个方法集合意思就是说你能够传入多个方法进来,其实用一个就够了,由于你能够把多方法写成一个方法传进来。

那下面就看看怎么执行保存方法,撤销和重作了。

public class UnRedoHelp
    {
        static UnRedoHelp()
        {
            UndoStack = new Stack<UnRedoInfo>();
            RedoStack = new Stack<UnRedoInfo>();
        }
        //撤销和重作栈。
        public static Stack<UnRedoInfo> UndoStack;
        public static Stack<UnRedoInfo> RedoStack;

        /// <summary>
        /// 说明功能是否在撤销或者重作,true正在进行操做
        /// </summary>
        public static bool IsUnRedo = false;
        /// <summary>
        /// 把重作栈清空
        /// </summary>
        private static void RedoStackClear()
        {
            if (RedoStack.Count != 0)
            {
                //添加要把重作清空
                RedoStack.Clear();
            }
        }

        //添加撤销命令
        /// <summary>
        /// 添加撤销命令
        /// </summary>
        /// <param name="target"></param>
        /// <param name="propValueDry"></param>
        public static void Add(object target, Dictionary<string, PropInfo> propValueDry)
        {
            if (IsUnRedo) return;

            RedoStackClear();
            UnRedoInfo info = new UnRedoInfo();
            info.Target = target;
            info.PropValueDry = propValueDry;
            //将命令参数压到栈顶。
            UndoStack.Push(info);
        }

        /// <summary>
        /// 添加撤销命令,普通对象属性
        /// </summary>
        /// <param name="target"></param>
        /// <param name="propNames">记录的属性名更改数组</param>
        public static void Add(object target, params string[] propNames)
        {
            if (IsUnRedo) return;

            RedoStackClear();
            UnRedoInfo info = GetPropertyValue(target, propNames);
            //将命令参数压到栈顶。
            UndoStack.Push(info);
        }

        /// <summary>
        /// 添加撤销命令,消息命令
        /// </summary>
        /// <param name="target"></param>
        /// <param name="cmd">命令名</param>
        /// <param name="cmdDry">命令参数,key:方法名</param>
        public static void AddCmd(object target,  Dictionary<string, CmdInfo> cmdDry)
        {
            if (IsUnRedo) return;

            RedoStackClear();
            UnRedoInfo info = GetCmd(target, cmdDry);
            //将命令参数压到栈顶。
            UndoStack.Push(info);
        }

        /// <summary>
        /// 添加撤销命令,消息命令
        /// </summary>
        /// <param name="target"></param>
        /// <param name="cmd">命令名</param>
        /// <param name="uncmd">反命令名</param>
        /// <param name="paras">命令参数</param>
        public static void AddCmd(object target, string cmd, string uncmd, params object[] paras)
        {
            if (IsUnRedo) return;

            RedoStackClear();
            //获取命令的基本信息
            Dictionary<string, CmdInfo> cmdDry = new Dictionary<string, CmdInfo>();
            CmdInfo cmdInfo = new CmdInfo();
            cmdInfo.Name = cmd;//方法名
            cmdInfo.UnName = uncmd;//反方法
            cmdInfo.Paras = paras;//参数
            cmdDry.Add(cmd,cmdInfo);

            UnRedoInfo info = GetCmd(target, cmdDry);

            //将命令参数压到栈顶。
            UndoStack.Push(info);
        }


        /// <summary>
        /// 撤销
        /// </summary>
        public static void Undo()
        {
            if (UndoStack.Count == 0)
            {
                return;
            }
            IsUnRedo = true;
            try
            {
                UnRedoInfo info = UndoStack.Pop();
                UnRedoInfo oldinfo;
                if (info.InfoType == UnRedoInfoType.Prop)
                {
                    //先压到重作栈,再改变值 重作时可恢复
                    oldinfo = GetPropertyValue(info.Target, info.PropValueDry.Keys.ToArray());
                    RedoStack.Push(oldinfo);
                    //使用栈数据进行属性赋值
                    SetPropertyValue(info);
                }
                else
                {
                    //命令
                    oldinfo = GetCmd(info.Target,info.CmdDry,true);
                    RedoStack.Push(oldinfo);

                    //使用栈数据进行执行命令
                    SetCmd(info);
                }
            }
            catch (Exception e)
            {
                Console.WriteLine(e);
                //LogHelp.WriteLog(e.Message + e.StackTrace);
            }
            finally
            {
                IsUnRedo = false;
            }


        }
        /// <summary>
        /// 重作
        /// </summary>
        public static void Redo()
        {
            if (RedoStack.Count == 0)
            {
                return;
            }
            IsUnRedo = true;
            try
            {
                UnRedoInfo info = RedoStack.Pop();
                UnRedoInfo oldinfo;
                if (info.InfoType == UnRedoInfoType.Prop)
                {
                    //先压到撤销栈,再改变值 撤销时可恢复
                    oldinfo = GetPropertyValue(info.Target, info.PropValueDry.Keys.ToArray());
                    UndoStack.Push(oldinfo);
                    //设置属性值
                    SetPropertyValue(info);
                }
                else
                {
                    //命令
                    oldinfo = GetCmd(info.Target, info.CmdDry, true);
                    UndoStack.Push(oldinfo);

                    SetCmd(info);
                }
            }
            catch (Exception e)
            {
                Console.WriteLine(e);
                //LogHelp.WriteLog(e.Message + e.StackTrace);
            }
            finally
            {
                IsUnRedo = false;
            }

        }

        #region 命令
        /// <summary>
        /// 获取关于命令数据的栈数据
        /// </summary>
        /// <param name="target"></param>
        /// <param name="cmd"></param>
        /// <param name="uncmd"></param>
        /// <param name="paras"></param>
        /// <param name="isUn">true是获取相反的命令</param>
        /// <returns></returns>
        public static UnRedoInfo GetCmd(object target, Dictionary<string, CmdInfo> cmdDry,bool isUn = false)
        {
            UnRedoInfo info = new UnRedoInfo();
            info.InfoType = UnRedoInfoType.Cmd;
            info.Target = target;
            Dictionary<string, CmdInfo> cmdDryTemp = new Dictionary<string, CmdInfo>();
            if (isUn)
            {
                //命令颠倒,因此两个正反方法的参数是同样的
                foreach (var x in cmdDry)
                {
                    CmdInfo cmdInfo = new CmdInfo();
                    cmdInfo.Name = x.Value.UnName;
                    cmdInfo.UnName = x.Value.Name;
                    cmdInfo.Paras = x.Value.Paras;
                    cmdDryTemp.Add(x.Value.UnName, cmdInfo);
                }
                info.CmdDry = cmdDryTemp;
            }
            else
            {
                info.CmdDry = cmdDry;
            }

            return info;
        }
        /// <summary>
        /// 执行命令
        /// </summary>
        /// <param name="info"></param>
        public static void SetCmd(UnRedoInfo info)
        {
            foreach (var x in info.CmdDry)
            {
                //获取方法信息 执行反命令
                MethodInfo methodInfo = info.Target.GetType().GetMethod(x.Value.UnName);
                methodInfo?.Invoke(info.Target,x.Value.Paras);
            }
        }
        #endregion

        #region 属性
        /// <summary>
        /// 获取属性值
        /// </summary>
        /// <param name="obj"></param>
        /// <param name="name"></param>
        /// <returns></returns>
        public static UnRedoInfo GetPropertyValue(object obj, string[] propNames)
        {
            UnRedoInfo info = new UnRedoInfo();
            info.Target = obj;

            //添加属性和属性值
            Dictionary<string, PropInfo> propValueDry = new Dictionary<string, PropInfo>();
            for (int i = 0; i < propNames.Length; i++)
            {
                //对象属性名
                string name = propNames[i];
                //获取属性相关信息
                PropertyInfo property = obj.GetType().GetProperty(name);

                if (property != null)
                {
                    #region 设置撤销重作的属性信息
                    //设置撤销重作的属性信息
                    PropInfo propInfo = new PropInfo();
                    propInfo.PropName = name;
                    //获取属性值
                    var prop = property.GetValue(obj);
                    if (prop is System.Collections.IList)
                    {
                        //列表
                        propInfo.InfoType = PropInfoType.IList;
                        propInfo.PropValueLst = new List<object>();
                        var lst = (IList)prop;
                        foreach (var item in lst)
                        {
                            propInfo.PropValueLst.Add(item);
                        }

                    }
                    else
                    {
                        //不是列表,单个对象
                        propInfo.InfoType = PropInfoType.Object;
                        propInfo.PropValue = prop;
                    }

                    if (!propValueDry.ContainsKey(propNames[i]))
                    {
                        propValueDry.Add(propNames[i], propInfo);
                    }
                    #endregion
                }

            }
            //设置对象
            info.PropValueDry = propValueDry;
            return info;
        }
        /// <summary>
        /// 设置属性值
        /// </summary>
        /// <param name="obj"></param>
        /// <param name="name"></param>
        /// <param name="value"></param>
        public static void SetPropertyValue(UnRedoInfo info)
        {
            //设置属性值
            foreach (var item in info.PropValueDry)
            {
                PropertyInfo property = info.Target.GetType().GetProperty(item.Key);

                if (property != null)
                {
                    if (item.Value.InfoType == PropInfoType.Object)
                    {
                        //单个对象值的,直接赋值
                        property.SetValue(info.Target, item.Value.PropValue);
                    }
                    else if (item.Value.InfoType == PropInfoType.IList)
                    {
                        //列表对象值,先清除该列表对象的子项,而后从新添加子项
                        var lst = (IList)property.GetValue(info.Target);
                        lst.Clear();
                        foreach (var x in item.Value.PropValueLst)
                        {
                            lst.Add(x);
                        }
                    }
                }
            }

        }
        #endregion
    }

这里的IsUnRedo要注意一下,它是为了防止,在撤销重作Undo和Redo操做的里面改变一个属性,或者调用方法的时候也“添加进栈”,这是咱们不肯意看到的。

其实若是以前的代码你整明白了,加一个保存方法,是一点问题都没有的。由于属性和方法分开处理的。

下面给出我使用撤销重作配合AOP使用须要注意的地方

/// <summary>
    /// 这个类须要建立与使用的特征的类型必须引用MrAdvice,否则不起做用
    /// </summary>
    [Serializable]
    //[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property | AttributeTargets.Method)]
    [AttributeUsage(AttributeTargets.All | AttributeTargets.Method, AllowMultiple = true)]
    public class UnRedoAttribute : Attribute, IMethodAdvice
    {
        /// <summary>
        /// 撤销重作类型
        /// </summary>
        public UnRedoInfoType UnRedoInfoType { get; set; } = UnRedoInfoType.Prop;
        /// <summary>
        /// 反命令,当撤销重作类型是命令类型可用 须要撤销的命令必须拥有正反方法的所有参数,而且互相赋值 并且参数对象是外面建立的
        /// 反正就是说我知道参数名称,可是不知道参数,因此要用到前面的方法参数,故正反用的参数是相同的
        /// </summary>
        public string UnCmd { get; set; }
        /// <summary>
        /// 命令是否在执行,true执行,
        /// 防止cmd中执行cmd或者修改撤销重作特征的属性而后添加进栈
        /// </summary>
        private static bool IsCmdRun { get; set; } = false;
        /// <summary>
        /// 切面方法
        /// </summary>
        /// <param name="context"></param>
        public void Advise(MethodAdviceContext context)
        {
            //若是有正在进行则返回,因此多线程不能记录
            MemberInfo memberInfo = context.TargetMethod;
            var unRedoAtr = memberInfo.GetCustomAttribute(typeof(UnRedoAttribute)) as UnRedoAttribute;
            //若是有正在进行则不记录,因此多线程不能记录
            if (!IsCmdRun)
            {
                //必须是属性改变
                if (context.TargetName.Length > 4 && context.TargetName.StartsWith("set_"))
                {
                    //属性改变
                    //添加历史记录 须要记录的属性名 去掉set_
                    string prop = context.TargetName.Remove(0, 4);
                    UnRedoHelp.Add(context.Target, prop);
                }
                else if (!context.TargetName.StartsWith("get_") && unRedoAtr.UnRedoInfoType == UnRedoInfoType.Cmd)
                {
                    UnRedoHelp.AddCmd(context.Target, context.TargetName, unRedoAtr.UnCmd, context.Arguments.ToArray());
                }
            }

            // do things you want here
            //执行方法,只对属性设置的设置控制,IsCmdRun = true,属性或者命令中修改涉及的撤消重作都不会生效
            if (!IsCmdRun && ((context.TargetName.Length > 4 && context.TargetName.StartsWith("set_"))
                || (unRedoAtr != null && unRedoAtr.UnRedoInfoType == UnRedoInfoType.Cmd)))
            {
                IsCmdRun = true;
                context.Proceed(); // this calls the original method
                IsCmdRun = false;
            }
            else
            {
                context.Proceed();
            }

            // do other things here
        }
    }

上面特性类是专门给这个撤销重作写的,IsCmdRun这里也注意一下就是防止执行的方法里重复执行撤销重作特性的方法,改变一个属性的同时去修改另外一个属性,或者执行一个撤销重写操做时候去修改属性或者调用操做的时候“添加进栈”

说那么多,简单说,IsCmdRun防止不少没必要要的栈数据添加进来。

IsCmdRun和IsUnRedo做用是不同的:

IsUnRedo是针对Redo和Undo方法里面的调用方法或者修改属性会重复再次记录,因此限制;

IsCmdRun是针对执行方法自己里执行另外一个拥有撤销重作特性的方法或者修改属性,因此限制;

使用例子

public class TestC:ITest
    {
        public  string CName { get; set; }
        [UnRedo]
        public  string Name { get; set; }
        [UnRedo]
        public  int Count { get; set; }
        [UnRedo]
        public  TestD TestD { get; set; }
        [UnRedo]
        public  List<TestD> TestDs { get;set; }

        #region 无参数
        [UnRedo(UnCmd = nameof(UnCmd), UnRedoInfoType = UnRedoInfoType.Cmd)]
        public void Cmd()
        {
            Console.WriteLine("testCmd");
        }

        [UnRedo(UnCmd = nameof(Cmd), UnRedoInfoType = UnRedoInfoType.Cmd)]
        public void UnCmd()
        {
            Console.WriteLine("testUnCmd");
        }
        #endregion

        #region 简单参数
        [UnRedo(UnCmd = nameof(ParaCmd2), UnRedoInfoType = UnRedoInfoType.Cmd)]
        public void ParaCmd(int temp)
        {
            Console.WriteLine($"testCmd:{temp}");
        }

        [UnRedo(UnCmd = nameof(ParaCmd), UnRedoInfoType = UnRedoInfoType.Cmd)]
        public void ParaCmd2(int temp)
        {
            Console.WriteLine($"testCmd2:{temp}");
        }
        #endregion
    }

加入无参和有参数的简单例子

static void Main(string[] args)
        {
            TestC testC = new TestC();
            TestD testD = new TestD();
            testD.W = 5;

            testC.TestD = testD;
            testC.Name = "name1";
            testC.Count = 2;
            UnRedoHelp.Undo();
            UnRedoHelp.Undo();
            UnRedoHelp.Undo();
            UnRedoHelp.Undo();

            testC.Cmd();
            testC.ParaCmd(2);
            UnRedoHelp.Undo();
            UnRedoHelp.Undo();

            testC.TestDs = new List<TestD>();
            for (int i = 0; i < 3; i++)
            {
                testC.TestDs.Add(new TestD() { W = i });
            }

            testC.TestDs[0].W = -2;

            for (int i = 0; i < 3; i++)
            {
                testC.TestDs.Add(new TestD() { W = i + 3 });
            }

            testC.Name = "name2";
            testC.Count = 3;
            testC.TestDs[0].W = -9;

            testC.Name = "name3";
            testC.Count = 4;

            for (int i = 0; i < 3; i++)
            {
                testC.TestDs.Add(new TestD() { W = i + 6 });
            }
            UnRedoHelp.Undo();
            UnRedoHelp.Undo();
            UnRedoHelp.Redo();
            UnRedoHelp.Redo();
            Console.ReadKey();
        }

效果图

 

 

是否须要再次升级

其实这里有个争议(我跟本身吵),是否须要把这个撤销重作进一步升级。

里面的栈数据,重写,撤销,相关的方法不写死了,让使用者的继承,本身写本身的栈数据和具体怎么处理。

若是大家用起来有情绪,能够抽象接口,反正我用起来没情绪(你本身写的)

反正源码和思路都在这里了,大家还有更好的想法,也搞起来,优化以后,评论通知我一下,灰常感谢。告辞。。

 

我只有一个要求:

看到这里的道友,就不要翻我以前的随笔了大部分都是我在网上转发或者抄的。

你为何这么作,还这么多(我想要找的方便呀)

你能够收藏呀(他删了怎么办)

之前还觉得能够学习,但除了第一次看,后面几乎没看过。

那为何不删(懒)

 

并且我弄了一个底部加版权提示以后,以前的全部随笔都给我加上了,方了呀。

太难了。

相关文章
相关标签/搜索