国内最全IT社区平台 联系我们 | 收藏本站
华晨云阿里云优惠2
您当前位置:首页 > php框架 > 框架设计 > 使用 .NET WinForm 开发所见即所得的 IDE 开发环境,实现不写代码直接生成应用程序

使用 .NET WinForm 开发所见即所得的 IDE 开发环境,实现不写代码直接生成应用程序

来源:程序员人生   发布时间:2015-04-24 08:00:06 阅读次数:4917次

直接切入正题,这是我09年到11年左右业余时间编写的项目,最初的想法很简单,做1个能拖拖拽拽就直接生成利用程序的工具,不用写代码,把能想到的业务操作全部封装起来,通过配置的方式把这些业务操作组织起来运行。

项目的核心功能已基本实现,但12年以后我基本停止了这方面的开发,现在翻出来在这里写出来想和大家交换1下。

 

鉴于篇幅和精力的缘由,请谅解我这篇博文对技术实现的具体细节谈的不是很多,只能算是1个概述。对业务的说明也不多,我想大家都是技术流,应当1看就明白。

写这个项目的时间是56年前,现在回过头去看,有很多不足的地方,设计上的,技术上的都有,加上当时技术力有限,不足的地方还请指正,谢谢。

后续是不是会写1个系列的博文详细的分析讲授实现方法,我暂时也没有想好,主要是没有太多时间,我现在基本又回到了当初每天只睡45个小时的状态。

如果此篇博文有点儿价值,给个推荐呗 ^_^ 

 

项目使用了 .Net Framework 3.5 开发,分为两大块: IDE 和 运行时(解析器)

IDE中开发的项目在打包后生成 zip 格式的包,解析器通过读取 zip 包实时解析运行,有点类似中间语言的概念,但我这里生成的 zip 包中主要以 xml 文件为主,通过 xml 文件对项目的 UI,业务,数据结构 进行描写。

 

到此可以看出,运行时本身其实不1定是 .Net 或 WinForm 的,而是可使用任何平台或语言实现,只要读取 zip 文件和 xml 文件并解析便可。

事实上我自己实现的默许运行时也不是 WinForm,而是用了 Silverlight。

 

再简单说说 IDE 的设计思路,几个主要的设计目标以下:

 

1.像 Visual Studio 1样

  有可视化的环境,拖拖拽拽界面就出来了。

2.模块化设计

  功能模块全部独立,解耦,以插件的情势存在于主程序(宿主)中。

2.不要写代码,业务通过界面,向导进行配置

  拖1个按钮上去,想要单击时做1件事情,就先把按钮拖上去,然后设置这个按钮的事件序列,配置对应的事件。

3.把事件这个概念抽象并封装起来

  如“保存数据”这个事件,配置好数据的来源,如窗体上的数据,或系统数据,再配置好要保存的目标,某种数据实体,便可,这个事件被添加到某个事件序列,如按钮的单击事件序列中,项目被运行时解析时,就会按钮这个逻辑履行。

4.对数据操作要有1定的自由度

  除基本的向导式配置之外,要能满足特殊需求,比如支持自定义 sql 语句。但是自定义 sql 语句怎样与数据源,目标交互呢?我设计了1种简单的表达方法,如 UPDATE FROM [User] SET [Name] = {FormElement.txtName} WHERE [Id]={System.UserId}

5.数据库数据表的操作怎样交互

  就是将其抽象为“数据实体”,数据实体也在 IDE 中由用户自己定义,定义的进程类似于 SqlServer,定义好数据实体以后,在 IDE 中进行设计时,通过数据实体来抽象对数据库、表的操作,在打包项目时,可以根据定义的数据实体,生成多种数据库,如 SqlServer,Mysql 等。

6.资源文件的管理

  在项目中必定要援用到外部资源,这部份外部资源,怎样引入,管理,打包呢?我在 IDE 中设计了独立的资源管理器,在 IDE 中设计 UI 时,通过资源管理器援用资源,打包时,将资源打包到 zip 文件中。

7.打包前的静态编译检查

  类似于我们在 Visual Studio 中写程序,编译时如果有毛病就会出现正告或毛病提示。在这个 IDE 中,也必须有一样的功能。当援用的数据实体被删除,数据项不存在,援用的资源文件不存在,和事件配置中1些问题出现时,能够实时,并在打包项目时指出这些毛病的具体位置。

8.支持嵌入脚本

  能够在事件序列中添加自定义脚本,支持在运行时动态解析或调用某种脚本语言。此功能有所设计,但并未开发

9.支持插件

  此处插件支持指的是 IDE 层面能够支持插件,类似 Visaul Studio 或 Eclipse 的插件机制,我当时使用的是 .NET 管线技术(很冷门),实现了相干DEMO,但是没有集成到IDE中。

10.IDE界面支持多国语言

  目前IDE完全支持多国语言,所有文本均使用了资源,但是我没有直接使用资源文件,而是将其强类型化了,具体实现方式下文详述。

 

在设计开发这个 IDE 的初期,我并没有给自己设定如此详细的目标,现在写其实更多的是回顾和总结。

 

在这个项目中,大量使用了 GDI+ 绘图,说复杂,给你调用的接口也就那末多,说简单,用 GDI+ 自己写1个功能完备的 WinForm 控件,分分钟教你重新做人。在这个项目中,几近所有的界面元素都是我自己用 GDI+ 绘制的,使用的第3方控件不多。后面我会写1些这方面的感想。

 

下面罗列1些技术难点和主要功能点,有些细节可能没有试着做过都不会心识到那是个问题。

 

1.工具栏按钮/右键菜单的状态控制

  就是控制状态栏上按钮可用不可用,可见不可见,绝大多数时候这不是个问题,但是作为1个 IDE,工具栏上的各种按钮非常多,且按钮的状态和当前设计器中的选中元素个数,选中元素类型,乃至选中元素本身的状态等等相干,还有些按钮是特定控件或元素提供的,怎样统1控制这些按钮的状态?

黄色背景部份是动态挂载上去的,状态的控制在后文中说明。

 

此处右键菜单指的是窗体设计器中的右键菜单,在窗体设计器中,右键菜单比较复杂,不同控件的右键菜单有所不同,有共通的项目,有特殊的项目,和状态是否是灰掉可能和控件本身的某些因素有关,但右键菜单本身是不可能通过处于设计状态的控件本身提供的,所以此处如何把控件独有的菜单项挂载上去,又怎样控制它们的状态?注意设计器本身和用来设计的控件是解耦合的。

注意这个例子,右键 DataGrid 产生的右键菜单中存在“添加列” 和“编辑列”两个特殊的项目。

此处当我选中 DataGrid 时,属性网格下方也会出现这两个项目,这里先提1个关键概念,叫做“谓词”,这是1个 DesignSurface 中的概念,后文再详述。

 

此处实际上我实现了1个独立的菜单(包括工具栏项目)的管理器,并不是直接创建 MenuItem 之类的实例去使用,这个管理器也是独立于业务进行设计的,对菜单项的各种状态,行动都进行了抽象与封装。在管理器层面统1调度这些菜单项,通过1定的机制使菜单项的状态与业务状态关联起来,不允许外部代码直接修改菜单项的状态,整套机制本身,与菜单项在UI层面的实现也是无关解耦的,终究生成可见菜单项时,才会生成特定的控件,如 MenuItem,也能够换成其它任何菜单项控件,不影响管理器的功能与逻辑。

我记得当时我研究了几个IDE的设计细节,包括  Visual Studio,应当都采取了类似的机制,好吧我承认是我研究以后鉴戒了它们的机制。

这个问题我放在首位,是我意想到这个问题在大型软件中,真的是个很大的问题,我现在参与开发的1款电气化CAD软件中,就存在这个问题,但是他们初期并未意想到这个问题,也谈不上能很好的解决,几千个菜单项,工具栏按钮项,直接硬编码,对他们的状态控制也谈不上成体系,就是粗鲁的硬编码,现在的保护,修改,调剂都异常痛苦。

 

2.窗体设计器

  最初我是自己用 GDI+ 写了1个简单的设计器模型,支持拖拽,绘制,动态对齐等等功能,但是越往后越复杂,比如绘制1个 DataGrid,你不能光是1个框框,你要自己去绘制它的列,列头,如果要绘制1个图片框,你就要自己去绘制它的图片内容,要斟酌图片的缩放方式等等细节,如果要1条道走到黑完全自己实现,本钱将非常高昂。

先看看初期直接使用 GDI+ 实现的效果:

 

下面是直接使用微软 DesignSurface 效果:

和 Visual Studio 效果1样,不过这里需要注意的是 DesignSurface 仅仅也只是提供了基本的窗体设计能力(图中右边部份),比我上面GDI+自己写的功能多不了多少,但是不用自己绘制控件的外观,其它辅助功能都是需要自行开发的。

这里要注意的1点是窗体设计器中 允许被设计 的控件 们,是与设计器本身,与IDE解耦的,是完全独立实现的,后期添加新控件,修改控件都与IDE无关,这个地方的难点决然是解耦合,各种解耦合。

左边的属性列表是自行开发的,.Net Framework 中确切提供了 PropertyGrid 控件,但是对高阶开发此处其实不适用,有很多制限,下文详述。

 

 

3.工具箱

  工具箱本身是独立实现的,不依赖其所处的窗体设计器,同时它本身所承载的控件,也是动态载入的,后期允许第3方插件挂载控件到工具箱中。

  这个地方需要注意的不多,1个是动态载入控件,另外一个就是在和窗体设计器交互的时候,比如我拖1个控件到设计器上,这里是需要对接 DesignSurface 的。

 

4.属性网格(PropertyGrid)

.Net Framework 中提供了 PropertyGrid 控件,可以实现对对象实例的属性编辑功能,但是难于扩大与自定义,我此处需要个性化定制的地方比较多,所以选择自己实现1个。

主要实现了以下功能

1)对单个对象实例,列出它的属性(Property,下同),和属性的值,如果属性值与默许值不同,能够粗体显示。

2)对特殊的属性,提供对应的扩大编辑器,如色彩属性,在点击后应当提供1个色彩选择器。且这些扩大编辑器,是与属性网格本身解偶的。

3)如果同时设置了多个不同类型(Type)的对象实例,例如在窗体设计器中框选了多个控件,这个场景就复杂1些了;首先得到这些对象实例的类型(Type),抽取共通的属性,属性网格中仅显示共通属性,对某个属性的值,如果所有对象实例的值是相同的,则显示,如果有所不同,则留空不显示。在设置了某个属性的值以后,能够将新值设置到这些对象实例中。

 

 

5.撤消重做引擎

这里可以用的上“引擎”2字,由于确切比较复杂,我们先将这个问题简化,可以简单理解为对“对象”属性变化的跟踪,可以撤消这些变化,也能够重做这些变化,可以任意步骤的操作。

触及到的问题和知识点很多,在 IDE 里对象状态的变化又被抽象为具体的“操作”,和这些操作又要和设计器进行联动,有1定难度。

UI上的效果是直接使用 GDI+ 自定义的1个列表,其实不是很复杂,其它能够直观看的界面UI不多,主要是代码了。

 

6.事件及事件编辑器

上文中提到,要将经常使用的操作(事件)都封装起来,通过配置的方式来运行,大方向好像其实不复杂,但是,怎样做呢?首先事件本身的抽象要独立,要与窗体设计解耦合,其次“事件”的定义应当允许由第3方插件扩大,乃至“触发时机”也应当允许由第3方插件进行扩大。以1个最简单的按钮为例:

看上去和普通编程中的事件机制没甚么区分,是的,我们要做的是对其基本机制进行抽象化。例如:

1)触发时机应当与事件寄主解耦,乃至允许第3方插件挂载触发时机。

2)事件序列应当与触发时机解耦,事件序列中的事件定义,应当与以上机制解耦,乃至允许第3方插件扩大。

 

看看项目中实现的效果:

我们就以“为窗体元素加载数据”这个事件为例,看看现在的事件编辑器大概是甚么样子。

这个事件支持“关联数据实体方式”和“履行 SQL 方式”。

切换到数据实体界面中,选1个数据实体,然后设置相干的数据项。

这里就能够配置事件在履行时,从哪里获得数据,我们指定了从 用户 这个数据实体当选择数据,同时指定了1个条件,就是 用户的 Id 要等于 指定文本框中的值。

除使用界面元素中的值作为条件,还可使用系统数据,如:

 

对选择特定的用户,比如这个 Id 怎样获得呢?只要在加载数据时,把 Id 绑定到1个隐藏的文本框中就能够了,加载数据时,可以读取它的值。

然后切换到载入界面

在载入界面中,指定我的数据取出来以后,加载到界面的哪些元素中。

我们上文提到,希望对数据的操作有1定的自由度,那末在事件编辑器中,就允许直接定义 sql 语句,或说 sql 语句的模板。

切换到 sql 界面后,首先可以通过 获得 sql 按钮自动根据前面的配置生成 sql,然后在此基础上进行调剂,修改。

在 sql 编辑器中,可以通过 {Provider.Source} 的方式访问数据。

支持语法着色,支持智能提示。

目前实现了两种 Provider,FormElement (窗体元素)和 System (系统),在智能提示中支持递进的提示。

所谓递进的提示是输入“{”以后自动给出 Provider,选择落后1步自动给出 Source 列表。

也能够在 “{Provider” 后输入“.” 则自动给出 Source 列表。

智能提示用起来简单方便,看起来也很简单,貌似只是1个 Popup ,实则是1个不小的坑,这个功能困惑了我很久,记得当时到处找大神请教,除高谈阔论的就是直接告知我不知道,有个人也研究过 SharpDeveloper,告知我这个问题深了,后来我又去翻 SharpDeveloper 的源代码,参考了它的实现,完成以后还是相当有成绩感的。

 

对事件序列的编辑,有两种方法,1种是在设计器中双击控件以后打开的事件序列编辑器

另外一种方法是在窗体设计器中提供了以树形方式展现的事件序列,可以直接拖动改变事件的触发时机,或其在事件序列中的位置。

 

事件在解析器中履行时,是按钮它所处事件序列中的顺序进行履行的。

 

目前实现的事件大概有10几个,基本的利用程序操作,数据交互等。

不再逐一详细说明,由于事件本事是在解耦的情况下独立实现的,IDE其实不依赖他们,所以未来扩大也很容易,可以说IDE和解析器是核心引擎,而这些事件定义,只是系统中的“业务”部份。

 

7.集合编辑器

集合编辑器,就真的只是用来编辑对象集合的,支持对集合中对象实例的编辑,和集合中元素顺序的调剂,并且在与窗体设计器解耦合的基础上,与窗体设计器联动,能够从窗体设计器中的元素获得对象集合,同时与撤消/重做引擎对接,在编辑的进程中,提供撤消/重做的支持。

这个编辑器完成以后复用性比较强,在窗体设计器中有很多地方需要对集合进行编辑,行定义,列定义,元素定义之类。

1个典型的使用处景是在窗体设计器中,对 DataGrid 的列进行编辑。

 

8.有效性检查

在窗体设计器中,能够对当前窗体中的各项设置,包括的事件进行有效性的检查。例如我在某个事件中设置了加载数据到 TextBox1 ,后来我删除这个 TextBox1 ,那末就必须给出提示。

此处的主要难点应当在于解耦合,各种解耦合。

 

9.IDE多国语言实现

  Visual Studio 自带的资源文件编辑器使用起来不是很方便,比如多国语言,是分开在多个资源文件编辑器窗口中编辑的,没有逐一对应的显示语言文本,另外直接使用资源文件,使用的是通过 String 做参数的弱类型方式进行调用的,不能做静态编译时检查,也没法保证多语言相干编码的质量。所以这里我没有直接使用资源文件机制,而是进行了2次开发,我专门开发1个资源文件编辑器,提供1个1体化的界面同时编辑多国语言资源文件,使资源key同时和多个资源文件对应起来,同时支持导出excel,交给翻译翻译以后直接导回来,然后解析资源文件中的资源,生成1个统1的 ILanguage 接口,和不同的语言实现,如 class Chinese:ILanguage,class English:ILanguage,调用时,直接使用接口进行强类型调用,行将 Resource.GetString("buttonText") 变换为 _language.buttonText。

  另外一方面,实现了1种将界面文本绑定到资源的机制,这1点在 WPF 下非常方便,在 WinForm 下就要自己动手了。通过特定字符串标记资源key,在运行时自动扫描窗体或其它容器控件,通过解析这些字符串自动查找对应的资源,将其替换。

 

10.界面用户数据的验证

  目前几近所有的开发平台都提供了比较友好的用户输入验证方案,在 WinForm 下也有,不过其实不是很完善,使用起来限制比较多,功能也有限,不是很顺手。

  我自己开发了1套用于 WinForm 的用户界面数据验证功能。举个最简单的例子,我给文本框设置1个不允许空的属性,或设置1个正则表达式,在我调用验证方法时,就可以够对它进行有效性验证。方案非常简单,只是要花点心思把它实现好,各种控件都要支持,要解耦合,验证器要支持多种不同验证机制,验证结果如何向用户反馈等等。

  这套验证机制也一样实现在了运行时(解析器)当中。

验证结果的反馈其实不1定要用 MessageBox,可以很容易的改进为其它更友好的情势。

 

11.模块化设计

模块化,插件式的框架设计现在应当有很多现成的框架和设计方法,但是在当时,又是 WinForm 下,可以参考的资料非常少,大方向不复杂,但是做完善做细致,在当时对我来讲有相当大的难度。当时唯1可以参考的是微软的 CAB 框架,但在当时来看,CAB 就已是1个有些过时的框架了,使用起来有1些缺点和限制。

我在参考 CAB 的基础上在 WinForm 下实现了1套分解的非常细致的模块化开发框架,对软件的功能进行层层解耦,宿主程序与功能模块完全无关,而在我业务功能的设计上,IDE中的功能也实现了完全解耦,上文也多处提到了,窗体设计器与被设计的控件包解耦,事件机制与具体的事件定义解耦等等。

 

 

 

其它功能点

其它功能点主要是指:数据实体定义,主菜单定义,枚举定义,资源管理,和其它小功能等,下文先做个简单展现,暂不再做详细的说明。

 

1.欢迎界面

欢迎界面是内嵌了1个 HTML 页面,只是和 C# 代码有简单交互,例如单击链接会调用 C# 方法,并传入参数。

使用 HTML 的1个缘由是希望欢迎界面比较漂亮,但是在 WinForm 下实现1个漂亮的,交互性强,保护性强的欢迎界面有1定难度,很浪费时间。

此处注意1个细节, URL 地址不是1个磁盘文件地址,而是1个自定义协议和路径的地址,这个需要自己实现,但是很简单。

年代久远,CSS 和图片可能遗失了,不太好看,见谅。

 

2.数据实体定义

这块相对照较简单,没有复杂功能。

发布项目时可以根据数据实体定义自动生成数据库

 

也能够针对指定的数据实体生成脚本。

 

3.枚举定义

界面很简单,生成数据库时,根据枚举定义向枚举表插入枚举数据。

但是有1个小细节是它和窗体设计器是有对接的,它是1个数据源的 Provider,可以在设计器中把控件中的值绑定到枚举。

 

4.主菜单/工具栏定义

定义要生成的软件的主菜单和工具栏,运行时解析以后,根据自己的实现方式生成,可以是 Ribbon 的,也能够是传统的,或其它方式。

菜单或工具栏项目支持事件,可以挂载事件序列。

 

5.资源管理

实现1个资源管理器,目前只实现了对图片资源的管理。

 

 

这里有点看点的是,我当时没有找到我觉得不错缩略图控件,因而只好自己实现1个。和 Windows 资源管理器功能1致,没有需要特殊说明的地方,只是自己从头实现1个不能说很难,但是真的很麻烦。借这个地方简单讲1下这个缩略阅读器的实现,可能有些朋友对 GDI+ 不是很了解。

对在 WinForm 下使用 GDI+ 绘制界面(自己实现1个控件),是比较原始的,想像1下给你1张白纸,和1些简单的绘图接口,画线,画圆,画矩形,其实没别的了。画1个圆角矩形?自己计算坐标系,通过画弧线和画直线画1个。显示1些文本?自己进行字体字号丈量坐标系换算,如果触及到文本换行,超长用省略号代替,都是比较麻烦的。

你要自己在 逻辑上 掌控控件的不同状态,如选中,非选中,鼠标滑过等等,和WPF下预定义的状态组不同,WinForm 下这些状态是你要自己去掌控的,状态切换时,你要自己根据状态进行重绘……

在绘制界面时,你只有1个从0,0开始的2维的平面坐标系,和它的尺寸。实现这样1个缩略图阅读器,缩略图的排版,布局,1行显示多少个,甚么时候换行,选中非选中,鼠标框选,转动条转动都需要自己实现,包括鼠标框选时的框,也是需要自己用 GDI+ 绘制的,然后自己计算坐标系,判断哪些项目应当处于被选中状态。

这个控件的代码接近3000行。现在回过头去看,只觉得注释不够详细。

软件中大部份自己实现的控件,都采取了类似的架构进行设计:控件本身,布局管理器,显现器,显现器实现,主题。

 

6.生成数据库,生成项目

这里目前其实不复杂,生成数据库根据数据实体定义生成便可,生成项目目前我直接使用了项目文件,由于我目前的项目文件格式就是 zip 包,内含 xml 文件。

附带产出是实现了1个简单的比较通用的向导功能。

 

7.其它控件的美化。

从上面的截图中能够看到我使用了相当多的自定义控件,或经过美化的 WinForm 自带控件,典型的几个除上面的缩略图控件,还有IDE上面的主菜单(效果参考了 Paint.NET), DataGrid,重新实现的 ComboBox 等等,竟然1步1步构成了1个自己的控件包。惋惜技术更新换代日新月异,现在也基本用不上了。

 

IDE 部份现在回过头去看,貌似实际功能其实不多,但是核心架构已基本完全了,后续如果继续开发,基本相当于开发插件和添加新的功能包。 

其中绝大部份是从空白 class 硬写出来的,很多地方用现在的眼光去看,存在很大的过度设计问题。

 

解析器部份我是用 Silverlight 实现了1个,核心实现了,业务没有实现完全,多是我现在的机器 Silverlight 版本有问题还是怎样回事没有运行起来,也不想去调试了,

其实不复杂,只是解析 zip 包,xml文件,生成界面,事件处发时解析事件序列中的事件便可。

 

最后,如果你现在要做客户端软件,选择 WPF 吧,生产性非常高,功能非常完善与强大,如果你担心性能问题,我想说现在已2015年了,不是2005年,如果你在开发中遇到了性能问题或其它问题,先从本身找缘由。

专业程序员永久从本身找问题,业余程序员从平台从语言找问题。

 

后记:

最后想写1点点个人的感想与反思。我在开发这个软件的进程中,犯了许多的毛病,这些毛病未必是技术上的,但都是严重毛病。

首当其冲:闭门造车。活在自己的技术宅世界里,历来不去想,也不愿意去想这个东西有多少实际价值,谁会去用它,到了后来,我明明潜意识里知道这个软件没有太大市场价值,就是不愿意去想这个问题,1门心思去开发。我记得那两年我随身带的手机里,记满了关于这个软件的想法和1些问题的实现思路。我在路上想到某个解决方案或有甚么想法,就立马取出手机记录,怕回去就会忘记。有1个冬季住的地方没有空调,非常冷,写代码1直写1直写,两个手冻青了,就自己用热水瓶子捂捂继续写,那段时间每到周末就特别高兴,由于有2个完全的工作日可以利用了,不用单纯靠晚上的时间去写,很长1段时间我每天晚上我都只能睡45个小时,个别时候还没睡着,天就蒙蒙亮了。但是这么辛苦的意义是甚么呢?我当时没有认真的思考。

生活不易,码农辛苦
如果您觉得本网站对您的学习有所帮助,可以手机扫描二维码进行捐赠
程序员人生

------分隔线----------------------------
分享到:
------分隔线----------------------------
为码而活
积分:4237
15粉丝
7关注
栏目热点
关闭
程序员人生