最近尝试了一下将 XtraGrid 的初始化工作封装成内部 DSL,例如一个普通的基础数据的增删改查操作的代码会像下面这样:

public partial class UserForm : XtraForm
{
private readonly UserRepository UserRepository;
private readonly UserService UserService;
private readonly GridManager<UserDto> _gridManager; public UserForm(UserRepository UserRepository,
UserService UserService)
{
InitializeComponent(); this.UserRepository = UserRepository;
this.UserService = UserService; _gridManager = new GridManager<UserDto>(userGrid);
_gridManager.CommonInlineEditable()
.Caption("系统用户") .Column("登录名", t => t.LoginName)
.Column("用户名", t => t.Text)
.Column("工号", t => t.Code)
.Column("是否停用", t => t.IsStop)
.Column("录入码1", t => t.InputCode1)
.Column("录入码2", t => t.InputCode2)
.Column("录入码3", t => t.InputCode3)
.Column("排序", t => t.OrderField) .Validate(
v => v.Varify(t => t.LoginName, spec => spec.NotNull()
.Unique(UserRepository.IsLoginNameUnique)
.Msg("登录名不能为空或重复。").End())
.Varify(t => t.Text, spec => spec.NotNull().Msg("用户名不能为空。").End())
.Varify(t => t.Code, spec => spec.NotNull()
.Unique(UserRepository.IsCodeUnique)
.Msg("工号不能为空或重复。").End())) .DataSource(() => UserRepository.GetAllOrderBy(t => t.Text).ToDtoList<User, UserDto>())
.ById(id => new UserDto().FromEntity(UserRepository.GetById(id)))
.SaveAction(dto =>
{
User newEntity = new User();
dto.AssignToEntity(newEntity);
UserService.Save(newEntity);
})
.UpdateAction(dto =>
{
User entity = UserRepository.GetFromCache(dto.Id);
dto.AssignToEntity(entity);
UserService.Update(entity);
})
.DeleteAction(id => UserService.Delete(id));
} private void SaveButton_Click(object sender, EventArgs e)
{
_gridManager.SaveAll();
} private void AddButton_Click(object sender, EventArgs e)
{
_gridManager.AddNew();
} private void DeleteButton_Click(object sender, EventArgs e)
{
_gridManager.Delete();
} private void RefreshButton_Click(object sender, EventArgs e)
{
_gridManager.ReLoadData();
} private void UserForm_Load(object sender, EventArgs e)
{
_gridManager.ReLoadData();
}
}

运行起来像这样:

DevExpress 这套控件功能非常强大,自带的可视化设计器也非常方便。如果控件非常多,或者希望进行复杂的交互,配合 DataLayoutControl 拖拖拽拽就可生成表单窗体。感觉照比宝蓝的 RAD 辉煌时代有过之而无不及。那么为什么还要放弃方便的可视化设计器,费力封装成代码呢?主要考虑到直接设置控件的属性和事件有如下几个缺点
  1. 不便于重构。每个数据列的 FeildName 属性都是将实体或DTO的属性名以字符串的方式赋值,将来如果想改名、移除或移动一个属性、移除/合并/拆分实体,无法直接用重构工具精确地找到所有引用到该属性/实体的控件,只能用字符串查找的方法,低效而且容易有遗漏,这也是项目后期进行实体级别的重构让人望而却步的原因之一。虽然使用DTO能缓解此问题,但终究让人不爽。 而使用类似 Column("登录名", t => t.LoginName) 这样的语句进行配置就不会有这种问题了。
  2. 导致大量的重复代码。像基础数据维护这种功能,大部分都是简单的增删改查,往往只使用了 Grid 控件功能的一个子集而且大部分都比较相像。例如上图所示,需要显示查找栏,不需要显示分组栏,要显示Grid的标题栏,自动列宽就可以等等。这些属性一个一个地进行设置的话,也挺费力气。当需要增加一个“部门管理”的时候,感觉跟“用户管理” 差不多,就会把“用户管理”这个 Grid 复制过来,甚至可能把 “用户管理”整个窗体的代码完全复制过来,再进行修改。这其实也是一种重复代码。对于把 DRY(don't repeat yourself) 作为一项基本原则的人来说这是无法忍受的。
  3. 代码意图不明显,可读性差。虽然只是简单的增删改查,但是麻雀虽小五脏俱全——需要进行前端验证提升用户体验,验证不通过时要以友好的方式显示错误信息,按ESC键能够撤销修改;修改过的数据行希望以粗体显示,按保存按钮保存之后要恢复成正常字体;保存时要判断是修改还是新增数据;对于引用了其它的实体的属性或者枚举类型的属性要做特殊处理。如果前端验证失败,而后用户又修改成可以通过验证的数据之后直接按了保存按钮,虽然能保存成功,但是如果不做特殊处理的话那个红色的叉叉并不会自动消失。。。要把这些功能和细节全处理好的话往往要在好几个事件里面写代码配合着一起完成。这样单独看某一块代码的话往往不知道它是做什么的。不但新人掌握起来比较费劲,过一段时间我们自己要想读懂都要费一番力气。我们希望能把相关的功能集中起来,并且隐藏实现细节。

所以这次设计这组操作 XtraGrid 的内部 DSL 的目标就是:
  1. 可读性第一。不追求读起来像自然语言,但希望相关的内容能在一处,读起来自然流畅,尽量隐藏实现细节。从实际效果来看,由于有 lambda 表达式和一些语法糖可用,虽然有一些杂音和少量实现细节,基本上还挺让人满意。
  2. 不使用字符串硬编码。不太地道地借助了 FluentNHibernate 的帮助类库轻松实现,以后再彻底把它的代码偷过来。
  3. DSL 实现代价最好小一点,作为一个薄的封装层,并且可以随需演进。出于希望DSL的实现越简单越好的想法,并没有使用大量自己的对象模型,而是使用DSL的API直接操作Grid控件的属性。这样一开始确实挺省事,但是往往出于实现上的困难而把实现代码越搞越乱,这个挺让人纠结。
  4. 减少冗余代码。冗余代码还是有一点,例如 t=>t.Code 在 Column 和 Validate 中就出现了 2 次,出于实现上的困难暂时只能这样了。

总之基本还算不错,由于实现代码还很不成熟就先不贴出来了。

完全使用一组 DSL 来操作 Grid 控件的更多相关文章

  1. 扩展BindingList,防止增加、删除项时自动更新界面而不出现“跨线程操作界面控件 corss thread operation”异常

    在做界面程序时,常常需要一些数据类,界面元素通过绑定等方式显示出数据,然而由于UI线程不是线程安全的,一般都需要通过Invoke等方式来调用界面控件.但对于数据绑定bindingList而言,没法响应 ...

  2. WPF平台Grid控件性能比较

    WPF官方发布第一个版本至今已经有10年了, 我们几乎在同时也开始了XAML开发.即使经过多年打造,我们依旧尝试提高:我们真的成功打造了高效灵活的控件吗?我没有在其他地方找到任何关于优秀的WPF表格性 ...

  3. C#多线程操作界面控件的解决方案(转)

    C#中利用委托实现多线程跨线程操作 - 张小鱼 2010-10-22 08:38 在使用VS2005的时候,如果你从非创建这个控件的线程中访问这个控件或者操作这个控件的话就会抛出这个异常.这是微软为了 ...

  4. Grid控件

    Grid控件是WPF布局容器中功能最强大.最灵活的控件.Grid控件基本上能够完成其他WPF容器控件所能完成的功能,Microsoft建议大多数界面的布局都使用Grid控件来实现,因此默认情况下.vs ...

  5. [WinForm]WinForm跨线程UI操作常用控件类大全

    前言 在C#开发的WinForm窗体程序开发的时候,经常会使用多线程处理一些比较耗时之类的操作.不过会有一个问题:就是涉及到跨线程操作UI元素. 相信才开始接触的人一定会遇上这个问题. 为了解决这个问 ...

  6. FineUI Grid控件高度自适应

    引言 页面里使用f:Grid控件,添加分页功能,然后高度填充整个页面. 如何使用 使用FineUI 控件的每个页面都有一个f:PageManager控件,它包含属性:AutoSizePanelID,设 ...

  7. Jquery 操作Html 控件 CheckBox、Radio、Select 控件 【转】http://www.cnblogs.com/lxblog/archive/2013/01/09/2853056.html

    Jquery 操作Html 控件 CheckBox.Radio.Select 控件   在使用 Javascript 编写前台脚本的时候,经常会操作 Html 控件,比如 checkbox.radio ...

  8. FineUI Grid控件右键菜单的实现

    FineUI官方Demo上一直没有Grid右键菜单的实现,其实从4.1.x的版本开始,允许添加自定义的事件监听(Listeners),所以要实现这个功能已经相当容易了. ExtJs右键菜单有很多种,对 ...

  9. 实现控件WPF(4)----Grid控件实现六方格

    PS:今天上午,非常郁闷,有很多简单基础的问题搞得我有些迷茫,哎,代码几天不写就忘.目前又不当COO,还是得用心记代码哦! 利用Grid控件能很轻松帮助我们实现各种布局.上面就是一个通过Grid单元格 ...

随机推荐

  1. [转]IIS6.0迁移至IIS7.0

    原文地址:http://www.splaybow.com/post/iis-6.0-7.0.html 公司的项目需要迁移到IIS7的目标机器中 在此做记录 原来server 2003系统 迁到2008 ...

  2. Beginning Scala study note(4) Functional Programming in Scala

    1. Functional programming treats computation as the evaluation of mathematical and avoids state and ...

  3. 安装hadoop+zookeeper ha

    安装hadoop+zookeeper ha 前期工作配置好网络和主机名和关闭防火墙 chkconfig iptables off //关闭防火墙 1.安装好java并配置好相关变量 (/etc/pro ...

  4. P87LPC760/61/62/64/67/68/69/78/79芯片解密单片机破解价格

    NXP恩智浦P87LPC760/61/62/64/67/68/69/78/79芯片解密单片机破解 NXP LPC700系列单片机解密型号: P87LPC759.P87LPC760.P87LPC761. ...

  5. locky勒索样本分析

    前段时间收到locky样本,分析之后遂做一个分析. 样本如下所示,一般locky勒索的先决条件是一个js的脚本,脚本经过了复杂的混淆,主要用于下载该样本文件并运行,. 解密 样本本身进行了保护,通过i ...

  6. 窗体Showmedol 遇到的奇怪异常-->进阶问题

    procedure SetTransparentForm (popupFrm:TForm;Color:TColor;AlphaBlendValue:Integer); var FrmTranspare ...

  7. LeetCode 344. Reverse String

    Problem: Write a function that takes a string as input and returns the string reversed. Example: Giv ...

  8. phoneGap

    1.安装参考http://blog.csdn.net/mage694/article/details/16846331 2.API  参考http://phonegap.com/developer/

  9. iOS 图片文件格式判断、圆角图片

    1.圆角图片 // 设置圆形图片(放到分类中使用) - (UIImage *)cutCircleImage { UIGraphicsBeginImageContextWithOptions(self. ...

  10. [转]WebPack 常用功能介绍

    概述 Webpack是一款用户打包前端模块的工具.主要是用来打包在浏览器端使用的javascript的.同时也能转换.捆绑.打包其他的静态资源,包括css.image.font file.templa ...