[C1] 实现 C1FlexGrid 撤销还原功能

采用设计模式中的“命令模式”实现 C1FlexGrid 的撤销还原功能,那就先从命令模式简单介绍开始吧。

一  命令模式

命令模式属于对象的行为型模式,将一个请求封装为一个对象,从而使你可以用不同的请求对客户进行参数化,对请求排队和记录请求日志,以及支持可撤销还原的操作。

clip_image002

采用命令模式,把发出命令的责任和执行命令的责任分隔开,委派给不同的对象。

ICommand 是命令的接口,指定所有命令必须实现两个方法 Execute(执行,还原)和 Undo(撤销);

ConcreteCommand 作为具体命令的实现,诸如增加/删除行/列命令,调整行/列命令,编辑命令等等;

Invoker 作为命令调用者,可以理解为命令集管理类,负责命令的调用以及命令集合管理等操作;

Receiver 是命令的接收者,是命令的真正执行者,在本文的实现里,可以理解为 C1FlexGrid

Client 则负责创建具体命令对象,并确定其接收者,这个 Client 可以是任何一个地方,只要那里需要执行某个命令;

命令模式具体讲解参考博客 savilleEdward_jie,楼主就不班门弄斧了。

理论毕竟是理论,还是要靠实践来检验,而且要根据实际情况灵活变通才是王道。于是楼主把它用到 C1FlexGrid 里玩玩看。

二  C1FlexGrid 撤销还原模块设计实现

模块设计图如下,楼主不擅长画什么 UML 活动图类图什么的,就画个大概意思吧。

image

先从一个单元格编辑命令 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()
    }
}
IUndoableAction

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
    }
}
FlexGridExtAction

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
    }
}
EditAction

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
    }
}
UndoStack

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
    }
}
FlexGridUndoStack

明显还差一个 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
    }
}
SLFlexGridExt

 

好了,万事俱备了,也不欠东风了,只需要在界面上加上一个“撤销”按钮和一个“还原”按钮,然后事件里依次执行 flex.Undo(); flex.Redo(); 即可,so easy 吧!

其他复杂的命令以后慢慢完善添加吧。欢迎指教!

posted @ 2015-04-10 17:30  01码匠  阅读(1121)  评论(1编辑  收藏  举报