Command(命令)——物件行為型模式(通過Command設計模式實現WinForm表單維護的撤銷與重做功能)

CodingPioneer發表於2018-10-17

意圖

將一個請求封裝為一個物件,從而使你可用不同的請求對客戶進行引數化;對請求排隊或記錄請求日誌,以及支援可撤銷的操作。

動機

有時必須向某個物件提交請求,但並不知道關於被請求的操作或請求的接受者的任何資訊。

典型場景

Command模式的典型應用場景就是實現撤銷與恢復功能。下圖為實現普通介面的撤銷與恢復功能的類
Command設計模式實現撤銷與恢復功能

程式碼實現

ICommand介面,定義execute和undo操作

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Mesnac.Basic.Service
{
    /// <summary>
    /// 操作命令介面
    /// </summary>
    public interface ICommand
    {
        /// <summary>
        /// 命令執行方法,對應恢復操作
        /// </summary>
        void execute();
        /// <summary>
        /// 命令撤銷方法
        /// </summary>
        void undo();
    }
}

OperationCommand實現ICommand介面,定義操作的具體實現

using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Windows.Forms;

namespace Mesnac.Basic.Service
{
    /// <summary>
    /// 操作命令類,用與進行撤銷和恢復操作的封裝類
    /// </summary>
    public class OperationCommand : ICommand
    {
        #region 欄位定義

        private Control _ctrl;
        private object _newValue;
        private object _oldValue;
        private EventHandler _eventHandler;
        private DataGridViewCellEventHandler _dataGridViewCellEventHandler;

        #endregion

        #region 構造方法

        public OperationCommand (Control ctrl, object newValue, object oldValue)
        {
            this._ctrl = ctrl;
            this._newValue = newValue;
            this._oldValue = oldValue;
        }

        public OperationCommand(Control ctrl, object newValue, object oldValue, EventHandler eventHandler)
        {
            this._ctrl = ctrl;
            this._newValue = newValue; 
            this._oldValue = oldValue;
            this._eventHandler = eventHandler;
        }

        public OperationCommand(Control ctrl, object newValue, object oldValue, DataGridViewCellEventHandler dataGridViewCellEventHandler, int rowIndex, int columnIndex)
        {
            this._ctrl = ctrl;
            this._newValue = newValue;
            this._oldValue = oldValue;
            this._dataGridViewCellEventHandler = dataGridViewCellEventHandler;
        }

        #endregion

        #region ICommand介面成員實現

        #region 恢復操作實現

        /// <summary>
        /// 恢復操作實現
        /// </summary>
        public void execute()
        {
            if (this._ctrl is TextBox)
            {
                (this._ctrl as TextBox).TextChanged -= this._eventHandler;
                (this._ctrl as TextBox).Text = this._newValue == null ? String.Empty : this._newValue.ToString();
                (this._ctrl as TextBox).SelectionStart = (this._ctrl as TextBox).Text.Length;
                (this._ctrl as TextBox).TextChanged += this._eventHandler;
            }
            if (this._ctrl is CheckBox)
            {
                (this._ctrl as CheckBox).CheckedChanged -= this._eventHandler;
                bool newValue = false;
                if (this._newValue != null)
                {
                    bool.TryParse(this._newValue.ToString(), out newValue);
                }
                (this._ctrl as CheckBox).Checked = newValue;
                (this._ctrl as CheckBox).CheckedChanged += this._eventHandler;
            }
            if (this._ctrl is ComboBox)
            {
                (this._ctrl as ComboBox).SelectedIndexChanged -= this._eventHandler;
                (this._ctrl as ComboBox).SelectedItem = this._newValue;
                (this._ctrl as ComboBox).SelectedIndexChanged += this._eventHandler;
            }
            if (this._ctrl is DateTimePicker)
            {
                (this._ctrl as DateTimePicker).ValueChanged -= this._eventHandler;
                DateTime newValue = DateTime.Now;
                if (this._newValue != null)
                {
                    DateTime.TryParse(this._newValue.ToString(), out newValue);
                }
                (this._ctrl as DateTimePicker).Value = newValue;
                (this._ctrl as DateTimePicker).ValueChanged += this._eventHandler;
            }
            if (this._ctrl is DataGridView)
            {
                if (this._dataGridViewCellEventHandler != null)
                {
                    (this._ctrl as DataGridView).CellValueChanged -= this._dataGridViewCellEventHandler;
                }
                (this._ctrl as DataGridView).DataSource = this._newValue;
                Mesnac.Basic.DataProcessor.ClearSelectedStatus((this._ctrl as DataGridView));
                if (this._dataGridViewCellEventHandler != null)
                {
                    (this._ctrl as DataGridView).CellValueChanged += this._dataGridViewCellEventHandler;
                }
            }
        }

        #endregion

        #region 撤銷操作實現

        /// <summary>
        /// 撤銷操作實現
        /// </summary>
        public void undo()
        {
            if (this._ctrl is TextBox)
            {
                (this._ctrl as TextBox).TextChanged -= this._eventHandler;
                (this._ctrl as TextBox).Text = this._oldValue == null ? String.Empty : this._oldValue.ToString();
                (this._ctrl as TextBox).SelectionStart = (this._ctrl as TextBox).Text.Length;
                (this._ctrl as TextBox).TextChanged += this._eventHandler;
            }
            if (this._ctrl is CheckBox)
            {
                (this._ctrl as CheckBox).CheckedChanged -= this._eventHandler;
                bool oldValue = false;
                if (this._oldValue != null)
                {
                    bool.TryParse(this._oldValue.ToString(), out oldValue);
                }
                (this._ctrl as CheckBox).Checked = oldValue;
                (this._ctrl as CheckBox).CheckedChanged += this._eventHandler;
            }
            if (this._ctrl is ComboBox)
            {
                (this._ctrl as ComboBox).SelectedIndexChanged -= this._eventHandler;
                (this._ctrl as ComboBox).SelectedItem = this._oldValue;
                (this._ctrl as ComboBox).SelectedIndexChanged += this._eventHandler;
            }
            if (this._ctrl is DateTimePicker)
            {
                (this._ctrl as DateTimePicker).ValueChanged -= this._eventHandler;
                DateTime oldValue = DateTime.Now;
                if (this._oldValue != null)
                {
                    DateTime.TryParse(this._oldValue.ToString(), out oldValue);
                }
                (this._ctrl as DateTimePicker).Value = oldValue;
                (this._ctrl as DateTimePicker).ValueChanged += this._eventHandler;
            }
            if (this._ctrl is DataGridView)
            {
                if (this._dataGridViewCellEventHandler != null)
                {
                    (this._ctrl as DataGridView).CellValueChanged -= this._dataGridViewCellEventHandler;
                }
                
                (this._ctrl as DataGridView).DataSource = this._oldValue;
                Mesnac.Basic.DataProcessor.ClearSelectedStatus((this._ctrl as DataGridView));
                
                if (this._dataGridViewCellEventHandler != null)
                {
                    (this._ctrl as DataGridView).CellValueChanged += this._dataGridViewCellEventHandler;
                }
            }
        }

        #endregion

        #endregion
    }
}

EventHandlerProcess對常規WinForm控制元件的事件進行處理把對控制元件的操作封裝為ICommand物件

using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Text;
using System.Windows.Forms;

namespace Mesnac.Basic.Service
{
    /// <summary>
    /// 事件處理器
    /// </summary>
    public class EventHandlerProcessor
    {
        #region 欄位定義

        private object _oldValue;           //儲存事件源原始值

        #endregion

        #region 構造方法

        /// <summary>
        /// 構造方法
        /// </summary>
        /// <param name="oldValue">傳遞事件源原始值</param>
        public EventHandlerProcessor(object oldValue)
        {
            this._oldValue = oldValue;
        }

        #endregion

        #region 事件處理

        #region 文字框事件處理

        /// <summary>
        /// 文字框事件處理
        /// </summary>
        /// <param name="sender">事件源</param>
        /// <param name="e">事件引數</param>
        public void TextBox_LostFocus(object sender, EventArgs e)
        {
            TextBox textBox = sender as TextBox;
            string oldStr = String.IsNullOrEmpty(this._oldValue as string) ? String.Empty : this._oldValue.ToString();
            if (textBox.Text.Equals(oldStr))
            {
                return;
            }
            OperationCommand cmd = new OperationCommand(textBox, textBox.Text, oldStr, this.TextBox_LostFocus);
            UndoRedoService.UndoStack.Push(cmd);
            this._oldValue = textBox.Text;
        }

        #endregion

        #region 核取方塊事件處理

        /// <summary>
        /// 核取方塊事件處理
        /// </summary>
        /// <param name="sender">事件源</param>
        /// <param name="e">事件引數</param>
        public void CheckBox_CheckedChanged(object sender, EventArgs e)
        {
            CheckBox checkBox = sender as CheckBox;
            bool oldValue = false;
            if (this._oldValue != null)
            {
                bool.TryParse(this._oldValue.ToString(), out oldValue);
            }
            if (checkBox.Checked == oldValue)
            {
                return;
            }
            OperationCommand cmd = new OperationCommand(checkBox, checkBox.Checked, oldValue, this.CheckBox_CheckedChanged);
            UndoRedoService.UndoStack.Push(cmd);
            this._oldValue = checkBox.Checked;
        }

        #endregion

        #region 組合框事件處理

        /// <summary>
        /// 組合框事件處理
        /// </summary>
        /// <param name="sender">事件源</param>
        /// <param name="e">事件引數</param>
        public void ComboBox_SelectedIndexChanged(object sender, EventArgs e)
        {
            ComboBox comboBox = sender as ComboBox;
            if (comboBox.SelectedItem == this._oldValue)
            {
                return;
            }
            OperationCommand cmd = new OperationCommand(comboBox, comboBox.SelectedItem, this._oldValue, this.ComboBox_SelectedIndexChanged);
            UndoRedoService.UndoStack.Push(cmd);
            this._oldValue = comboBox.SelectedItem;
        }

        #endregion

        #region 日曆事件處理

        /// <summary>
        /// 日曆控制元件事件處理
        /// </summary>
        /// <param name="sender">事件源</param>
        /// <param name="e">事件引數</param>
        public void DateTimePicker_ValueChanged(object sender, EventArgs e)
        {
            DateTimePicker dateTimePicker = sender as DateTimePicker;
            DateTime oldValue = DateTime.Now;
            if (this._oldValue != null)
            {
                DateTime.TryParse(this._oldValue.ToString(), out oldValue);
            }
            if (String.Format("{0:yyyyMMddHHmmss}", dateTimePicker.Value).Equals(String.Format("{0:yyyyMMddHHmmss}", oldValue)))
            {
                return;
            }
            OperationCommand cmd = new OperationCommand(dateTimePicker, dateTimePicker.Value, this._oldValue, this.DateTimePicker_ValueChanged);
            UndoRedoService.UndoStack.Push(cmd);
            this._oldValue = dateTimePicker.Value;
        }

        #endregion

        #region DataGridView事件處理

        /// <summary>
        /// DataGridView事件處理
        /// </summary>
        /// <param name="sender">事件源</param>
        /// <param name="e">事件引數</param>
        public void DataGridView_CellValueChanged(object sender, DataGridViewCellEventArgs e)
        {
            DataGridView dataGridView = sender as DataGridView;
            DataTable dtNew = Mesnac.Basic.DataProcessor.GetDataTableFromGridView(dataGridView);
            OperationCommand cmd = new OperationCommand(dataGridView, dtNew, this._oldValue, this.DataGridView_CellValueChanged, e.RowIndex, e.ColumnIndex);
            UndoRedoService.UndoStack.Push(cmd);
            this._oldValue = Mesnac.Basic.DataProcessor.GetDataTableFromGridView(dataGridView);
        }

        #endregion

        #endregion
    }
}

UndoRedoService定義撤銷恢復服務類,作為呼叫入口

using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Text;
using System.Windows.Forms;

namespace Mesnac.Basic.Service
{
    /// <summary>
    /// UndoRedo服務類
    /// </summary>
    public class UndoRedoService
    {
        #region 欄位定義

        private static Dictionary<Control, EventHandlerProcessor> _dicControlEventHandler = new Dictionary<Control, EventHandlerProcessor>();

        #endregion

        #region 屬性定義

        /// <summary>
        /// Undo操作棧
        /// </summary>
        public static Stack<ICommand> UndoStack = new Stack<ICommand>();

        /// <summary>
        /// Redo操作棧
        /// </summary>
        public static Stack<ICommand> RedoStack = new Stack<ICommand>();

        /// <summary>
        /// 控制元件事件處理繫結集合
        /// </summary>
        public static Dictionary<Control, EventHandlerProcessor> DicControlEventHandler
        {
            get
            {
                return _dicControlEventHandler;
            }
        }

        #endregion

        #region Undo Redo服務初始化

        /// <summary>
        /// Undo Redo服務初始化
        /// </summary>
        /// <param name="controls">要註冊UndoRedo服務的控制元件集合</param>
        public static void Init(List<Control> controls)
        {
            Clear();

            EventHandlerProcessor ehp = null;

            foreach (Control ctl in controls)
            {
                if (ctl is TextBox)
                {
                    ehp = new EventHandlerProcessor((ctl as TextBox).Text);
                    _dicControlEventHandler.Add(ctl, ehp);
                    (ctl as TextBox).LostFocus -= ehp.TextBox_LostFocus;
                    (ctl as TextBox).LostFocus += ehp.TextBox_LostFocus;
                }
                if (ctl is CheckBox)
                {
                    ehp = new EventHandlerProcessor((ctl as CheckBox).Checked);
                    _dicControlEventHandler.Add(ctl, ehp);
                    (ctl as CheckBox).CheckedChanged -= ehp.CheckBox_CheckedChanged;
                    (ctl as CheckBox).CheckedChanged += ehp.CheckBox_CheckedChanged;
                }
                if (ctl is ComboBox)
                {
                    ehp = new EventHandlerProcessor((ctl as ComboBox).SelectedItem);
                    _dicControlEventHandler.Add(ctl, ehp);
                    (ctl as ComboBox).SelectedIndexChanged -= ehp.ComboBox_SelectedIndexChanged;
                    (ctl as ComboBox).SelectedIndexChanged += ehp.ComboBox_SelectedIndexChanged;
                }
                if (ctl is DateTimePicker)
                {
                    ehp = new EventHandlerProcessor((ctl as DateTimePicker).Value);
                    _dicControlEventHandler.Add(ctl, ehp);
                    (ctl as DateTimePicker).ValueChanged -= ehp.DateTimePicker_ValueChanged;
                    (ctl as DateTimePicker).ValueChanged += ehp.DateTimePicker_ValueChanged;
                }
                if (ctl is DataGridView)
                {
                    DataTable dtOld = Mesnac.Basic.DataProcessor.GetDataTableFromGridView((ctl as DataGridView));
                    ehp = new EventHandlerProcessor(dtOld);
                    _dicControlEventHandler.Add(ctl, ehp);
                    (ctl as DataGridView).CellValueChanged -= ehp.DataGridView_CellValueChanged;
                    (ctl as DataGridView).CellValueChanged += ehp.DataGridView_CellValueChanged;
                }
            }
        }

        #endregion

        #region 清除操作棧

        /// <summary>
        /// 清除操作棧
        /// </summary>
        public static void Clear()
        {
            foreach(Control ctl in _dicControlEventHandler.Keys)
            {
                if (ctl is TextBox)
                {
                    (ctl as TextBox).LostFocus -= _dicControlEventHandler[ctl].TextBox_LostFocus;
                }
                if (ctl is CheckBox)
                {
                    (ctl as CheckBox).CheckedChanged -= _dicControlEventHandler[ctl].CheckBox_CheckedChanged;
                }
                if (ctl is ComboBox)
                {
                    (ctl as ComboBox).SelectedIndexChanged -= _dicControlEventHandler[ctl].ComboBox_SelectedIndexChanged;
                }
                if (ctl is DateTimePicker)
                {
                    (ctl as DateTimePicker).ValueChanged -= _dicControlEventHandler[ctl].DateTimePicker_ValueChanged;
                }
                if (ctl is DataGridView)
                {
                    (ctl as DataGridView).CellValueChanged -= _dicControlEventHandler[ctl].DataGridView_CellValueChanged;
                }
            }
            
            _dicControlEventHandler.Clear();

            UndoStack.Clear();
            RedoStack.Clear();
        }

        #endregion
    }
}

呼叫入口為UndoRedoService.Init

在需要有撤銷與恢復功能的介面初始化的地方呼叫UndoRedoService.Init方法,把介面控制元件列表傳入Init方法即可。

相關文章