撤消重做我们业务中非常常用的功能,我这里写个带限制步数的撤销操作功能的基础实现。
先来了解下撤销重做操作的基本思路。
- 撤销的操作往往是先撤回最近的操作,重做则是重做最近一次的撤销操作。所以,撤销重做功能都是符合先进后出的逻辑。所以我们在选择的时候,可以直接采用栈来做实现。
- 为了避免撤销操作和重做操作的不一致性,我们需要定义一个对象,能够同时用于撤销和重做的具体操作行为,这样我们在做撤销和重做的操作时,就不至于会出现对不上的情况。
- 如果要实现撤销步数限制,那么我们就需要在撤销步数达到最大时,删除最前面多余的撤销步数。
撤销重做栈的实现
因为业务需要,我们这个栈需要提供删除超出的撤销操作,具体方法如下:
/// <summary>
/// 包含栈对象的节点
/// </summary>
/// <typeparam name="T"></typeparam>
public class LinkedStackNode<T>
{
public LinkedStackNode(T value)
{
Value = value;
}
/// <summary>
/// 下一个节点对象
/// </summary>
public LinkedStackNode<T> Next { get; internal set; }
/// <summary>
/// 上一个节点对象
/// </summary>
public LinkedStackNode<T> Pre { get; internal set; }
/// <summary>
/// 包含对象的值
/// </summary>
public T Value { get; }
}
/// <summary>
/// 链表实现的栈,非线程安全
/// </summary>
/// <typeparam name="T"></typeparam>
public class LinkedStack<T>
{
public LinkedStack(int capacity)
{
_capacity = capacity;
}
/// <summary>
/// 根节点
/// </summary>
private LinkedStackNode<T> _root;
/// <summary>
/// 叶子节点
/// </summary>
private LinkedStackNode<T> _leaf;
/// <summary>
/// 最大容量
/// </summary>
private int _capacity;
/// <summary>
/// 获取或设置<see cref="LinkedStack{T}"/>允许容纳的最大数量
/// </summary>
public int Capacity
{
get
{
return _capacity;
}
set
{
_capacity = value;
if (_capacity < Count)
{
RemoveLastOf(Count - _capacity);
}
}
}
/// <summary>
/// <see cref="LinkedStack{T}"/>所包含<see cref="LinkedStackNode{T}"/>数量
/// </summary>
public int Count { get; private set; }
/// <summary>
/// 移除并返回顶端的<see cref="LinkedStackNode{T}"/>对象
/// </summary>
/// <returns></returns>
public T Pop()
{
if (_leaf == null)
throw new Exception("当前栈为空");
var value = _leaf.Value;
var pre = _leaf.Pre;
if (pre != null)
{
pre.Next = null;
}
_leaf = pre;
Count--;
return value;
}
/// <summary>
/// 移除并返回顶端的<see cref="LinkedStackNode{T}"/>对象
/// </summary>
/// <param name="t"></param>
/// <returns></returns>
public bool TryPop(out T t)
{
if (_leaf == null)
{
t = default;
return false;
}
t = Pop();
return true;
}
/// <summary>
/// 在栈顶端插入一个隶属于<see cref="LinkedStackNode{T}"/>的对象
/// </summary>
/// <param name="t"></param>
public void Push(T t)
{
var node = new LinkedStackNode<T>(t);
if (_root == null)
{
_root = node;
_leaf = node;
}
else
{
if (_root.Next == null)
{
_root.Next = node;
node.Pre = _root;
_leaf = node;
}
else
{
_leaf.Next = node;
node.Pre = _leaf;
_leaf = node;
}
}
if (Capacity - Count > 1)
{
Count++;
return;
}
else
{
RemoveLastOf(1);
}
}
/// <summary>
/// 返回顶端的<see cref="LinkedStackNode{T}"/>对象并不进行移除
/// </summary>
/// <returns></returns>
public T Peek()
{
if (_leaf == null)
throw new Exception("当前栈为空");
return _leaf.Value;
}
/// <summary>
/// 返回顶端的<see cref="LinkedStackNode{T}"/>对象并不进行移除
/// </summary>
/// <param name="t"></param>
/// <returns></returns>
public bool TryPeek(out T t)
{
if (_leaf == null)
{
t = default;
return false;
}
t = Peek();
return true;
}
/// <summary>
/// 从栈底端移除节点
/// </summary>
/// <param name="count">需要移除的对象数量</param>
/// <returns></returns>
int RemoveLastOf(int count)
{
var c = Math.Min(count, Count);
var current = _root;
for (int i = 0; i < c; i++)
{
current = current.Next;
}
current.Pre = null;
_root = current;
Count -= c;
return c;
}
}
定义可撤销操作接口IUndoable
/// <summary>
/// 表示可撤销的
/// </summary>
public interface IUndoable
{
/// <summary>
/// 恢复
/// </summary>
void Redo();
/// <summary>
/// 撤销
/// </summary>
void Undo();
}
定义撤销重做操作
internal class UndoableOperator
{
internal UndoableOperator()
{
_redoCommandStack = new LinkedStack<IUndoable>(_defaultCapacity);
_undoCommandStack = new LinkedStack<IUndoable>(_defaultCapacity);
}
readonly LinkedStack<IUndoable> _redoCommandStack;
readonly LinkedStack<IUndoable> _undoCommandStack;
/// <summary>
/// 默认支持撤销重做的步数
/// </summary>
const int _defaultCapacity = 100;
private int capacity = _defaultCapacity;
public int Capacity
{
get
{
return capacity;
}
set
{
capacity = value;
_redoCommandStack.Capacity = value;
_undoCommandStack.Capacity = value;
}
}
public bool CanUndo
{
get
{
return _undoCommandStack.Count > 0;
}
}
public bool CanRedo
{
get
{
return _redoCommandStack.Count > 0;
}
}
/// <summary>
/// 插入一个新的操作
/// </summary>
/// <param name="undoable"></param>
public void Insert(IUndoable undoable)
{
_undoCommandStack.Push(undoable);
}
/// <summary>
/// 执行一个可撤销操作
/// </summary>
internal void Undo()
{
if (_undoCommandStack.TryPop(out IUndoable cmd))
{
cmd.Undo();
_redoCommandStack.Push(cmd);
}
}
/// <summary>
/// 执行一个可恢复操作
/// </summary>
internal void Redo()
{
if (_redoCommandStack.TryPop(out IUndoable cmd))
{
cmd.Redo();
_undoCommandStack.Push(cmd);
}
}
}
这里没有使用框架自带的栈,真正业务上可以使用System.Collections.Concurrent.ConcurrentStack
,线程安全栈来代替以上链表栈。
到这里,撤销重做的功能就基本实现了,只需要在实际业务上稍微做些封装就可以工作了。
本文会经常更新,请阅读原文: https://huchengv5.gitee.io//post/%E5%B8%A6%E6%92%A4%E9%94%80%E6%AD%A5%E6%95%B0%E9%99%90%E5%88%B6%E7%9A%84%E6%92%A4%E6%B6%88%E9%87%8D%E5%81%9A%E5%8A%9F%E8%83%BD%E5%AE%9E%E7%8E%B0.html ,以避免陈旧错误知识的误导,同时有更好的阅读体验。
本作品采用 知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议 进行许可。欢迎转载、使用、重新发布,但务必保留文章署名胡承(包含链接: https://huchengv5.gitee.io/ ),不得用于商业目的,基于本文修改后的作品务必以相同的许可发布。如有任何疑问,请 与我联系 。