“你们开发的系统如果能够像Excel那样支持全选、反选、多选、撤销、导入、导出等命令;不要每次增加一条记录都必须按一下‘新增’,完了以后再按一下‘保存’,取而代之的是我想保存的时候一按‘保存’,就把我前面所有的操作全都保存,如果我的操作有误,可以撤销刚刚所做的操作。那真是爽死了。”很多的用户这样感慨。用户体验非常重要,很多的时候,如果用户觉得你的系统不方便,他就会拒绝使用。这样,系统也就没有什么意义了。如果在给客户做演示的时候,能够充分展现你系统在易用性方面的优势,那么中标几率就会大大的增加。
用DataTable、DataGridView和Command设计模式完全可以前面用户所提到的功能。我们先分析一下,用户所提到的功能,实际上就是希望能够对界面层表示的数据方便的发出各种命令,这些命令可以通过一个个的按钮提供给用户调用。如果我们用DataTable来容纳业务数据,用DataGridView来表示这些数据,那么对“全选、反选、多选”等,就是针对DataGridView来说的,而对于“撤销、导入、批量保存”等则是对业务数据来说的,而DataTable则支持这些操作。下面是DataTable、DataRow、DataGridView、按钮和数据操作命令之间的关系简图。可以看出,数据操作命令就是Command模式中的Command,而按钮就是Invoker,而DataTable和DataGridView则是Receiver,在WinForm中,实际上我们主要是通过BindingSource来操作DataTable,所以对于DataTable这个间接的Receiver来说,其直接Receiver就是一个BindingSource。但是,不管是DataTable还是BindingSource都不能全部符合我们的要求,所以我们必须自己实现一个符合我们要求的Receiver,假设我们叫他DataCommandReceiverBase,为了能够进行可是化设计,我们让他继承自Component,其原型如下:
public abstract class DataCommandReceiverBase:Component
{
public abstract void New();
public abstract void Delete();
public abstract void UnDo();
public abstract void Save();
}
对于“新增、删除、保存、撤销”等命令,我们把它们抽象为一个公共的类,叫做DataCommand,其原型如下:
public abstract class DataCommand : Component
{
public abstract void Do();
DataCommandReceiverBase _Receiver;
public DataCommandReceiverBase Receiver
{
get { return _Receiver; }
set { _Receiver = value; }
}
}
然后,我们对每一个基本的操作,比如“新增”建立一个对应的DataCommand的子类,并重写Do函数,在该函数中调用Receiver相应的功能。如下所示:
public class NewCommand : DataCommand
{
protected override void Do()
{
Receiver.New();
}
}
public class DeleteCommand : DataCommand
{
protected override void Do()
{
Receiver.Delete();
}
}
public class SaveCommand : DataCommand
{
protected override void Do()
{
Receiver.Save();
}
}
public class UndoCommand : DataCommand
{
protected override void Do()
{
Receiver.UnDo();
}
}
对于给用户提供数据操作命令的按钮,我们叫他CommandButton,他包含有一个DataCommand的实例Command以便在用户按下按钮时执行用户的命令。其原型如下:
public class CommandButton:Button
{
DataCommand _Command;
public DataCommand Command
{
get { return _Command; }
set
{
if (_Command == value) return;
_Command = value;
}
}
protected override void OnClick(EventArgs e)
{
if (_Command != null)
_Command.Do();
base.OnClick(e);
}
}
现在,只要实现一个DataCommandReceiverBase的子类,我们的目的就达到了。要实现一个这样的类也非常的简单,稍微难一点的地方就是Undo方法的实现,如果用其他的平台我不知道,但是用.net 下面的DataTable,真的很容易,只要在目标DataTable的RowChanged事件和RowDeleted事件中把操作过的DataRow记录下来,在Undo的时候,取出最近的记录,然后调用DataRow的RejectChanges方法就可以搞定一切。其代码如下:
public class DefaultDataCommandReceiver : DataCommandReceiverBase
{
DataTable _SourceTable = null;
List<DataRow> _HistoryList = new List<DataRow>();
BindingSource _BindingSource;
public BindingSource BindingSource
{
get { return _BindingSource; }
set
{
if (_BindingSource == value)
return;
if (_SourceTable != null)
{
_SourceTable.RowChanged -= new DataRowChangeEventHandler(_SourceTable_RowChanged);
_SourceTable.RowDeleted -= new DataRowChangeEventHandler(_SourceTable_RowDeleted);
}
_SourceTable = null;
if (value == null)
{
_BindingSource = null;
return;
}
if (IsValidDataSource(value,ref _SourceTable))
{
_BindingSource = value;
if (_SourceTable != null)
{
_SourceTable.RowChanged += new DataRowChangeEventHandler(_SourceTable_RowChanged);
_SourceTable.RowDeleted += new DataRowChangeEventHandler(_SourceTable_RowDeleted);
}
return;
}
throw new Exception("BindingSource的DataSource必须是DataTable/DataSet/DataView");
}
}
bool IsValidDataSource(BindingSource value, ref DataTable ReturnTable)
{
if ((value.DataSource is DataTable) || (value.DataSource is DataSet) || (value.DataSource is DataView))
{
if (value.DataSource is DataTable)
ReturnTable = value.DataSource as DataTable;
else if (value.DataSource is DataSet)
ReturnTable = (value.DataSource as DataSet).Tables[value.DataMember];
else
ReturnTable = (value.DataSource as DataView).Table;
return true;
}
if (value.DataSource is BindingSource)
return IsValidDataSource(value.DataSource as BindingSource, ref ReturnTable);
return false;
}
void _SourceTable_RowDeleted(object sender, DataRowChangeEventArgs e)
{
if (!BeginLogUpdate) return;
LogRow(e.Row);
}
void _SourceTable_RowChanged(object sender, DataRowChangeEventArgs e)
{
if (!BeginLogUpdate) return;
if (e.Action == DataRowAction.Add || e.Action == DataRowAction.Change)
LogRow(e.Row);
}
void LogRow(DataRow ARow)
{
if (_HistoryList.Contains(ARow))
_HistoryList.Remove(ARow);
_HistoryList.Insert(0,ARow);
}
bool _BeginLogUpdate = false;
public bool BeginLogUpdate
{
get { return _BeginLogUpdate; }
set { _BeginLogUpdate = value; }
}
public override void New()
{
BindingSource.AddNew();
}
public override void Delete()
{
if (BindingSource.Count == 0) return;
((DataRowView)BindingSource.Current).Row.Delete();
}
public override void Save()
{
if (_HistoryList.Count == 0) return;
SaveDataToDb();
_SourceTable.AcceptChanges();
_HistoryList.Clear();
}
protected virtual void SaveDataToDb()
{
//
// Save the changed data into database
//
}
public override void UnDo()
{
if (_HistoryList.Count == 0) return;
_HistoryList[0].RejectChanges();
_HistoryList.RemoveAt(0);
}
}
到现在为止,我们还没有实现针对DataGridView的“全选、反选”等功能,这个就留给大家来完成吧。
学了一段时间的设计模式,发现设计模式的概念不难理解,难在应用,希望这些文章能对各位特别是初学设计模式的朋友有用。也希望各位高人能不吝指点,以让大家能够在设计上更上一层楼。参考代码:Command.rar