采用设计模式中的“命令模式”实现 C1FlexGrid 的撤销还原功能,那就先从命令模式简单介绍开始吧。
一 命令模式
命令模式属于对象的行为型模式,将一个请求封装为一个对象,从而使你可以用不同的请求对客户进行参数化,对请求排队和记录请求日志,以及支持可撤销还原的操作。
采用命令模式,把发出命令的责任和执行命令的责任分隔开,委派给不同的对象。
ICommand 是命令的接口,指定所有命令必须实现两个方法 Execute(执行,还原)和 Undo(撤销);
ConcreteCommand 作为具体命令的实现,诸如增加/删除行/列命令,调整行/列命令,编辑命令等等;
Invoker 作为命令调用者,可以理解为命令集管理类,负责命令的调用以及命令集合管理等操作;
Receiver 是命令的接收者,是命令的真正执行者,在本文的实现里,可以理解为 C1FlexGrid;
Client 则负责创建具体命令对象,并确定其接收者,这个 Client 可以是任何一个地方,只要那里需要执行某个命令;
命令模式具体讲解参考博客 saville 和 Edward_jie,楼主就不班门弄斧了。
理论毕竟是理论,还是要靠实践来检验,而且要根据实际情况灵活变通才是王道。于是楼主把它用到 C1FlexGrid 里玩玩看。
二 C1FlexGrid 撤销还原模块设计实现
模块设计图如下,楼主不擅长画什么 UML 活动图类图什么的,就画个大概意思吧。
先从一个单元格编辑命令 EditAction 开始吧。
IUndoableAction 相当于上面所讲的 ICommand 接口,这里楼主把里面的方法换成了 Undo 和 Redo,依次对应前面的 Undo 和 Execute,还有一个方法是 SaveNewState,是用于在命令第一次执行后,保存命令执行后的新状态,以便于还原;(说明一点的是,旧状态会在命令初始化时进行备份;而新状态则是通过调用 SaveNewState 方法来备份);
namespace Memento.SLFlexGrid.UndoStack { /// <summary> /// 定义实现可撤销动作对象所需要的方法 /// </summary> public interface IUndoableActio { /// <summary> /// 撤销 /// </summary> void Undo() /// <summary> /// 还原 /// </summary> void Redo() /// <summary> /// 动作执行后保存状态 /// </summary> bool SaveNewState() } }
FlexGridExtAction 算是在接口 ICommand 和具体命令实现类 ConcreteCommand 中间插入的一层,作为专门的 C1FlexGrid 命令的父类;
namespace Memento.SLFlexGrid.UndoStack { /// <summary> /// SLFlexGridExt里撤销/还原动作的基类 /// </summary> public abstract class FlexGridExtAction : IUndoableActio { #region 私有变量 protected SLFlexGridExt _flex;// 命令模式执行者 #endregion #region 构造函数 /// <summary> /// 构造函数 /// </summary> /// <param name="flex"></param> public FlexGridExtAction(SLFlexGridExt flex) { _flex = flex } #endregion #region 虚方法 /// <summary> /// 撤销最后一个动作 /// </summary> public abstract void Undo() /// <summary> /// 还原最后一个动作 /// </summary> public abstract void Redo() /// <summary> /// 保存当前表格状态 /// </summary> public abstract bool SaveNewState() #endregion } }
EditAction 是具体命令的实现类,相当于之前的 ConcreteCommand;
using C1.Silverlight.FlexGrid namespace Memento.SLFlexGrid.UndoStack { /// <summary> /// 单元格编辑动作 /// </summary> public class EditAction : FlexGridExtActio { # region 私有变量 private CellRange _range private object _oldValue private object _newValue # endregion # region 构造函数 /// <summary> /// 构造函数 /// </summary> public EditAction(SLFlexGridExt flex) : base(flex) { _range = flex.Selectio _oldValue = GetValue() } # endregion # region 其他方法 /// <summary> /// 获取单元格的内容 /// </summary> private object GetValue() { Row row = _flex.Rows[_range.Row] Column col = _flex.Columns[_range.Column] return row[col] } # endregion # region 接口IUndoableAction方法 /// <summary> /// 撤销 /// </summary> public override void Undo() { _flex[_range.Row, _range.Column] = _oldValue _flex.Select(_range, true) } /// <summary> /// 还原 /// </summary> public override void Redo() { _flex[_range.Row, _range.Column] = _newValue _flex.Select(_range, true) } /// <summary> /// 动作执行后,保存新状态 /// </summary> public override bool SaveNewState() { _newValue = GetValue() // 默认null和空串等效,不做撤销还原 return !(object.Equals(_oldValue, _newValue) || (_oldValue == null && _newValue.Equals(""))) } # endregion } }
UndoStack 是命令集堆栈管理类,相当于前面说道的 Invoker,这里的 UndoStack 还负责撤销还原状态的判断和通知;
using System using System.Collections.Generic namespace Memento.SLFlexGrid.UndoStack { /// <summary> /// 撤销还原栈基类 /// </summary> public class UndoStack { #region 私有变量 private List<IUndoableAction> _stack = new List<IUndoableAction>() private int _ptr = -1 private const int MAX_STACK_SIZE = 500 #endregion #region 构造函数 /// <summary> /// 构造函数 /// </summary> public UndoStack() { } #endregion #region 公开方法 /// <summary> /// 清空撤销还原堆栈 /// </summary> public virtual void Clear() { _stack.Clear() _ptr = -1 OnStateChanged(EventArgs.Empty) } /// <summary> /// 获得一个值表示堆栈内是否有可撤销的动作 /// </summary> public bool CanUndo { get { return _ptr > -1 && _ptr < _stack.Count } } /// <summary> /// 获得一个值表示堆栈内是否有可还原的动作 /// </summary> public bool CanRedo { get { return _ptr + 1 > -1 && _ptr + 1 < _stack.Count } } /// <summary> /// 执行一个撤销命令 /// </summary> public void Undo() { if (CanUndo) { IUndoableAction action = _stack[_ptr] BeforeUndo(action) action.Undo() _ptr-- OnStateChanged(EventArgs.Empty) } } /// <summary> /// 执行一个还原命令 /// </summary> public void Redo() { if (CanRedo) { _ptr++ IUndoableAction action = _stack[_ptr] BeforeRedo(action) action.Redo() OnStateChanged(EventArgs.Empty) } } /// <summary> /// 添加动作到撤销还原堆栈 /// </summary> public void AddAction(IUndoableAction action) { // 整理堆栈 while (_stack.Count > 0 && _stack.Count > _ptr + 1) { _stack.RemoveAt(_stack.Count - 1) } while (_stack.Count >= MAX_STACK_SIZE) { _stack.RemoveAt(0) } // 更新指针并添加动作到堆栈中 _ptr = _stack.Count _stack.Add(action) OnStateChanged(EventArgs.Empty) } #endregion #region 委托事件 /// <summary> /// 当堆栈状态改变时触发 /// </summary> public event EventHandler StateChanged #endregion #region 虚方法 /// <summary> /// 触发事件<see cref="StateChanged"/> /// </summary> /// <param name="e"><see cref="EventArgs"/>包含事件参数</param> protected virtual void OnStateChanged(EventArgs e) { if (StateChanged != null) { StateChanged(this, e) } } /// <summary> /// 在执行撤销动作之前调用 /// </summary> protected virtual void BeforeUndo(IUndoableAction action) { } /// <summary> /// 在执行还原动作之前调用 /// </summary> protected virtual void BeforeRedo(IUndoableAction action) { } #endregion } }
ExcelUndoStack 则是继承 UndoStack,专门作为 C1FlexGrid 的命令集管理者;
namespace Memento.SLFlexGrid.UndoStack { public class FlexGridUndoStack : UndoStack { #region 私有变量 private SLFlexGridExt _flex private IUndoableAction _pendingAction;// 当前挂起的动作 private string _oldCellValue = "";// 单元格编辑前的内容 #endregion #region 构造函数 /// <summary> /// 构造函数 /// </summary> public FlexGridUndoStack(SLFlexGridExt flex) { _flex = flex flex.PrepareCellForEdit += flex_PrepareCellForEdit flex.CellEditEnded += flex_CellEditEnded } #endregion #region 重写方法 /// <summary> /// 在执行撤销动作之前调用 /// </summary> protected override void BeforeUndo(IUndoableAction action) { base.BeforeUndo(action) } /// <summary> /// 在执行还原动作之前调用 /// </summary> protected override void BeforeRedo(IUndoableAction action) { base.BeforeRedo(action) } #endregion #region 事件处理 // 单元格编辑 private void flex_PrepareCellForEdit(object sender, C1.Silverlight.FlexGrid.CellEditEventArgs e) { _pendingAction = new EditAction(_flex) } private void flex_CellEditEnded(object sender, C1.Silverlight.FlexGrid.CellEditEventArgs e) { if (!e.Cancel && _pendingAction is EditAction && _pendingAction.SaveNewState()) { _flex.UndoStack.AddAction(_pendingAction) } _pendingAction = null } #endregion } }
明显还差一个 Client 角色,既然是扩展 C1FlexGrid,实现其撤销还原模块,那就让它自己来负责吧,在 C1FlexGrid 的扩展类 FlexGridExt 中,添加 Invoker 对象,缓存命令集;添加 CanUndo 和 CanRedo 依赖属性,添加撤销还原方法的调用;下面是具体实现:
using System using System.Collections.Generic using System.Window using System.Windows.Control using System.Windows.Data using System.Windows.Input using System.Windows.Media using C1.Silverlight.FlexGrid using Memento.SLFlexGrid.UndoStack namespace Memento.SLFlexGrid { public class SLFlexGridExt : C1FlexGrid { #region 私有属性 private FlexGridUndoStack _undo;// 撤销还原堆栈 #endregion #region 公开属性 /// <summary> /// 获得该<see cref="SLFlexGrid"/>的<see cref="UndoStack"/> /// </summary> public FlexGridUndoStack UndoStack { get { return _undo } } /// <summary> /// 是否可以撤销 /// </summary> public bool CanUndo { get { return (bool)GetValue(CanUndoProperty) } } /// <summary> /// 是否可以还原 /// </summary> public bool CanRedo { get { return (bool)GetValue(CanRedoProperty) } } #endregion #region 依赖属性 /// <summary> /// 定义<see cref="CanUndo"/>依赖属性 /// </summary> public static readonly DependencyProperty CanUndoProperty = DependencyProperty.Register( "CanUndo", typeof(bool), typeof(SLFlexGridExt), new PropertyMetadata(false)) /// <summary> /// 定义<see cref="CanRedo"/>依赖属性 /// </summary> public static readonly DependencyProperty CanRedoProperty = DependencyProperty.Register( "CanRedo", typeof(bool), typeof(SLFlexGridExt), new PropertyMetadata(false)) #endregion #region 构造函数 /// <summary> /// 构造函数 /// </summary> public SLFlexGridExt() { this.DefaultStyleKey = typeof(SLFlexGridExt) // 默认添加50行10列 for (int i = 0; i < 50; i++) { Rows.Add(new Row()) } for (int c = 0; c < 10; c++) { Columns.Add(new Column()) } } #endregion #region 重写方法 /// <summary> /// 应用模版 /// </summary> public override void OnApplyTemplate() { try { base.OnApplyTemplate() _undo = new FlexGridUndoStack(this) _undo.StateChanged += (s, e) => { SetValue(CanUndoProperty, _undo.CanUndo) SetValue(CanRedoProperty, _undo.CanRedo) } } catch (Exception ex) { MessageBox.Show(ex.Message) } } #endregion #region 公开方法 /// <summary> /// 撤销 /// </summary> public void Undo() { _undo.Undo() } /// <summary> /// 还原 /// </summary> public void Redo() { _undo.Redo() } #endregion } }
好了,万事俱备了,也不欠东风了,只需要在界面上加上一个“撤销”按钮和一个“还原”按钮,然后事件里依次执行 flex.Undo(); 和 flex.Redo(); 即可,so easy 吧!
其他复杂的命令以后慢慢完善添加吧。欢迎指教!