Command(命令)——对象行为型模式(通过Command设计模式实现WinForm表单维护的撤销与重做功能)
意图
将一个请求封装为一个对象,从而使你可用不同的请求对客户进行参数化;对请求排队或记录请求日志,以及支持可撤销的操作。
动机
有时必须向某个对象提交请求,但并不知道关于被请求的操作或请求的接受者的任何信息。
典型场景
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方法即可。