使⽤.NETWinForm开发所见即所得的IDE开发环境,实现不写代
码直接⽣成应⽤程序
:
SailingEase WinForm Framework WinForm开发框架开发⼿册:
直接切⼊正题,这是我09年到11年左右业余时间编写的项⽬,最初的想法很简单,做⼀个能拖拖拽拽就直接⽣成应⽤程序的⼯具,不⽤写代码,把能想到的业务操作全部封装起来,通过配置的⽅式把这些业务操作组织起来运⾏。
项⽬的核⼼功能已经基本实现,但12年之后我基本停⽌了这⽅⾯的开发,现在翻出来在这⾥写出来想和⼤家交流⼀下。
鉴于篇幅和精⼒的原因,请原谅我这篇博⽂对于技术实现的具体细节谈的不是很多,只能算是⼀个概述。对业务的说明也不多,我想⼤家都是技术流,应该⼀看就明⽩。
写这个项⽬的时间是五六年前,现在回过头去看,有很多不⾜之处,设计上的,技术上的都有,加上当时技术⼒有限,不⾜之处还请指正,谢谢。
后续是否会写⼀个系列的博⽂详细的分析讲解实现⽅法,我暂时也没有想好,主要是没有太多时间,我现在基本⼜回到了当初每天只睡四五个⼩时的状态。
如果此篇博⽂有点⼉价值,给个推荐呗 ^_^
项⽬使⽤了 .Net Framework 3.5 开发,分为两⼤块:IDE 和运⾏时(解析器)
IDE中开发的项⽬在打包后⽣成 zip 格式的包,解析器通过读取 zip 包实时解析运⾏,有点类似中间语⾔的概念,但我这⾥⽣成的 zip 包中主要以 xml ⽂件为主,通过 xml ⽂件对项⽬的 UI,业务,数据结构进⾏描述。
到此可以看出,运⾏时本⾝并不⼀定是 .Net 或 WinForm 的,⽽是可以使⽤任何平台或语⾔实现,只要读取 zip ⽂件和 xml ⽂件并解析即可。
事实上我⾃⼰实现的默认运⾏时也不是 WinForm,⽽是⽤了 Silverlight。
再简单说说 IDE 的设计思路,⼏个主要的设计⽬标如下:
1.像 Visual Studio ⼀样
有可视化的环境,拖拖拽拽界⾯就出来了。
2.模块化设计
功能模块全部独⽴,解耦,以插件的形式存在于主程序(宿主)中。
2.不要写代码,业务通过界⾯,向导进⾏配置
拖⼀个按钮上去,想要单击时做⼀件事情,就先把按钮拖上去,然后设置这个按钮的事件序列,配置对应的事件。
3.把事件这个概念抽象并封装起来
如“保存数据”这个事件,配置好数据的来源,如窗体上的数据,或系统数据,再配置好要保存的⽬标,某种数据实体,即可,这个事件被添加到某个事件序列,如按钮的单击事件序列中,项⽬被运⾏时解析时,就会按钮这个逻辑执⾏。
4.对数据操作要有⼀定的⾃由度
除了基本的向导式配置以外,要能满⾜特殊需求,⽐如⽀持⾃定义 sql 语句。但是⾃定义 sql 语句怎样与数据源,⽬标交互呢?我设计了⼀种简单的表达⽅法,如 UPDATE FROM [User] SET [Name] = {Name} WHERE [Id]={System.UserId}
5.对数据库数据表的操作怎样交互
就是将其抽象为“数据实体”,数据实体也在 IDE 中由⽤户⾃⼰定义,定义的过程类似于 SqlServer,定义好数据实体以后,在 IDE 中进⾏设计时,通过数据实体来抽象对数据库、表的操作,在打包项⽬时,可以根据定义的数据实体,⽣成多种数据库,如 SqlServer,Mysql 等。
6.资源⽂件的管理
在项⽬中必然要引⽤到外部资源,这部分外部资源,怎样引⼊,管理,打包呢?我在 IDE 中设计了独⽴的资源管理器,在 IDE 中设计 UI 时,通过资源管理器引⽤资源,打包时,将资源打包到 zip ⽂件中。
7.打包前的静态编译检查
类似于我们在 Visual Studio 中写程序,编译时如果有错误就会出现警告或错误提⽰。在这个 IDE 中,也必须有同样的功能。当引⽤的数据实体被删除,数据项不存在,引⽤的资源⽂件不存在,以及事件配置中⼀些问题出现时,能够实时,并在打包项⽬时指出这些错误的具体位置。
8.⽀持嵌⼊脚本
能够在事件序列中添加⾃定义脚本,⽀持在运⾏时动态解析或者调⽤某种脚本语⾔。此功能有所设计,但并未开发。
9.⽀持插件
此处插件⽀持指的是 IDE 层⾯能够⽀持插件,类似 Visaul Studio 或者 Eclipse 的插件机制,我当时使⽤的是 .NET 管线技术(很冷门),实现了相关DEMO,但是没有集成到IDE中。
10.IDE界⾯⽀持多国语⾔
⽬前IDE完整⽀持多国语⾔,所有⽂本均使⽤了资源,但是我没有直接使⽤资源⽂件,⽽是将其强类型化了,具体实现⽅式下⽂详述。
在设计开发这个 IDE 的早期,我并没有给⾃⼰设定如此详细的⽬标,现在写其实更多的是回顾和总结。
在这个项⽬中,⼤量使⽤了 GDI+ 绘图,说复杂,给你调⽤的接⼝也就那么多,说简单,⽤ GDI+ ⾃⼰写⼀个功能完备的 WinForm 控件,分分钟教你重新做⼈。在这个项⽬中,⼏乎所有的界⾯元素都是我⾃⼰⽤ GDI+ 绘制的,使⽤的第三⽅控件不多。后⾯我会写⼀些这⽅⾯的感想。
下⾯罗列⼀些技术难点和主要功能点,有些细节可能没有试着做过都不会意识到那是个问题。
1.⼯具栏按钮/右键菜单的状态控制
就是控制状态栏上按钮可⽤不可⽤,可见不可见,绝⼤多数时候这不是个问题,但是作为⼀个 IDE,⼯具栏上的各种按钮⾮常多,且按钮的状态和当前设计器中的选中元素个数,选中元素类型,甚⾄选中元素⾃⾝的状态等等相关,还有些按钮是特定控件或元素提供的,怎样统⼀控制这些按钮的状态?
黄⾊背景部分是动态挂载上去的,状态的控制在后⽂中说明。
此处右键菜单指的是窗体设计器中的右键菜单,在窗体设计器中,右键菜单⽐较复杂,不同控件的右键菜单有所不同,有共通的项⽬,有特殊的项⽬,以及状态是不是灰掉可能和控件本⾝的某些因素有关,但右键菜单本⾝是不可能通过处于设计状态的控件⾃⾝提供的,所以此处如何把控件特有的菜单项挂载上去,⼜怎样控制它们的状态?注意设计器本⾝和⽤来设计的控件是解耦合的。
注意这个例⼦,右键 DataGrid 产⽣的右键菜单中存在“添加列” 和“编辑列”两个特殊的项⽬。怎样写代码 自己做编程
此处当我选中 DataGrid 时,属性⽹格下⽅也会出现这两个项⽬,这⾥先提⼀个关键概念,叫做“谓词”,这是⼀个 DesignSurface 中的概念,后⽂再详述。
此处实际上我实现了⼀个独⽴的菜单(包括⼯具栏项⽬)的管理器,并⾮直接创建 MenuItem 之类的实例去使⽤,这个管理器也是独⽴于业务进⾏设计的,对菜单项的各种状态,⾏为都进⾏了抽象与封
装。在管理器层⾯统⼀调度这些菜单项,通过⼀定的机制使菜单项的状态与业务状态关联起来,不允许外部代码直接修改菜单项的状态,整套机制本⾝,与菜单项在UI层⾯的实现也是⽆关解耦的,最终⽣成可见菜单项时,才会⽣成特定的控件,如 MenuItem,也可以换成其它任何菜单项控件,不影响管理器的功能与逻辑。
我记得当时我研究了⼏个IDE的设计细节,包括 Visual Studio,应该都采⽤了类似的机制,好吧我承认是我研究之后借鉴了它们的机制。
这个问题我放在⾸位,是我意识到这个问题在⼤型软件中,真的是个很⼤的问题,我现在参与开发的⼀款电⽓化CAD软件中,就存在这个问题,但是他们早期并未意识到这个问题,也谈不上能很好的解决,⼏千个菜单项,⼯具栏按钮项,直接硬编码,对他们的状态控制也谈不上成体系,就是粗暴的硬编码,现在的维护,修改,调整都异常痛苦。
2.窗体设计器
最初我是⾃⼰⽤ GDI+ 写了⼀个简单的设计器模型,⽀持拖拽,绘制,动态对齐等等功能,但是越往后越复杂,⽐如绘制⼀个 DataGrid,你不能光是⼀个框框,你要⾃⼰去绘制它的列,列头,如果要绘制⼀个图⽚框,你就要⾃⼰去绘制它的图⽚内容,要考虑图⽚的缩放⽅式等等细节,如果要⼀条道⾛到⿊完全⾃⼰实现,成本将⾮常⾼昂。
先看看早期直接使⽤ GDI+ 实现的效果:
下⾯是直接使⽤微软 DesignSurface 效果:
和 Visual Studio 效果⼀样,不过这⾥需要注意的是 DesignSurface 仅仅也只是提供了基本的窗体设计能⼒(图中右侧部分),⽐我上⾯GDI+⾃⼰写的功能多不了多少,但是不⽤⾃⼰绘制控件的外观,其它辅助功能都是需要⾃⾏开发的。
这⾥要注意的⼀点是窗体设计器中允许被设计的控件们,是与设计器本⾝,与IDE解耦的,是完全独⽴实现的,后期添加新控件,修改控件都与IDE⽆关,这个地⽅的难点毅然是解耦合,各种解耦合。
左侧的属性列表是⾃⾏开发的,.Net Framework 中确实提供了 PropertyGrid 控件,但是对于⾼阶开发此处并不适⽤,有很多制限,下⽂详述。
3.⼯具箱
⼯具箱本⾝是独⽴实现的,不依赖其所处的窗体设计器,同时它⾃⾝所承载的控件,也是动态载⼊的,后期允许第三⽅插件挂载控件到⼯具箱中。
这个地⽅需要注意的不多,⼀个是动态载⼊控件,另⼀个就是在和窗体设计器交互的时候,⽐如我拖⼀个控件到设计器上,这⾥是需要对接 DesignSurface 的。
4.属性⽹格(PropertyGrid)
.Net Framework 中提供了 PropertyGrid 控件,可以实现对对象实例的属性编辑功能,但是难于扩展与⾃定义,我此处需要个性化定制的地⽅⽐较多,所以选择⾃⼰实现⼀个。
主要实现了以下功能
1)对于单个对象实例,列出它的属性(Property,下同),以及属性的值,如果属性值与默认值不同,能够粗体显⽰。
2)对于特殊的属性,提供对应的扩展编辑器,如颜⾊属性,在点击后应该提供⼀个颜⾊选择器。且这些扩展编辑器,是与属性⽹格本⾝解偶的。
3)如果同时设置了多个不同类型(Type)的对象实例,例如在窗体设计器中框选了多个控件,这个场景就复杂⼀些了;⾸先得到这些对象实例的类型(Type),抽取共通的属性,属性⽹格中仅显⽰共通属性,对于某个属性的值,如果所有对象实例的值是相同的,则显⽰,如果有所不同,则留空不显⽰。在设置了某个属性的值之后,能够将新值设置到这些对象实例中。
5.撤销重做引擎
这⾥可以⽤的上“引擎”⼆字,因为确实⽐较复杂,我们先将这个问题简化,可以简单理解为对“对象”属性变化的跟踪,可以撤销这些变化,也可以重做这些变化,可以任意步骤的操作。
涉及到的问题和知识点很多,在 IDE ⾥对象状态的变化⼜被抽象为具体的“操作”,以及这些操作⼜要和设计器进⾏联动,有⼀定难度。
UI上的效果是直接使⽤ GDI+ ⾃定义的⼀个列表,并不是很复杂,其它能够直观看的界⾯UI不多,主要是代码了。
6.事件及事件编辑器
上⽂中提到,要将常⽤的操作(事件)都封装起来,通过配置的⽅式来运⾏,⼤⽅向好像并不复杂,但是,怎么做呢?⾸先事件本⾝的抽象要独⽴,要与窗体设计解耦合,其次“事件”的定义应该允许由第三⽅插件扩展,甚⾄“触发时机”也应该允许由第三⽅插件进⾏扩展。以⼀个最简单的按钮为例:
看上去和普通编程中的事件机制没什么区别,是的,我们要做的是对其基本机制进⾏抽象化。例如:
1)触发时机应该与事件寄主解耦,甚⾄允许第三⽅插件挂载触发时机。
2)事件序列应该与触发时机解耦,事件序列中的事件定义,应该与以上机制解耦,甚⾄允许第三⽅插件扩展。
看看项⽬中实现的效果:
我们就以“为窗体元素加载数据”这个事件为例,看看现在的事件编辑器⼤概是什么模样。
这个事件⽀持“关联数据实体⽅式”和“执⾏ SQL ⽅式”。
切换到数据实体界⾯中,选⼀个数据实体,然后设置相关的数据项。
这⾥就可以配置事件在执⾏时,从哪⾥取得数据,我们指定了从⽤户这个数据实体中选择数据,同时指定了⼀个条件,就是⽤户的 Id 要等于指定⽂本框中的值。
除了使⽤界⾯元素中的值作为条件,还可以使⽤系统数据,如:
对于选择特定的⽤户,⽐如这个 Id 怎样获取呢?只要在加载数据时,把 Id 绑定到⼀个隐藏的⽂本框中就可以了,加载数据时,可以读取它的值。
然后切换到载⼊界⾯
在载⼊界⾯中,指定我的数据取出来以后,加载到界⾯的哪些元素中。
我们上⽂提到,希望对数据的操作有⼀定的⾃由度,那么在事件编辑器中,就允许直接定义 sql 语句,
或者说 sql 语句的模板。
切换到 sql 界⾯后,⾸先可以通过获取 sql 按钮⾃动根据前⾯的配置⽣成 sql,然后在此基础上进⾏调整,修改。
在 sql 编辑器中,可以通过 {Provider.Source} 的⽅式访问数据。
⽀持语法着⾊,⽀持智能提⽰。
⽬前实现了两种 Provider,FormElement (窗体元素)和 System (系统),在智能提⽰中⽀持递进的提⽰。
所谓递进的提⽰是输⼊“{”之后⾃动给出 Provider,选择后进⼀步⾃动给出 Source 列表。
也可以在 “{Provider” 后输⼊“.” 则⾃动给出 Source 列表。
智能提⽰⽤起来简单⽅便,看起来也很简单,貌似只是⼀个 Popup ,实则是⼀个不⼩的坑,这个功能困惑了我很久,记得当时到处⼤神请教,除了⾼谈阔论的就是直接告诉我不知道,有个⼈也研究过 SharpDeveloper,告诉我这个问题深了,后来我⼜去翻 SharpDeveloper 的源代码,参考了它的实现,完成之后还是相当有成就感的。
对于事件序列的编辑,有两种⽅法,⼀种是在设计器中双击控件之后打开的事件序列编辑器
另⼀种⽅法是在窗体设计器中提供了以树形⽅式展⽰的事件序列,可以直接拖动改变事件的触发时机,或其在事件序列中的位置。
事件在解析器中执⾏时,是按钮它所处事件序列中的顺序进⾏执⾏的。
⽬前实现的事件⼤概有⼗⼏个,基本的应⽤程序操作,数据交互等。
不再⼀⼀详细说明,因为事件本事是在解耦的情况下独⽴实现的,IDE并不依赖他们,所以未来扩展也很容易,可以说IDE和解析器是核⼼引擎,⽽这些事件定义,只是系统中的“业务”部分。
7.集合编辑器
集合编辑器,就真的只是⽤来编辑对象集合的,⽀持对集合中对象实例的编辑,以及集合中元素顺序的调整,并且在与窗体设计器解耦合的基础上,与窗体设计器联动,能够从窗体设计器中的元素取得对象集合,同时与撤销/重做引擎对接,在编辑的过程中,提供撤销/重做的⽀持。
这个编辑器完成之后复⽤性⽐较强,在窗体设计器中有很多地⽅需要对集合进⾏编辑,⾏定义,列定义,元素定义之类。
⼀个典型的使⽤场景是在窗体设计器中,对 DataGrid 的列进⾏编辑。
8.有效性检查
在窗体设计器中,能够对当前窗体中的各项设置,包含的事件进⾏有效性的检查。例如我在某个事件中设置了加载数据到 TextBox1 ,后来我删除了这个TextBox1 ,那么就必须给出提⽰。
此处的主要难点应该在于解耦合,各种解耦合。
9.IDE多国语⾔实现
Visual Studio ⾃带的资源⽂件编辑器使⽤起来不是很⽅便,⽐如多国语⾔,是分开在多个资源⽂件编辑器窗⼝中编辑的,没有⼀⼀对应的显⽰语⾔⽂本,另外直接使⽤资源⽂件,使⽤的是通过 String 做参数的弱类型⽅式进⾏调⽤的,不能做静态编译时检查,也⽆法保证多语⾔相关编码的质量。所以这⾥我没有直接使⽤资源⽂件机制,⽽是进⾏了⼆次开发,我专门开发⼀个资源⽂件编辑器,提供⼀个⼀体化的界⾯同时编辑多国语⾔资源⽂件,使资源key同时和多个资源⽂件对应起来,同时⽀持导出excel,交给翻译翻译之后直接导回来,然后解析资源⽂件中的资源,⽣成⼀个统⼀的 ILanguage 接⼝,和不同的语⾔实现,如class Chinese:ILanguage,class English:ILanguage,调⽤时,直接使⽤接⼝进⾏强类型调⽤,即将 Resource.GetString("buttonText") 变换为
_language.buttonText。
另⼀⽅⾯,实现了⼀种将界⾯⽂本绑定到资源的机制,这⼀点在 WPF 下⾮常⽅便,在 WinForm 下就要⾃⼰动⼿了。通过特定字符串标记资源key,在运⾏时⾃动扫描窗体或其它容器控件,通过解析这些字符串⾃动查对应的资源,将其替换。
10.界⾯⽤户数据的验证
⽬前⼏乎所有的开发平台都提供了⽐较友好的⽤户输⼊验证⽅案,在 WinForm 下也有,不过并不是很完善,使⽤起来限制⽐较多,功能也有限,不是很顺⼿。
我⾃⼰开发了⼀套⽤于 WinForm 的⽤户界⾯数据验证功能。举个最简单的例⼦,我给⽂本框设置⼀个不允许空的属性,或者设置⼀个正则表达式,在我调⽤验证⽅法时,就能够对它进⾏有效性验证。⽅案⾮常简单,只是要花点⼼思把它实现好,各种控件都要⽀持,要解耦合,验证器要⽀持多种不同验证机制,验证结果如何向⽤户反馈等等。
这套验证机制也同样实现在了运⾏时(解析器)当中。
验证结果的反馈并不⼀定要⽤ MessageBox,可以很容易的改进为其它更友好的形式。
11.模块化设计
模块化,插件式的框架设计现在应该有很多现成的框架和设计⽅法,但是在当时,⼜是 WinForm 下,可以参考的资料⾮常少,⼤⽅向不复杂,但是做完善做细致,在当时对我来说有相当⼤的难度。当时唯⼀可以参考的是微软的 CAB 框架,但在当时来看,CAB 就已经是⼀个有些过时的框架了,使⽤起来有⼀些缺点和限制。
我在参考 CAB 的基础上在 WinForm 下实现了⼀套分解的⾮常细致的模块化开发框架,对软件的功能进⾏层层解耦,宿主程序与功能模块完全⽆关,⽽在我业务功能的设计上,IDE中的功能也实现了完全解耦,上⽂也多处提到了,窗体设计器与被设计的控件包解耦,事件机制与具体的事件定义解耦等等。
其它功能点
其它功能点主要是指:数据实体定义,主菜单定义,枚举定义,资源管理,以及其它⼩功能等,下⽂先做个简单展⽰,暂不再做详细的说明。
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论