从零开始
概述
我承认标题有点哗众取宠,实在想不出好名字,随便起的。前面讲了很多理论性的东西,大家要把他们用起来估计还有一定的难度。这里我把刚做完的一张界面作为范例,详细解说下怎么进行CSMS2的开发。
真的从零开始了
首先我们需要创建一个窗体,一般情况下都插入DevExpress Form v10.1,真的碰到很多人还给我插入WinForm或v6的窗体或控件。所以这里还是需要强调一下。如果有低版本的请卸载掉,免得搞错了。其次是窗体的起名,这里我们不考大家的英文,都是前两个全拼,后面首字母缩写。再加上前缀Frm。窗体都建立在某个目录下。如果不确定的,向我或自己的项目经理确认。这里我们将在BiaoWuGL下建立一个FrmFuHeDJ的窗体。
其次,给窗体写注释
拿到要做的窗体后,第一件事情就是给在头部加入如下代码:
//创建日期:<创建日期,2012-05-24>
//创建作者:<张易,zhangyi@shanghai3h.com>
//功能说明: 表务工单复核登记界面
//********************************************************
不多解释,这里要注意的是,谁做的界面把名字都写上。以前做原型的人的名字可以忽略。对于大部分重构的界面,把名字也替换掉。如果是小修小改的,直接在代码上加注释说明了。还有多一点事界面的功能描述加上,有时光看个拼音还不知道是什么界面。
然后我们将界面分成多个region
第一个是变量region,把界面上用到的属性、域成员变量都放在这这个region下。如果是属性记得首字母大写。如果可能的话直接写{get;set}。域成员变量用"_"开头。
属性:
/// 账号
/// </summary>
public string S_CID { get; set; }
域成员变量:
第二个区域是事件,所有事件都放在这里,纠结点的就是构造函数了。看放在事件里的人多,那我们也放在事件里。这里很多人刚开始整理的蛮好的,到后面就乱来了。还有就是经常将一些事件的说明漏了。比如一个界面加载,如果没什么好说的,你就写个界面加载,如果有些特别要注意的,可以在上面写的在详细点。例如:
/// 站点改变,触发册本改变
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void ComTreeZhanDian_SelectValueChange(object sender, EventArgs e)
{
_Proxy.GetCeBenXXBySTAsync(ComTreeZhanDian.SelectValue);
}
这里要说的也就是一个持之以恒了。
第三个区域是方法。如果在引擎里,大家还可以分成公共方法和私有方法等区域。在窗体里一般情况下都是私有方法。就直接放一个方法region里了。这里要注意程序为什么有了方法。没方法全事件理论上也能写窗体。不就是为了代码重用和看起来简洁吗!大家要多注意归纳总结。将公共的东西提炼出来。放到方法里。将更加通用的方法提炼出来放到基础架构中。我们的目的是在写一篇给自己和别人能看懂的“小说”。
接下来,就要真正开始开发了
首先我们需要打开需求文档设计界面。有部分界面和以前的项目差不多,可以直接复制界面过来。做界面时注意控件的命名。首字母需要大写,其次是缩写的控件名,在后面是名称。例如TxtKeHuBH、BtnChaXun。画界面的时候注意Dock和Anchor的使用。经常看到有些界面放到宽屏或非宽屏,显示效果就不一样了。这里可能就是某个按钮做了Anchor定位,某个没做。
其次我们需要设计对应的应用层方法,用复核登记界面举例,它里面有两个按钮两个按钮一个“查询”、一个“登记”是需要和后台数据库做交互的。这时我们就需要在BIAOWUGL.svc中添加两个方法FuHeDJ_ChaXun、FuHeDJ_DengJi。命名规则是界面名称+下划线+功能名称。
/// 复核登记查询
/// </summary>
/// <param name="dto"></param>
/// <returns></returns>
public DataTable FuHeDJ_ChaXun(FuHeDJSearchDTO dto)
{
return DynamicSQL.GetTable(dto);
}
/// <summary>
/// 复核登记
/// </summary>
/// <param name="message">错误消息</param>
/// <param name="dto">dto</param>
/// <param name="Entity.BW_YUYUEXX">预约信息</param>
/// <param name="login">登陆信息</param>
/// <returns>true or false</returns>
public bool FuHeDJ_DengJi(out string message, FuHeDJDTO dto,Entity.BW_YUYUEXX yuyuexx, Entity.LoginInfo login)
{
return BiaoWuGLEngine.DengJi(out message, dto, yuyuexx, login);
}
然后我们需要考虑应该需要哪些参数,对于查询来说。以前已经介绍过了,只需要传入对应的查询dto即可。对于复核登记。我们需要传入登记信息、预约信息,一般情况下我们也会传入登录信息,让业务逻辑层将对应的信息更新到操作人字段等。如果出现异常,通过message接受错误信息。这里要注意如下情况:
- WCF中一定要实现对应的接口,接口中需要加入OperationContract的特性
public class BiaoWuGL : IBiaoWuGL
public interface IBiaoWuGL
{
[OperationContract]
DataTable FuHeDJ_ChaXun(FuHeDJSearchDTO dto);
[OperationContract]
bool FuHeDJ_DengJi(out string message, FuHeDJDTO dto, Entity.BW_YUYUEXX yuyuexx, Entity.LoginInfo login);
}
- DTO的设计尽量精简
经常看到一些庞大的DTO,美其名曰通用性强。其实这种设计完全是败笔,设想一下这里如果暴露一个BW_GONGDAN的DTO,确实也能操作。但这时候你让界面开发人员到底传多少参数给你那,哪些属性需要赋值界面开发人员根本不知道。所以dto需要尽量的精简,不要然我们的界面层充斥太多的赋值操作。另外这对开发模式其实也是一种变革。以前我们一个人从头做到尾,我想在哪里实现业务逻辑还不都是我一个人的工作。所以这种变革也涉及到开发模式,尽量安排2个人进行一张界面的开发。一个做表现层、应用层。还有一个做业务逻辑等。要不然确实有点精神分裂的感觉。
应用层设计好后,由于对应的业务逻辑还没有实现,我们直接在方法里return null,reutrn ture就可以了,等待编写业务逻辑的人员将代码补充进来。其实应用层到底应该放什么方法,传哪些参数,应该需要项目经理设计一下,但有的项目经理比较忙,如果这部分设计上有什么问题,而各组的项目经理又不在,可以向我咨询。
表现层开发
首先我们要引用WCF,一般情况下引用已经有了。则我们只要对前面添加的应用层方法做更新。然后我们需要思考初识化时需要绑定的控件。代码如下:
/// 构造函数
/// </summary>
public FrmFuHeDJ()
{
InitializeComponent();
InitControl();
}
/// 初始化控件
/// </summary>
public void InitControl()
{
BindDataHelper.BindDropDownList(ClientCacheHelper.GetYHWordsByID(YHWordsType.外复原因), LueWaiFuYY,true);
BindDataHelper.BindDropDownList(ClientCacheHelper.GetWordsByID(WordsType.外复类型), LueWaiFuLX, true);
ComTreeZhanDian.BindS_ST();
_Proxy.GetCeBenXXBySTCompleted += new EventHandler<KeHuGXService.GetCeBenXXBySTCompletedEventArgs>(_Proxy_GetCeBenXXBySTCompleted);
InitGridControl();
}
初识化控件,主要是做一些绑定,和赋默认值。对于绑定类似dropDownList的控件使用BindDataHelper类,对于站点,使用站点控件。对于绑定DropDownList就不做过多介绍了,大家看对应绑定数据的介绍。这里用到个小技巧处理站点和册本的联动。用了异步调用的方式,具体代码如下:
/// 站点改变,触发册本改变
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void ComTreeZhanDian_SelectValueChange(object sender, EventArgs e)
{
_Proxy.GetCeBenXXBySTAsync(ComTreeZhanDian.SelectValue);
}
/// <summary>
/// 异步调用册本绑定
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void _Proxy_GetCeBenXXBySTCompleted(object sender, KeHuGXService.GetCeBenXXBySTCompletedEventArgs e)
{
DataTable dt = e.Result;
BindDataHelper.BindDropDownList(dt, LueCH, true);
}
接下来我们对Grid进行默认赋值,Grid控件,请大家都拖拽MyGridControl控件,这里对以前的GridControl做了一些扩展。
/// 初始化GridControl
/// </summary>
private void InitGridControl()
{
List<UserControl.MyGridControl.ColumnInitInfo> lst = new List<UserControl.MyGridControl.ColumnInitInfo>();
lst.Add(new UserControl.MyGridControl.ColumnInitInfo { Caption = "客户编号"});
lst.Add(new UserControl.MyGridControl.ColumnInitInfo { Caption = "账号"});
lst.Add(new UserControl.MyGridControl.ColumnInitInfo { Caption = "册本号"});
lst.Add(new UserControl.MyGridControl.ColumnInitInfo { Caption = "户名"});
lst.Add(new UserControl.MyGridControl.ColumnInitInfo { Caption = "地址"});
lst.Add(new UserControl.MyGridControl.ColumnInitInfo { Caption = "口径"});
lst.Add(new UserControl.MyGridControl.ColumnInitInfo { Caption = "表分类"});
lst.Add(new UserControl.MyGridControl.ColumnInitInfo { Caption = "表类型"});
lst.Add(new UserControl.MyGridControl.ColumnInitInfo { Caption = "表型码"});
lst.Add(new UserControl.MyGridControl.ColumnInitInfo { Caption = "表号"});
GCDaiDengJWF.ColumnInitInfoCollection = lst;
GCDaiDengJWF.HasCheckColumn = true;
GCDaiDengJWF.IsRadio = true;
}
由于我想不要再对GridControl的每个列做添加,所以增加了一个ColumnInitInfoCollection,默认情况下赋一下Caption就可以了,如果需要改变列宽,里面也有对应的设置。HasCheckColumn是表示是否添加Check列,IsRadio是表示是否只能单选。以后如果用到Image图标和右键功能等,我在增加一下了,总之大家用这个控件就对了。
做完初始化操作后我们需要对一些事件进行操作了。
查看界面我们发现有3个按钮、分别是查询、添加预约信息、登记。下面我们分别对3个按钮进行事件添加。
先来看查询,首先我们需要对查询DTO赋值,然后调用数据校验方法进行数据验证。然后将查询的结果返回,如果没有查到数据则给出提示。为了简化掉一些验证和提示的操作,这里用了一个委托方法的小技巧,简化部分代码。
searchDTO = GetFuHeDJSearchDTO();
Func<DataTable> func = () =>
{
BiaoWuGLService.BiaoWuGLClient client = new BiaoWuGLService.BiaoWuGLClient();
DataTable dt = client.FuHeDJ_ChaXun(searchDTO);
client.Close();
return dt;
};
OperateHelper.Search(searchDTO, GCDaiDengJWF, func);
大家可以看到除了查询和赋值,其他的都隐藏起来了。我们在看下OperateHelper.Search做了点什么。
/// 简化查询操作
/// </summary>
/// <typeparam name="DTO"></typeparam>
/// <param name="successMessage"></param>
/// <param name="dto"></param>
/// <param name="func"></param>
/// <returns></returns>
public static void Search<DTO>(DTO dto,dynamic gc, Func<DataTable> func)
{
string message;
if (!DataAnnotationsEntityValidator.IsValid(dto, out message))
{
AlterHelper.MessageBoxShowInfo(message);
return;
}
DataTable dt = func();
if (null != dt && dt.Rows.Count > 0)
{
gc.DataSource = dt;
}
else
{
AlterHelper.MessageBoxShowInfo("COMMON021");
}
}
看代码就知道做了校验,绑定,和一些提示信息。把公共的抽取出来了。尽管我挺讨厌委托方法的语法的,但是等省略点代码我还是加上了。这里注意对WCF方法的关闭。
然后我们看登记事件。
要做的事情和查询差不多,都是对实体赋值,检验、调业务逻辑、最后返回结果,如果登记成功则,结果集刷新,清空登记记录。和前面一样这里也用了个委托方法。
/// 登记
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void BtnDengJi_Click(object sender, EventArgs e)
{
FuHeDJDTO dto = GetFuHeDJDTO();
Func<string> func = () =>
{
string message = string.Empty;
BiaoWuGLService.BiaoWuGLClient client = new BiaoWuGLService.BiaoWuGLClient();
client.FuHeDJ_DengJi(out message, dto,(Entity.BW_YUYUEXX)ClientCacheHelper.GetCache("BW_YUYUEXX"), Entity.LoginInfo.CurrentUser);
client.Close();
return message;
};
bool result = OperateHelper.Save("BIAOWUGL001", dto, func);
if (result)
GVDaiDengJWF.ReDataBind();
}
这样我们就把重复劳动分出去了,对于OperateHelper.Save操作代码如下:
/// 简化保存操作
/// </summary>
/// <typeparam name="DTO"></typeparam>
/// <param name="successMessage"></param>
/// <param name="dto"></param>
/// <param name="func"></param>
/// <returns></returns>
public static bool Save<DTO>(string successMessage,DTO dto, Func<string> func)
{
string message = string.Empty;
if (!DataAnnotationsEntityValidator.IsValid(dto, out message))
{
AlterHelper.MessageBoxShowInfo(message);
return false;
}
message = func();
if (!string.IsNullOrEmpty(message))
{
AlterHelper.MessageBoxShowError(message);
return false;
}
else
{
AlterHelper.MessageBoxShowInfo(successMessage);
}
return true;
}
添加预约信息,其实就是show一个窗体,代码如下
/// 添加预约信息
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void BtnTianJiaYYXX_Click(object sender, EventArgs e)
{
string s = GVDaiDengJWF.GetKey("S_CID");
if (!string.IsNullOrEmpty(s))
{
FrmYuYueXX frm = new FrmYuYueXX(GVDaiDengJWF.GetKey("S_CID"));
frm.ShowDialog();
}
else
{
AlterHelper.MessageBoxShowInfo("BIAOWUGL003");
}
}
这里需要说明的是一些缓存的应用,比如预约信息。用户需要保存在内存中,这个我们可以使用ClientCacheHelper将其先保存在内存里,然后界面直接用ClientCacheHelper.GetCache获取内存数据。还有就是Grid提供了一些GetKey、GetCheckInfo的公共方法用于简化获取。如果还有什么不清楚的直接来问我吧,我写文章还是不够精细的。
关于DOT的一些验证,可以参考数据验证章节,这样我们除了业务逻辑,界面就都完成了。个人觉得对dto的赋值还是比较麻烦,最好能调个方法自己循环去。