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

有限次数的Undo&Redo的C#实现的更多相关文章

  1. MySQL,MariaDB:Undo | Redo [转]

    本文是介绍MySQL数据库InnoDB存储引擎重做日志漫游 00 – Undo LogUndo Log 是为了实现事务的原子性,在MySQL数据库InnoDB存储引擎中,还用Undo Log来实现多版 ...

  2. iOS: 为画板App增加 Undo/Redo(撤销/重做)操作

    这个随笔的内容以上一个随笔为基础,(在iOS中实现一个简单的画板),上一个随笔实现了一个简单的画板:   今天我们要为这个画板增加Undo/Redo操作,当画错了一笔,可以撤销它,或者撤销之后后悔了, ...

  3. [转]MySQL日志——Undo | Redo

    本文是介绍MySQL数据库InnoDB存储引擎重做日志漫游 00 – Undo LogUndo Log 是为了实现事务的原子性,在MySQL数据库InnoDB存储引擎中,还用Undo Log来实现多版 ...

  4. 从Undo,Redo谈命令模式

    一般的应用软件中,通常会提供Redo和Undo的操作,比如Paint.NET中的动作面板,Word中的撤销重做,一般我们按Ctrl-Z即可回退到上次操作. 要实现上面的这一功能,最直观的想法就是,我们 ...

  5. Undo/Redo for Qt Tree Model

    Undo/Redo for Qt Tree Model eryar@163.com Abstract. Qt contains a set of item view classes that use ...

  6. 【转载】MySQL 日志 undo | redo

    本文是介绍MySQL数据库InnoDB存储引擎重做日志漫游 00 – Undo LogUndo Log 是为了实现事务的原子性,在MySQL数据库InnoDB存储引擎中,还用Undo Log来实现多版 ...

  7. MySQL InnoDB存储引擎undo redo解析

    本文介绍MySQL数据库InnoDB存储引擎重做日志漫游 00 – Undo Log Undo Log 为了实现事务原子,在MySQL数据库InnoDB存储引擎,还使用Undo Log(简称:MVCC ...

  8. MySQL日志Undo&Redo

    00 – Undo LogUndo Log 是为了实现事务的原子性,在MySQL数据库InnoDB存储引擎中,还用Undo Log来实现多版本并发控制(简称:MVCC). - 事务的原子性(Atomi ...

  9. MySql Undo Redo

    Undo LogUndo Log 是为了实现事务的原子性,在MySQL数据库InnoDB存储引擎中,还用Undo Log来实现多版本并发控制(简称:MVCC). - 事务的原子性(Atomicity) ...

随机推荐

  1. python 并行

    网址:http://www.parallelpython.com/ 下载模块[根据使用环境选择相应的下载版本]下载pp-1.6.4.4 .zip,注意不要下载md5 解压缩上面下载的文件:pp-1.6 ...

  2. navicat for sqlserver 注册过程

    1.安装原软件,不要打开软件2.将Navicat_Keygen_Patch_v3.4_By_DFoX_URET复制到软件安装位置,运行3.选择navicat v12 products:SQL Serv ...

  3. [刘阳Java]_酷炫视频播放器制作_界面篇

    今天开始分享一篇酷炫播放器制作,包括界面+JS.整个案例非常类似腾讯视频,优酷视频,爱奇艺视频.我们先看一下效果图,然后这篇文章主要界面篇 是不是效果比较酷炫,那么我接着来给大家说一下这个界面设计思路 ...

  4. Spark—编译Spark源码

    Spark版本:Spark-2.1.0 Hadoop版本:hadooop-2.6.0-cdh5.7.0 官方文档:http://spark.apache.org/docs/latest/buildin ...

  5. kafka可视化工具

    Kafka可视化客户端工具(Kafka Tool 2)的安装和使用 Kafka Tool 2 是一款 Kafka 的可视化客户端工具,可以非常方便的查看 Topic 的队列信息.消费者信息以及 kaf ...

  6. Cesium局部区域精细瓦片数据下载技巧

    当Cesium加载局部的目标地区(如中国某个市)的0-18层或更高层数据时,当缩小到zoom较小时可能地球有部分区域(如南半球或左半球)无瓦片覆盖. 为使得整个地球有瓦片覆盖,可利用以下技巧下载瓦片: ...

  7. windows上安装python3里没有pip问题,解决办法!

    安装python3: 1.下载python:https://www.python.org/downloads/ 2.安装:   3.安装完成进cmd里验证,但发现无pip包 所以操作步骤如下: 1.下 ...

  8. Jenkins远程命令执行漏洞(CVE-2018-1000861)

    此漏洞没有回显,直接利用orange的exp执行命令反弹shell 工具地址https://github.com/orangetw/awesome-jenkins-rce-2019 web服务器下写1 ...

  9. 使用 GLFW 在 OpenGL 的场景中漫游

    前言 前面已经建立了 OpenGL 框架,加载了 3D 模型,但是还没有在场景中漫游的功能.为了展示 3D 模型,我只是简单地利用变换视图矩阵的方式使模型在视野中旋转.同时,之前的程序连最简单的改变窗 ...

  10. 腾讯云分布式数据库TDSQL在银行传统核心系统中的应用实践

    本文是腾讯云TDSQL首席架构师张文在腾讯云Techo开发者大会现场的演讲实录,演讲主题是<TDSQL在银行传统核心系统中的应用实践>. 我是TDSQL架构师张文,同时也是TDSQL的开发 ...