完全使用一组 DSL 来操作 Grid 控件
最近尝试了一下将 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 控件的更多相关文章
- 扩展BindingList,防止增加、删除项时自动更新界面而不出现“跨线程操作界面控件 corss thread operation”异常
在做界面程序时,常常需要一些数据类,界面元素通过绑定等方式显示出数据,然而由于UI线程不是线程安全的,一般都需要通过Invoke等方式来调用界面控件.但对于数据绑定bindingList而言,没法响应 ...
- WPF平台Grid控件性能比较
WPF官方发布第一个版本至今已经有10年了, 我们几乎在同时也开始了XAML开发.即使经过多年打造,我们依旧尝试提高:我们真的成功打造了高效灵活的控件吗?我没有在其他地方找到任何关于优秀的WPF表格性 ...
- C#多线程操作界面控件的解决方案(转)
C#中利用委托实现多线程跨线程操作 - 张小鱼 2010-10-22 08:38 在使用VS2005的时候,如果你从非创建这个控件的线程中访问这个控件或者操作这个控件的话就会抛出这个异常.这是微软为了 ...
- Grid控件
Grid控件是WPF布局容器中功能最强大.最灵活的控件.Grid控件基本上能够完成其他WPF容器控件所能完成的功能,Microsoft建议大多数界面的布局都使用Grid控件来实现,因此默认情况下.vs ...
- [WinForm]WinForm跨线程UI操作常用控件类大全
前言 在C#开发的WinForm窗体程序开发的时候,经常会使用多线程处理一些比较耗时之类的操作.不过会有一个问题:就是涉及到跨线程操作UI元素. 相信才开始接触的人一定会遇上这个问题. 为了解决这个问 ...
- FineUI Grid控件高度自适应
引言 页面里使用f:Grid控件,添加分页功能,然后高度填充整个页面. 如何使用 使用FineUI 控件的每个页面都有一个f:PageManager控件,它包含属性:AutoSizePanelID,设 ...
- 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 ...
- FineUI Grid控件右键菜单的实现
FineUI官方Demo上一直没有Grid右键菜单的实现,其实从4.1.x的版本开始,允许添加自定义的事件监听(Listeners),所以要实现这个功能已经相当容易了. ExtJs右键菜单有很多种,对 ...
- 实现控件WPF(4)----Grid控件实现六方格
PS:今天上午,非常郁闷,有很多简单基础的问题搞得我有些迷茫,哎,代码几天不写就忘.目前又不当COO,还是得用心记代码哦! 利用Grid控件能很轻松帮助我们实现各种布局.上面就是一个通过Grid单元格 ...
随机推荐
- 体验Visual Studio 2015 之 MVC - 视图组建
VS2015 PERVIEW中可以创建MVC 项目. 我们可以 发现有几大亮点. 首先我们看目录结构: 当前项目包含两个主要的文件夹:Solution Items .src 很明显src文件夹下为当前 ...
- RSA密钥之C#格式与Java格式转换
前言 最近由于项目需求,服务端由c#编写,客户端由java编写.通信数据使用RSA非对称加密.但是java和c#生成的密钥格式是不一样的,所以需要转换格式才可以正常使用.网上搜到使用java进行格式转 ...
- 进击的Python【第十章】:Python的socket高级应用(多进程,协程与异步)
Python的socket高级应用(多进程,协程与异步)
- Hibernate 非常见异常集合
异常一:org.hibernate.AnnotationException: Collection has neither generic type or OneToMany.targetEntity ...
- Oracle VM VirtualBox 安装CentOS 配置图形界面记录
su yum groupinstall "X Window System" -y yum groupinstall "Desktop" -y 最后运行#star ...
- Android中轻松显示Gif图片
android中现在没有直接显示gif的view,只能通过mediaplay来显示,且还常常不能正常显示出来,为此写了这个gifview,其用法和imageview一样使用方法:1-把GifView. ...
- 机器学习之K-近邻算法
机器学习可分为监督学习和无监督学习.有监督学习就是有具体的分类信息,比如用来判定输入的是输入[a,b,c]中的一类:无监督学习就是不清楚最后的分类情况,也不会给目标值. K-近邻算法属于一种监督学习分 ...
- 动作手游实时PVP技术揭密(服务器篇)
前言 我们的游戏是一款以忍者格斗为题材的ACT游戏,其主打的玩法是PVE推图及PVP 竞技.在剧情模式中,高度还原剧情再次使不少玩家泪目.而竞技场的乐趣,伴随着赛季和各种赛事相继而来,也深受玩家喜爱, ...
- 该不该在C#中使用var关键词
作为一个并不勤快的程序猿,在项目开发过程中总是想尽办法少写代码,对var关键词的使用自然不会放过,几乎在每个能使用var的地方都用了var,对此,很多朋友同事给出了不同的建议,觉得能不使用var关键词 ...
- MongoDB CURD 介绍
MongoDB是用JSON格式的field和value成对的documents存储数据,documents类似于编程语言中的key value 键值对(例如:dictionaries,hashes,m ...