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类中为其生成一个压栈操作的方法即可。

  • 相关阅读:
    Java中的final关键字
    使用chrome浏览器调试时的搜索技巧
    查看“文件路径”&“在此处打开命令窗口”
    python安装 错误 “User installations are disabled via policy on the machine”
    Charles 激活入口以及账号密码
    大数据学习(一) | 初识 Hadoop
    Django基础3-数据库交互
    Django基础2
    Django基础使用1
    创建-Django创建启动
  • 原文地址:https://www.cnblogs.com/bruce1992/p/14227729.html
Copyright © 2011-2022 走看看