概述
本文是从英文的网站翻译,用作自己的整理和记录,有修改调整。水平有限,欢迎指正。原文地址:地址
一、引言
这是关于用C#编写多级撤销和重做实现的系列文章的第二部分。本系列展示了针对同一问题的三种方法的撤销/重做实现,以及我们如何使用这些方法实现不同场景的撤销/恢复。这些方法使用单对象状态变化、命令模式和备忘录模式。
- 单对象状态变化(不推荐)
- 命令模式(推荐)
- 备忘录模式(推荐)
正如我们所知道的,撤销/重做没有通用的解决方案,撤销/重做的实现对于每个应用程序都具有定制化部分。因此,本系列文章的每个部分首先讨论了该模式下的设计思路,然后在实例程序中展示了如何实现。该系列,撤销/重做操作使用三种不同的设计方式,因此您可以将每个模式实现与其他方法实现进行比较,并选择最适合您需求的方法。在每个部分文章中我们还讨论了每种方法的优缺点。
二、Undo/Redo实现的基本思路
我们知道,应用程序在每次操作后都会改变其状态。当应用程序操作运行时,它会改变其状态。因此,如果我们想Undo撤销,我们必须回到以前的状态。因此,为了能够恢复到以前的状态,我们需要存储应用程序运行时的状态。为了支持Redo重做,我们必须从当前状态转到下一个状态。
要实现Undo/Redo,我们必须存储应用程序的状态,并且必须转到上一状态进行撤销,转至下一状态进行重做。因此,我们必须维护应用程序的状态以支持Undo/Redo。为了在三种设计方法中维护应用程序的状态,我们使用了两个堆栈。一个堆栈包含撤消操作的状态,第二个堆栈包含重做操作的状态。撤消操作弹出撤消堆栈以获取以前的状态,并将以前的状态设置为应用程序。同样,重做操作弹出重做堆栈以获得下一个状态,并为应用程序设置下一状态。
现在我们知道,实现撤销-重做操作就是在应用程序的每个操作之后保持状态。现在的问题是,这种方法如何保持状态?在命令模式中,我们将单个操作的更改保留在ICommand对象中,该对象用于作为状态的特定操作类型。
大家可以有兴趣的可以去看看设计模式中的命令模式
三、命令模式通用思路
在以下步骤中讨论了如何使用命令模式设计思路:
3.1 第一步
首先确定要支持 Undo/Redo 的操作。然后,确定在哪个容器中支持撤消/重做,以及要支持撤消/恢复的对象。
3.2 第二步
为每个标识的操作创建从ICommand继承的command类。每个command 类将包含一个需要支持 Undo/Redo 属性。ICommand接口如下所示:
interface ICommand
{
void Execute();
void UnExecute();
}
在Execute()方法中,您将使用命令的属性执行操作。在Unexecuted()方法中,您将使用命令的属性执行撤消操作。在这里,命令的属性包含相应命令进行撤消/重做操作所需的更改,以及更改对象的引用。
特别注意:如果同一操作在不同对象上的行为不同,则必须为此操作发出多个命令。这是每个对象类型的操作命令。
3.3 第三步
然后创建一个名为UndoRedo的类,该类包含两个堆栈。第一个用于撤消操作,第二个用于重做操作。此类实现了Undo方法、Redo方法和许多InsertInUnDoRedo方法,以在Undo/Redo系统中插入ICommand对象。调用InsertInUnDoRedo方法时,将ICommand对象插入到撤消堆栈中,以使操作撤消/重做启用并清除重做堆栈。
3.5.1 在每次撤消操作中:
- 首先,您将检查撤消Undo堆栈是否为空。如果为空,则返回,否则继续。
- 如果不是,则pop 从
UndoStack 中
弹出ICommand
。 - 然后把这个 command push 到
RedoStack
。 - 然后,调用
ICommand类中的Unexecute方法
。
3.5.2在每个重做操作中:
- 首先,您将检查
RedoStack
堆栈是否为空。如果为空,则返回,否则继续。 - 如果不是,则从
RedoStack
弹出Icommand
- 把
Icommand
push到Undostack
。 - 然后,调用
Icommand类中的execute方法
。
3.4 第四步
当您在应用程序中执行不同的操作时,创建这个操作类型的Command类,
并将其push撤消/重做系统。当您需要执行undo 撤消操作时,只需从应用程序中调用UndoRedo类的undo方法,当您需要进行重做操作时,则只需从您的应用程序调用redo
重做操作。
四、程序实现
下面讨论了使用命令模式示例应用程序的撤消/重做实现:
5.1 第一步
示例应用程序中有四个操作,分别是移动、调整大小、插入和删除。对象是矩形、椭圆,容器是WPF画布。
5.2 第二步
现在我们将为继承ICommand接口的四个操作中的每一个创建四个命令类。
5.2.1 MoveCommand
class MoveCommand : ICommand
{
private Thickness _ChangeOfMargin;
private FrameworkElement _UiElement;
public MoveCommand(Thickness margin, FrameworkElement uiElement)
{
_ChangeOfMargin = margin;
_UiElement = uiElement;
}
#region ICommand Members
public void Execute()
{
_UiElement.Margin = new Thickness(_UiElement.Margin.Left +
_ChangeOfMargin.Left, _UiElement.Margin.Top
+ _ChangeOfMargin.Top, _UiElement.Margin.Right +
_ChangeOfMargin.Right, _UiElement.Margin.Bottom +
_ChangeOfMargin.Bottom);
}
public void UnExecute()
{
_UiElement.Margin = new Thickness(_UiElement.Margin.Left -
_ChangeOfMargin.Left, _UiElement.Margin.Top -
_ChangeOfMargin.Top, _UiElement.Margin.Right -
_ChangeOfMargin.Right, _UiElement.Margin.Bottom -
_ChangeOfMargin.Bottom);
}
#endregion
}
由于移动操作仅更改几何对象的边距,因此移动命令将包含边距的更改,即几何对象参考。在move命令中,Execute方法通过添加margin更改来完成对_UiElement几何对象的更改,而unexecutes方法通过减去已应用的更改来撤消操作。为此,它从几何对象UIelement中减去边缘变化。
5.2.2 ResizeCommand
class ResizeCommand : ICommand
{
private Thickness _ChangeOfMargin;
private double _ChangeofWidth;
private double _Changeofheight;
private FrameworkElement _UiElement;
public ResizeCommand(Thickness margin, double width,
double height, FrameworkElement uiElement)
{
_ChangeOfMargin = margin;
_ChangeofWidth = width;
_Changeofheight = height;
_UiElement = uiElement;
}
#region ICommand Members
public void Execute()
{
_UiElement.Height = _UiElement.Height + _Changeofheight;
_UiElement.Width = _UiElement.Width + _ChangeofWidth;
_UiElement.Margin = new Thickness
(_UiElement.Margin.Left + _ChangeOfMargin.Left,
_UiElement.Margin.Top
+ _ChangeOfMargin.Top, _UiElement.Margin.Right +
_ChangeOfMargin.Right, _UiElement.Margin.Bottom +
_ChangeOfMargin.Bottom);
}
public void UnExecute()
{
_UiElement.Height = _UiElement.Height - _Changeofheight;
_UiElement.Width = _UiElement.Width - _ChangeofWidth;
_UiElement.Margin = new Thickness(_UiElement.Margin.Left -
_ChangeOfMargin.Left, _UiElement.Margin.Top -
_ChangeOfMargin.Top, _UiElement.Margin.Right -
_ChangeOfMargin.Right, _UiElement.Margin.Bottom -
_ChangeOfMargin.Bottom);
}
#endregion
}
“调整大小”操作更改几何对象的边距、高度和宽度,因此“调整大小”命令可保存边距的更改、高度的更改、宽度的更改以及几何对象的引用。在resize命令中,Execute方法通过添加边距变化、高度变化和宽度变化来完成对_UiElement几何对象的更改。未执行方法通过减去已应用的更改来撤消操作。为此,它从几何对象_UIelement中减去边距变化、高度变化和宽度变化。
5.2.3 InsertCommand
class InsertCommand : ICommand
{
private FrameworkElement _UiElement;
private Canvas _Container;
public InsertCommand(FrameworkElement uiElement, Canvas container)
{
_UiElement = uiElement;
_Container = container;
}
#region ICommand Members
public void Execute()
{
if (!_Container.Children.Contains(_UiElement))
{
_Container.Children.Add(_UiElement);
}
}
public void UnExecute()
{
_Container.Children.Remove(_UiElement);
}
#endregion
}
插入操作是在面板中插入几何对象,插入命令保存几何对象和画布的引用。在insert命令中,Execute方法将几何对象添加到画布,Unexecute方法从画布中删除几何对象。
5.2.4 DeleteCommand
class DeleteCommand : ICommand
{
private FrameworkElement _UiElement;
private Canvas _Container;
public DeleteCommand(FrameworkElement uiElement, Canvas container)
{
_UiElement = uiElement;
_Container = container;
}
#region ICommand Members
public void Execute()
{
_Container.Children.Remove(_UiElement);
}
public void UnExecute()
{
_Container.Children.Add(_UiElement);
}
#endregion
}
当删除操作从面板中删除几何对象时,delete命令保存几何对象和画布的引用。在delete命令中,Execute方法从画布中删除几何对象,而Unexecute方法将几何对象添加到画布中。
5.3 第三步
现在我们将根据通用模型的描述实现UndoRedo类。
public class UnDoRedo
{
private Stack<ICommand> _Undocommands = new Stack<ICommand>();
private Stack<ICommand> _Redocommands = new Stack<ICommand>();
private Canvas _Container;
public Canvas Container
{
get { return _Container; }
set { _Container = value; }
}
public void Redo(int levels)
{
for (int i = 1; i <= levels; i++)
{
if (_Redocommands.Count != 0)
{
ICommand command = _Redocommands.Pop();
command.Execute();
_Undocommands.Push(command);
}
}
}
public void Undo(int levels)
{
for (int i = 1; i <= levels; i++)
{
if (_Undocommands.Count != 0)
{
ICommand command = _Undocommands.Pop();
command.UnExecute();
_Redocommands.Push(command);
}
}
}
#region UndoHelperFunctions
public void InsertInUnDoRedoForInsert(FrameworkElement ApbOrDevice)
{
ICommand cmd = new InsertCommand(ApbOrDevice, Container);
_Undocommands.Push(cmd);_Redocommands.Clear();
}
public void InsertInUnDoRedoForDelete(FrameworkElement ApbOrDevice)
{
ICommand cmd = new DeleteCommand(ApbOrDevice, Container);
_Undocommands.Push(cmd);_Redocommands.Clear();
}
public void InsertInUnDoRedoForMove
(Point margin, FrameworkElement UIelement)
{
ICommand cmd = new MoveCommand(new Thickness
(margin.X, margin.Y, 0, 0), UIelement);
_Undocommands.Push(cmd);_Redocommands.Clear();
}
public void InsertInUnDoRedoForResize
(Point margin, double width, double height, FrameworkElement UIelement)
{
ICommand cmd = new ResizeCommand(new Thickness
(margin.X, margin.Y, 0, 0), width, height, UIelement);
_Undocommands.Push(cmd);_Redocommands.Clear();
}
#endregion
}
第一堆栈_Undocommands持有
可撤销操作,第二堆栈 _Redo
commands持有redoable 操作。当InsertInUnDoRedo
方法被调用时,Icommand对象被插入到_Undocommands栈中,以使命令成为可撤销操作,并清除重做堆栈。此处级别决定您要执行撤消的次数。
5.4 第四步
在你的应用程序中执行不同的操作时,给这个操作定义一个Command object,使用InsertInUnDoRedo方法新增到Undo/Redo系统。当从UI单击Undo时,我们调用UndoRedo类的Undo方法,当从UI中单击Redo时,我们将调用UndoRedo类的Redo方法。
在这里,没有明确设置撤消堆栈和重做堆栈的大小,保存的撤消重做状态的数量取决于系统内存。
六、优势和缺点
6.1 优点
其可维护性良好,不包含任何冗余信息。它不是内存密集型(对比第三种方法)。
6.2 缺点
命令模式的唯一缺点是,无论操作大小,都必须使命令类型等于操作数。随着操作的增加,命令也会增加。
最后
以上就是直率可乐为你收集整理的撤销和重做实现-第二部分(命令模式)一、引言二、Undo/Redo实现的基本思路三、命令模式通用思路四、程序实现六、优势和缺点的全部内容,希望文章能够帮你解决撤销和重做实现-第二部分(命令模式)一、引言二、Undo/Redo实现的基本思路三、命令模式通用思路四、程序实现六、优势和缺点所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复