C#中使用单个对象的方法实现Undo/Redo

开发 后端
本文将告诉我们如何在不同的场景下使用这些方法来实现Undo/Redo,并且帮助大家正确理解Undo/Redo。以及如何使用单个对象表示变化。

简介

我们如何在不同的场景下使用这些方法来实现Undo/Redo。这些方法是使用单个对象表示变化,命令模式和备忘录模式。

正如我们所知,Undo/Redo没有通用的解决方案,而Undo/Redo在每个应用程序中非常具体。处于这个原因,在该系列文章的开始部分,将讨论如何使用该方法建模任意的应用程序,然后展示一个简单应用程序的实现。

关于Undo/Redo实现的基本思想

正如我们所知,应用程序在每次操作后改变其状态。当操作应用程序时,它的状态会发生改变。所以,若有人想要做撤销,他不得不回到先前的状态。因此,为了能够回到先前状态,我们需要在应用程序运行时存储它的状态。要支持重做,我们不得不从目前状态跳到下一个状态。

为了实现Undo/Redo,我们不得不存储应用程序的状态并在撤销时跳到前一个状态而在重做时跳到下一个状态。因此我们需要维护应用程序的状态来支持Undo/Redo。在所有三种方法中,应用程序状态的维护用到了两个栈。一个栈包含用于撤销操作的状态,第二个包含用于重做的状态。撤销操作弹出撤销栈以获取前一个状态并将其设置给应用程序。同样的,重做操作弹出重做栈以获取下一个状态并将其设置给应用程序。

现在,我们知道了Undo/Redo的实现操作都是关于保持应用程序每次操作后的状态。现在的问题是该方法如何保存状态。本方法中,单个操作的改变被保存在一个对象中,有些属性为该操作作为状态是多余的,因为这里,单个对象被用于包含所有类型的动作数据。

什么是单个对象表示改变的方法?

首先,我对这是由我命名表示抱歉。这里,单个对象表示了应用程序中所有操作的所有改变。因此,当你准备了一个关于操作更改的类型的对象,在执行一次操作后,你仅使用了该对象属性的子集,而剩余属性仍旧没有被使用。例如,你在一个应用程序中有两个操作;它们是高度的改变和宽度的改变。因此,对象类型包含两个属性:高度和宽度。当你准备了变化对象,在执行高度更改方法后,你仅需设置变化对象的高度字段,而其他字段仍旧没有被使用。

如何应用单个对象表示变化的方法对任意应用程序Undo/Redo操作建模?

单个对象表示变化的方法如何对任意应用程序Undo/Redo操作建模将在以下步骤中讨论:

步骤1

首先识别出你希望哪些操作能支持Undo/Redo。然后,识别出你将在哪个容器下支持Undo/Redo以及你希望哪些对象支持Undo/Redo。

步骤2

为了进一步处理每个Undo/Redo操作,识别出需要被保存的属性。

步骤3
然后创建一个类(ChangeRepresentationObject),它包含支持全部操作Undo/Redo的所有属性。同样,准备一个动作类型enum,它将代表全部操作。这个动作类型enum是ChangeRepresentationObject类的一部分。

步骤4

然后创建一个名为UndoRedo的类,它包含两个类型的ChangeRepresentationObject栈。一个用于撤销操作,一个用于重做操作。该类将实现以下接口:

interface IUndoRedo
{
void Undo(int level);
void Redo(int level);
void InsertObjectforUndoRedo(ChangeRepresentationObject dataobject);
}

步骤5

然后实现具体方法:Undo、 Redo、InsertObjectforUndoRedo。

在每个Undo操作中:

◆首先检查Undo栈是否为空。

◆如果不是,则弹出一个ChangeRepresentationObject并将其压入重做栈。

◆检查动作类型。

◆然后基于动作类型,利用ChangeRepresentationObject的属性完成撤销操作。
在每个Redo操作中,你几乎做与Undo同样的事。

◆首先检查Redo栈是否为空。

◆如果不是,弹出一个ChangeRepresentationObject,然后将其压入撤销栈。

◆检查动作类型。

◆然后基于动作的类型,利用ChangeRepresentationObject属性完成重做操作。
在InsertObjectforUndoRedo操作中,你只要把数据对象插入Undo栈并清空Redo栈中。

步骤6

然后,在完成每次操作前,调用InsertObjectforUndoRedo方法以对所有操作提供Undo/Redo支持。在用户界面上点击Undo时,只需调用UndoRedo类的Undo方法,而在用户界面上点击Redo时,只需调用UndoRedo类的redo方法。

#p#

示例应用程序说明

这个示范WPF绘制应用程序用来作为结合Undo/Redo操作的案例。该WPF应用程序示例支持四种操作:插入对象、删除对象、移动对象和调整对象的尺寸,它还有两种类型的几何对象:矩形和多边形。它使用画布作为包含这些几何对象的容器。

现在,在此系列文章中,我们可以看到如何让这四个操作支持Undo/Redo。在第一部分,使用单个对象表示变化的方法实现。在第二部分,使用命令模式实现而在第三部分,使用备忘录模式实现。

使用单个对象表示变化的方法实现示范应用程序的Undo/Redo

利用单个对象表示变化的方法对示范应用程序Undo/Redo的实现将在以下步骤中讨论

步骤1

我们将识别出那些需要支持Undo/Redo的操作。这里有四个操作支持Undo/Redo。它们是::插入对象、删除对象、移动对象和调整对象的尺寸。我们将对矩形和椭圆支持Undo/Redo,这里的容器是画布。

步骤2

现在我们将识别出那些进一步处理Undo/Redo所需的保存的参数。几何对象移动时其边距改变,因此要支持对象移动的Undo/Redo,要保存边距。当对象改变尺寸时,它的高度、宽度和边距改变。因此为支持对象尺寸调整的Undo/Redo,我们需要保存高度、宽度和边距。为了支持插入和删除的Undo/Redo操作,我们需要保存几何对象的引用。

步骤3

现在我们得到包含边距、高度、宽度、动作类型、几何对象引用的ChangeRepresentationObject以支持所有操作的Undo/Redo。这里的几何对象引用被保存以便我们在对其进行Undo/Redo时获取。同样使动作类型enum代表插入、删除、移动和调整尺寸操作。此动作类型enum被用作ChangeRepresentationObject的一部分。

CollapseCopy Code
public enum ActionType
{
Delete = 0,
Move = 1,
Resize = 2,
Insert = 3
}
CollapseCopy Code
public class ChangeRepresentationObject
{
public ActionType Action;
public Point Margin;
public double Width;
public double height;
public FrameworkElement UiElement;
}

这里,已附上使用单个对象表示变化的方法实现Undo/Redo的项目。

步骤4&5

然后我们将包含两个ChangeRepresentationObject类型的栈的类命名为UndoRedo。一个栈用于撤销操作而另一个用于重做操作。类的代码如下:

CollapseCopy Code
public partial class UnDoRedo : IUndoRedo
{
private Stack _UndoActionsCollection =
 new Stack();
private Stack _RedoActionsCollection =
 new Stack();

#region IUndoRedo Members

public void Undo(int level)
{
for (int i = 1; i <= level; i++)
{
if (_UndoActionsCollection.Count == 0) return;

ChangeRepresentationObject Undostruct = _UndoActionsCollection.Pop();
if (Undostruct.Action == ActionType.Delete)
{
Container.Children.Add(Undostruct.UiElement);
this.RedoPushInUnDoForDelete(Undostruct.UiElement);
}
else if (Undostruct.Action == ActionType.Insert)
{
Container.Children.Remove(Undostruct.UiElement);
this.RedoPushInUnDoForInsert(Undostruct.UiElement);
}
else if (Undostruct.Action == ActionType.Resize)
{
if (_UndoActionsCollection.Count != 0)
{
Point previousMarginOfSelectedObject = new Point
 (((FrameworkElement)Undostruct.UiElement).Margin.Left,
((FrameworkElement)Undostruct.UiElement).Margin.Top);
this.RedoPushInUnDoForResize(previousMarginOfSelectedObject,
 Undostruct.UiElement.Width,
Undostruct.UiElement.Height, Undostruct.UiElement);
Undostruct.UiElement.Margin = new Thickness
 (Undostruct.Margin.X, Undostruct.Margin.Y, 0, 0);
Undostruct.UiElement.Height = Undostruct.height;
Undostruct.UiElement.Width = Undostruct.Width;
}
}
else if (Undostruct.Action == ActionType.Move)
{
Point previousMarginOfSelectedObject = new Point
 (((FrameworkElement)Undostruct.UiElement).Margin.Left,
((FrameworkElement)Undostruct.UiElement).Margin.Top);
this.RedoPushInUnDoForMove(previousMarginOfSelectedObject,
 Undostruct.UiElement);
Undostruct.UiElement.Margin = new Thickness
 (Undostruct.Margin.X, Undostruct.Margin.Y, 0, 0);
}
}
}

public void Redo(int level)
{
for (int i = 1; i <= level; i++)
{
if (_RedoActionsCollection.Count == 0) return;

ChangeRepresentationObject Undostruct = _RedoActionsCollection.Pop();
if (Undostruct.Action == ActionType.Delete)
{
Container.Children.Remove(Undostruct.UiElement);
this.PushInUnDoForDelete(Undostruct.UiElement);
}
else if (Undostruct.Action == ActionType.Insert)
{
Container.Children.Add(Undostruct.UiElement);
this.PushInUnDoForInsert(Undostruct.UiElement);
}
else if (Undostruct.Action == ActionType.Resize)
{
Point previousMarginOfSelectedObject = new Point
 (((FrameworkElement)Undostruct.UiElement).Margin.Left,
((FrameworkElement)Undostruct.UiElement).Margin.Top);
this.PushInUnDoForResize(previousMarginOfSelectedObject,
Undostruct.UiElement.Width,
Undostruct.UiElement.Height, Undostruct.UiElement);
Undostruct.UiElement.Margin = new Thickness
 (Undostruct.Margin.X, Undostruct.Margin.Y, 0, 0);
Undostruct.UiElement.Height = Undostruct.height;
Undostruct.UiElement.Width = Undostruct.Width;
}
else if (Undostruct.Action == ActionType.Move)
{
Point previousMarginOfSelectedObject = new Point
 (((FrameworkElement)Undostruct.UiElement).Margin.Left,
((FrameworkElement)Undostruct.UiElement).Margin.Top);
this.PushInUnDoForMove(previousMarginOfSelectedObject,
 Undostruct.UiElement);
Undostruct.UiElement.Margin = new Thickness
(Undostruct.Margin.X, Undostruct.Margin.Y, 0, 0);
}
}
}
public void InsertObjectforUndoRedo(ChangeRepresentationObject dataobject)
{
_UndoActionsCollection.Push(dataobject);_RedoActionsCollection.Clear();
}
 #endregion

步骤6

在完成每个操作前,调用InsertObjectforUndoRedo方法。当用户界面上Undo被点击,我们调用UndoRedo类的Undo方法,而当用户界面上Redo被点击,我们调用UndoRedo类的redo方法。

这里,我们没有明确设置Undo栈和Redo栈的大小,因此,应用程序能具有的状态数目取决于系统的内存。

使用单个对象表示变化的方法时的变更管理

如果你想要用单个对象表示变化的方法来为新的操作支持Undo/Redo时,你不得不作一些改变。你不得不修改表示变化的对象,动作类型Enum并改变Undo/Redo方法的代码。所以,它的可维护性很低。

使用单个对象表示变化的方法的优缺点

它的优点是实现简单,而不需要知道任何的设计模式,你就可以实现Undo/Redo。

可维护性很低。代表该方法的对象包含很多额外信息,因为这里,单个对象用来容纳所有动作类型的数据。例如,对移动而言,我们只需保存移动相关的数据,而对调整尺寸,我们应该仅保存该操作相关的数据。所以,我们在保存冗余的数据。随着操作数目的增加,冗余也在增加。这并不是好的面向对象的设计。

【编辑推荐】

  1. C#实用基础教程
  2. 如何使用C#代码实现DataTemplate
  3. 详解C# 4.0中必选参数与可选参数混合的问题
责任编辑:彭凡 来源: ITPUB
相关推荐

2024-04-26 08:46:38

WPF开发UndoRedo

2009-04-03 13:20:05

C#扩展方法调用

2021-01-28 05:14:40

C#接口签名

2009-06-18 13:06:59

C#位运算权限管理

2009-08-10 13:34:11

创建C# COM对象

2020-12-31 07:31:10

C# 反射数据

2019-04-23 15:20:26

JavaScript对象前端

2009-08-17 09:50:59

C# ping命令

2009-08-20 14:22:17

C#实现 Contro

2009-08-19 17:00:07

C#实现PrintPa

2009-09-07 09:36:29

C# DisposeDispose方法

2021-03-07 16:37:52

C#应用程序

2021-02-01 12:36:59

C# Channels存储

2013-03-15 10:37:08

C#

2009-03-27 10:10:13

c#远程启动远程管理

2009-08-25 14:26:28

C#播放AVI文件

2021-01-18 05:18:18

C# 8模式C# 7

2021-01-19 05:30:55

C# 8异步流IEnumerable

2009-08-06 11:00:19

C#对象的使用

2024-05-16 12:33:37

C#编程指针
点赞
收藏

51CTO技术栈公众号