zoukankan      html  css  js  c++  java
  • 有限次数的Undo&Redo的C#实现

    为了实现Undo和Redo,必须要在程序中保存起程序的运行状态,从而能够在Undo时跳转到前一个状态和在Redo时跳转到下一个状态。为了实现状态的维护,我采用了两个栈来分别保存Undo操作的状态和Redo操作的状态。


            public static Stack<MyCommand> undoStack = new Stack<MyCommand>();


            public static Stack<MyCommand> redoStack = new Stack<MyCommand>();



    首先要识别哪些操作可以支持Undo和Redo操作。在我的小程序中,支持的操作主要有几个:textbox的textchanged,textbox和button的焦点,radiobutton、checkbox、combox、listbox选项的改变。

    对于上述操作的实现,必须要实现一个MyCommand接口。


    using System;


    using System.Collections.Generic;


    using System.Linq;

     

    using System.Text;

     

    namespace UndoRedo


    {


        public interface MyCommand

        {

            void execute(); //完成动作

            void undo(); //撤销动作

        }


    }



    每个操作都要继承自这个MyCommand接口,在操作类中包含有实现Undo和Redo操作所需要的属性,并且实现了接口中的execute()和undo()

    创建了一个UndoRedo类,类中包含上面提到的两个栈,一个Undo栈,一个Redo栈。这个类实现了Undo方法和Redo方法,并且还有多个向Undo栈进行压栈的方法。

    Undo方法中:

    检查Undo栈是否为空,不为空,则弹出一个MyCommand对象,把这个对象压入Redo栈中,执行这个对象的undo()方法。

    00001. 

            /// <summary>

    00002. 

    00003. 

            /// 实现Undo操作

    00004. 

    00005. 

            /// </summary>

    00006. 

    00007. 

            /// <param name="times">撤销的次数</param>

    00008. 

    00009. 

            public static void Undo()

    00010. 

    00011. 

            {

    00012. 

    00013. 

                if (undoStack.Count != 0)

    00014. 

    00015. 

                {

    00016. 

    00017. 

                    MyCommand myCommand = undoStack.Pop();

    00018. 

    00019. 

                    myCommand.undo();

    00020. 

    00021. 

                    redoStack.Push(myCommand);

    00022. 

    00023. 

                }

    00024. 

    00025. 

            }

    00026. 

     

    Redo方法中:

    检查Redo栈是否为空,不为空,则弹出一个MyCommand对象,把这个对象压入Undo栈中,执行这个对象的execute()方法。

    00001. 

          /// <summary>

    00002. 

    00003. 

            /// 实现Redo操作

    00004. 

    00005. 

            /// </summary>

    00006. 

    00007. 

            /// <param name="times">撤销的次数</param>

    00008. 

    00009. 

            public static void Redo()

    00010. 

    00011. 

            {

    00012. 

    00013. 

                if (redoStack.Count != 0)

    00014. 

    00015. 

                {

    00016. 

    00017. 

                    MyCommand myCommand = redoStack.Pop();

    00018. 

    00019. 

                    myCommand.execute();

    00020. 

    00021. 

                    undoStack.Push(myCommand);

    00022. 

    00023. 

                }

    00024. 

    00025. 

            }

    00026. 

     

    在向Undo栈进行压栈的方法中:

    MyCommand对象压入Undo栈中,并且将Redo栈清空。在这个方法里需要注意一点的是,我是实现有限次数的Undo和Redo,所以将栈的大小必须控制起来。如果栈中的元素个数小于指定次数,则进行压栈操作;如果栈中元素等于指定次数,则将栈中元素进行了一个处理。我是这样处理的:将栈内的元素用一个list保存起来,并且将除了栈底元素外的其他元素都重新压回栈内,从而实现了栈的元素个数的有限。下面这段代码以textbox的text改变事件作为例子,其他操作类似。

           public static void dealWithUndoStack(MyCommand command)

     

            {


                List<MyCommand> commandList = new List<MyCommand>();


                for (int i = 0; i < undoTimes; i++)


                {


                    MyCommand cmd = undoStack.Pop();


                    commandList.Add(cmd);

     

                }


                for (int j = undoTimes - 2; j >= 0; j--)


                {


                    undoStack.Push(commandList[j]);


                }

            }

            /// <summary>

            /// 字符串的修改

            /// </summary>

            /// <param name="nstr">新字符串</param>

            public static void inStackForText(TextBox tb,string nstr,string ostr)

            {


                MyCommand command = new TextChangeCommand(tb,nstr,ostr);


                if (undoStack.Count < undoTimes)

                    undoStack.Push(command);


                else if (undoStack.Count == undoTimes)


                    dealWithUndoStack(command);

     

                redoStack.Clear();


            }

    在完成了上面的几个步骤后,只需要在执行程序的不同操作的时候将该操作对应的Command类通过与inStackForText类似的方法,将类的对象压入Undo栈即可。当需要执行Undo操作的时候,调用UndoRedo类中的Undo方法;当需要执行Redo操作的时候,调用UndoRedo类中的Redo方法。

    接着就是对于不同的操作,为其生成一个继承MyCommand接口的类即可。下面举个例子,依然是上面提到的textbox的text改变事件。

    using System;


    using System.Collections.Generic;


    using System.Linq;

     

    using System.Text;


    using System.Windows.Forms;

    namespace UndoRedo

    {

        class TextChangeCommand : MyCommand

        { 

            private string newStr;

     

            private string oldStr;


            private TextBox mTextbox;


            public TextChangeCommand(TextBox tb,string ntext,string otext)

            {


                this.newStr = ntext;

     

                this.mTextbox = tb;


                this.oldStr = otext;

            }


            public void execute()

            {

                mTextbox.Text = this.newStr;

            }

     

            public void undo()

            {


                mTextbox.Text = this.oldStr;


            }

        }

    }

    这样的类的实现很简单,只需要将特定某类操作的操作对象和前后状态保存起来,并且实现接口中的方法即可。

    总结一下:这样实现的好处就是不必把所需要用到Undo&Redo操作的控件的状态全保存起来,仅保存那一类操作所需的属性即可,让程序的可扩展性更好。当程序需要实现的功能发生改变的时候,只需要再实现一个继承自MyCommand接口的操作类,在UndoRedo类中为其生成一个压栈操作的方法即可。

     

    为了实现Undo和Redo,必须要在程序中保存起程序的运行状态,从而能够在Undo时跳转到前一个状态和在Redo时跳转到下一个状态。为了实现状态的维护,我采用了两个栈来分别保存Undo操作的状态和Redo操作的状态。

     

    1.  
      public static Stack<MyCommand> undoStack = new Stack<MyCommand>();
    2.  
      public static Stack<MyCommand> redoStack = new Stack<MyCommand>();


    首先要识别哪些操作可以支持Undo和Redo操作。在我的小程序中,支持的操作主要有几个:textbox的textchanged,textbox和button的焦点,radiobutton、checkbox、combox、listbox选项的改变。

     

    对于上述操作的实现,必须要实现一个MyCommand接口。

     

    1.  
      using System;
    2.  
      using System.Collections.Generic;
    3.  
      using System.Linq;
    4.  
      using System.Text;
    5.  
       
    6.  
      namespace UndoRedo
    7.  
      {
    8.  
      public interface MyCommand
    9.  
      {
    10.  
      void execute(); //完成动作
    11.  
      void undo(); //撤销动作
    12.  
      }
    13.  
      }


    每个操作都要继承自这个MyCommand接口,在操作类中包含有实现Undo和Redo操作所需要的属性,并且实现了接口中的execute()和undo()

     

    创建了一个UndoRedo类,类中包含上面提到的两个栈,一个Undo栈,一个Redo栈。这个类实现了Undo方法和Redo方法,并且还有多个向Undo栈进行压栈的方法。

    在Undo方法中:

    检查Undo栈是否为空,不为空,则弹出一个MyCommand对象,把这个对象压入Redo栈中,执行这个对象的undo()方法。

     

    1.  
      /// <summary>
    2.  
      /// 实现Undo操作
    3.  
      /// </summary>
    4.  
      /// <param name="times">撤销的次数</param>
    5.  
      public static void Undo()
    6.  
      {
    7.  
      if (undoStack.Count != 0)
    8.  
      {
    9.  
      MyCommand myCommand = undoStack.Pop();
    10.  
      myCommand.undo();
    11.  
      redoStack.Push(myCommand);
    12.  
      }
    13.  
      }



     

    在Redo方法中:

    检查Redo栈是否为空,不为空,则弹出一个MyCommand对象,把这个对象压入Undo栈中,执行这个对象的execute()方法。

     

    1.  
      /// <summary>
    2.  
      /// 实现Redo操作
    3.  
      /// </summary>
    4.  
      /// <param name="times">撤销的次数</param>
    5.  
      public static void Redo()
    6.  
      {
    7.  
      if (redoStack.Count != 0)
    8.  
      {
    9.  
      MyCommand myCommand = redoStack.Pop();
    10.  
      myCommand.execute();
    11.  
      undoStack.Push(myCommand);
    12.  
      }
    13.  
      }



     

    在向Undo栈进行压栈的方法中:

    将MyCommand对象压入Undo栈中,并且将Redo栈清空。在这个方法里需要注意一点的是,我是实现有限次数的Undo和Redo,所以将栈的大小必须控制起来。如果栈中的元素个数小于指定次数,则进行压栈操作;如果栈中元素等于指定次数,则将栈中元素进行了一个处理。我是这样处理的:将栈内的元素用一个list保存起来,并且将除了栈底元素外的其他元素都重新压回栈内,从而实现了栈的元素个数的有限。下面这段代码以textbox的text改变事件作为例子,其他操作类似。

     

    1.  
      public static void dealWithUndoStack(MyCommand command)
    2.  
      {
    3.  
      List<MyCommand> commandList = new List<MyCommand>();
    4.  
      for (int i = 0; i < undoTimes; i++)
    5.  
      {
    6.  
      MyCommand cmd = undoStack.Pop();
    7.  
      commandList.Add(cmd);
    8.  
      }
    9.  
      for (int j = undoTimes - 2; j >= 0; j--)
    10.  
      {
    11.  
      undoStack.Push(commandList[j]);
    12.  
      }
    13.  
      }
    14.  
       
    15.  
      /// <summary>
    16.  
      /// 字符串的修改
    17.  
      /// </summary>
    18.  
      /// <param name="nstr">新字符串</param>
    19.  
      public static void inStackForText(TextBox tb,string nstr,string ostr)
    20.  
      {
    21.  
      MyCommand command = new TextChangeCommand(tb,nstr,ostr);
    22.  
      if (undoStack.Count < undoTimes)
    23.  
      undoStack.Push(command);
    24.  
      else if (undoStack.Count == undoTimes)
    25.  
      dealWithUndoStack(command);
    26.  
      redoStack.Clear();
    27.  
      }

    在完成了上面的几个步骤后,只需要在执行程序的不同操作的时候将该操作对应的Command类通过与inStackForText类似的方法,将类的对象压入Undo栈即可。当需要执行Undo操作的时候,调用UndoRedo类中的Undo方法;当需要执行Redo操作的时候,调用UndoRedo类中的Redo方法。

     

    接着就是对于不同的操作,为其生成一个继承MyCommand接口的类即可。下面举个例子,依然是上面提到的textbox的text改变事件。

     

    1.  
      using System;
    2.  
      using System.Collections.Generic;
    3.  
      using System.Linq;
    4.  
      using System.Text;
    5.  
      using System.Windows.Forms;
    6.  
       
    7.  
      namespace UndoRedo
    8.  
      {
    9.  
      class TextChangeCommand : MyCommand
    10.  
      {
    11.  
      private string newStr;
    12.  
      private string oldStr;
    13.  
      private TextBox mTextbox;
    14.  
       
    15.  
      public TextChangeCommand(TextBox tb,string ntext,string otext)
    16.  
      {
    17.  
      this.newStr = ntext;
    18.  
      this.mTextbox = tb;
    19.  
      this.oldStr = otext;
    20.  
      }
    21.  
       
    22.  
      public void execute()
    23.  
      {
    24.  
      mTextbox.Text = this.newStr;
    25.  
      }
    26.  
       
    27.  
      public void undo()
    28.  
      {
    29.  
      mTextbox.Text = this.oldStr;
    30.  
      }
    31.  
      }
    32.  
      }


    这样的类的实现很简单,只需要将特定某类操作的操作对象和前后状态保存起来,并且实现接口中的方法即可。

     

    总结一下:这样实现的好处就是不必把所需要用到Undo&Redo操作的控件的状态全保存起来,仅保存那一类操作所需的属性即可,让程序的可扩展性更好。当程序需要实现的功能发生改变的时候,只需要再实现一个继承自MyCommand接口的操作类,在UndoRedo类中为其生成一个压栈操作的方法即可。

  • 相关阅读:
    流处理 —— Spark Streaming中的Window操作
    Spring框架参考手册(4.2.6版本)翻译——第三部分 核心技术 6.10.8 提供带注解的限定符元数据
    Spring框架参考手册(4.2.6版本)翻译——第三部分 核心技术 6.10.7 为自动检测组件提供作用域
    Spring框架参考手册(4.2.6版本)翻译——第三部分 核心技术 6.10.6 给自动检测组件命名
    Spring框架参考手册(4.2.6版本)翻译——第三部分 核心技术 6.10.5 在组件中定义bean的元数据
    Spring框架参考手册(4.2.6版本)翻译——第三部分 核心技术 6.10.4 使用过滤器自定义扫描
    Spring框架参考手册(4.2.6版本)翻译——第三部分 核心技术 6.10.3 自动检测类和注册bean的定义
    Spring框架参考手册(4.2.6版本)翻译——第三部分 核心技术 6.10.2 元注解
    Spring框架参考手册(4.2.6版本)翻译——第三部分 核心技术 6.10.1 @Component和深层的构造型注解
    Spring框架参考手册(4.2.6版本)翻译——第三部分 核心技术 6.10 类路径扫描和被管理的组件
  • 原文地址:https://www.cnblogs.com/bruce1992/p/14227729.html
Copyright © 2011-2022 走看看