RFID会议签到系统总结(二十)――数据窗体状态控制
数据窗体的状态大概可以概括为以下几种,即初始、浏览、新增、修改、删除、查询(单数据窗体的查询是一种简单地基于当前表的查询,各个条件之间只能“与”运算,关系运算只有“等于”,当然字符串字段会有“like”)。在各种状态下,窗体的UI呈现是不相同的,而且在不同状态下,一些按钮的动作是各异的。比如“确定”按钮,在新增状态下按“确定”和在查询状态下按“确定”明显是不同的二种动作。上述的这些情况可以用GoF的状态模式来解决之(很奇怪,这个模式很多人不甚清楚,以前有几次出去面试的时候说到这个模式,对面的人居然问我这东西是否属于二十三种之一的)。
数据窗体的各种状态下可以提取出来的公共动作基本可以分为二大类,一是按钮单击,一个是界面可视控件状态切换。
按钮单击只是指“确定”、“取消”二个按钮,其他如“新增”,“修改”之类的按钮由于都是互斥的,我们没有必要放到状态里来做特殊的处理。界面切换又分为二大部分,一个是工具栏按钮可用性的切换,一个是数据绑定控件可编辑性的切换。
界面切换这一块其实并没有那么多的状态,只可以归为浏览与非浏览二种,无论是新增、修改、查询,在界面处理上没有什么大的区别,而初始只是一种特殊的浏览状态,删除比较特殊另行处理(删除时并没有界面的切换)。
鉴于各个状态会有一些公共的方法,这里没有用Interface来实现,而是用了Class的继承。
public class FrmState
{
protected frmData med;
//……略
public FrmState(frmData frm)
{
med = frm;
}
/**//// <summary>
/// 根据界面控件值设置实体的属性
/// </summary>
/// <remarks>此方法只在新增与修改状态下使用</remarks>
protected void SetEntityValue()
{
if (stateName != StateEnum.Insert && stateName != StateEnum.Edit) return;
string dataField;
Type columnType;
DataText txt;
DataComboBox cmb;
for (int i = 0;i < med.pnlDetail.Controls.Count;i++)
{
txt = med.pnlDetail.Controls[i] as DataText;
if (txt != null)
{
dataField = med.GetControlBindField(txt);
columnType = med.dataTable.Columns[dataField].DataType;
if (!txt.NeedTrans)
med.record[dataField] = Convert.ChangeType(txt.Text.Trim(),columnType);
continue;
}
cmb = med.pnlDetail.Controls[i] as DataComboBox;
if (cmb != null)
{
dataField = med.GetControlBindField(cmb);
if (cmb.ListBind)
{
med.record[dataField] = cmb.SelectedValue;
}
else
{
med.record[dataField] = cmb.Text;
}
continue;
}
//其他控件略
}
}
/**//// <summary>
/// 设置工具栏按钮状态
/// </summary>
protected void SetBtnEnable()
{
bool browseMode = (stateName == StateEnum.Browse || stateName == StateEnum.Init);
for (int i = 0;i < med.toolBar1.Buttons.Count;i++)
{
//略
}
}
/**//// <summary>
/// 设置数据绑定控件外观
/// </summary>
protected void SetControlsAppearance()
{
bool browseMode = (stateName == StateEnum.Browse || stateName == StateEnum.Init);
DataText txt;
for (int i = 0;i < med.pnlDetail.Controls.Count;i++)
{
txt = med.pnlDetail.Controls[i] as DataText;
if (txt != null)
{
bool ronly = browseMode;
if (!ronly)
{
switch (stateName)
{
case StateEnum.Insert:
ronly = !txt.CanInsert;
break;
case StateEnum.Edit:
ronly = !txt.CanEdit;
break;
case StateEnum.Query:
ronly = !txt.CanQuery;
break;
}
}
txt.ReadOnly = ronly;
if (ronly)
{
txt.BackColor = Color.FromKnownColor(KnownColor.Control);
}
else
{
txt.BackColor = Color.FromKnownColor(KnownColor.Window);
}
continue;
}
//其他控件略
}
}
public virtual void btnOKClick() {}
public virtual void btnCancelClick() {}
public virtual void SetControlsPorperty()
{
SetBtnEnable();
SetControlsAppearance();
}
/**//// <summary>
/// 记录更改
/// </summary>
/// <remarks>此处不是严格意义上的数据log,只是作一个记录可以告诉客户端某数据表有更改,可以同步之</remarks>
protected void LogAssignment()
{
AssignmentListBO.LogAssignment(med.record.EntityName);
}
}
上面的数据绑定控件不是.Net Framework自带的控件,而是新加了一些属性来标识这个控件所绑定的字段在新增或修改状态下是否有效,或是这个字段是否用于查询条件设置。只是很简单的扩展。
各个状态子类要做的只是重写一下btnOKClick方法。btnCancelClick方法目前只是一个空架子,因为这个方法并没有状态各异,所以动作直接放到窗体的取消按钮里。
子类只以修改状态为例:
public class EditState : FrmState
{
public EditState(frmData md) : base(md) {stateName = StateEnum.Edit;}
public override void btnOKClick()
{
try
{
SetEntityValue();
med.record[med.record.Key] = med.pnlDetail.Tag;
med.record.Update();
LogAssignment();
}
catch (Exception e)
{
logNet.Error("修改表[" + med.record.EntityName + "]发生错误--" + e.Message);
}
}
}
最后做一个状态管理的类让数据窗体来操作就行了。
public sealed class StateManager
{
private FrmState curState;
private FrmState lastState;
private InitState initState;
private InsertState iState;
private EditState eState;
private QueryState qState;
private BrowseState bState;
private DeleteState dState;
public StateManager(frmData med)
{
initState = new InitState(med);
iState = new InsertState(med);
eState = new EditState(med);
qState = new QueryState(med);
bState = new BrowseState(med);
dState = new DeleteState(med);
curState = initState;
curState.SetControlsPorperty();
lastState = curState;
}
/**//// <summary>
/// 当前状态
/// </summary>
public StateEnum CurrentState
{
get { return curState.stateName; }
}
/**//// <summary>
/// 前一状态
/// </summary>
public StateEnum LastState
{
get { return lastState.stateName; }
}
public void SetInit() {lastState = curState; curState = initState;curState.SetControlsPorperty();}
public void SetInsert() {lastState = curState; curState = iState;curState.SetControlsPorperty();}
public void SetEdit() {lastState = curState; curState = eState;curState.SetControlsPorperty();}
public void SetQuery() {lastState = curState; curState = qState;curState.SetControlsPorperty();}
public void SetBrowse() {lastState = curState; curState = bState;curState.SetControlsPorperty();}
public void SetDelete() {lastState = curState; curState = dState;curState.SetControlsPorperty();}
public void btnOKClick()
{
curState.btnOKClick();
}
public void btnCancelClick()
{
curState.btnCancelClick();
}
internal void SetControlsPorperty()
{
curState.SetControlsPorperty();
}
}
在数据窗体上,只要根据需要调用一些Set***方法,然后适时的去btnOKClick,具体的可以参见上一篇。