知识管理系统Data Solution研发日记之六 窗体设计器
知识管理系统Data Solution已经有五篇文章对它进行介绍,可以通过下面的连接,找到前面的文章
知识管理系统Data Solution研发日记之一 场景设计与需求列出
知识管理系统Data Solution研发日记之二 应用程序系列
知识管理系统Data Solution研发日记之三 文档解决方案
知识管理系统Data Solution研发日记之四 片段式数据解决方案
知识管理系统Data Solution研发日记之五 网页下载,转换,导入
每一篇文章都试图从一个角度来分析这个系统的目的,设计思路。至于源代码,并没有什么特别的难度,问题的关键在于思路,找到了合适的解决方案,实现起来就相对容易一些。这篇文章讲解窗体设计器的主要内容。
设计器生成的的窗体文件,可以以二种方式保存,一种是源代码方式,C#,VB.NET代码的形式,另一种是XML格式,这里采用的是XML格式保存窗体设计格式。如果你对此并不熟悉,请通过查看文章《基于窗体设计器的企业管理软件开发工具》来了解更多的内容。
在Windows Forms的开发中,BindingSource控件扮演着数据与控件连接的桥梁,而且是双向的数据绑定。这句话在学习ASP.NET时,一直不得其解,双向的数据绑定与单向的有什么区别。用直白的话说,双向的数据绑定,可以让控件中被用户修改的数据,重新写到绑定的数据源中,在窗体的保存功作中,可以保存用户修改的数据。双向的数据绑定可以节省大量的数据绑定代码,想像一下,在刚学编程时,经常写出这样的代码
protected override void OnLoad() { txtUserName.Text=user.UserName; txtEmal.Text=user.Email; } protected override void OnClosing() { user.UserName=txtUserName.Text; user.Emal=txtEmail.Text; }
在窗体打开时,需要从数据实体类型中,绑定数据到控件中,在窗体关闭时,再从控件的值写到数据实体类型中。如果没有双向的数据绑定,界面中会大量的重复这样的代码。
先来看一个,读取单一个数据表的情况。比如,只读取采购单PUORDH一个表。在上图中的窗体设计器中,有两个FlexDataTable控件,它用来获取数据源。它的Name就是数据库中的表名,SQL属性是读取数据的SQL语句,比如有一个表是PUORDH(采购单),那么需要为它写SQL属性为SELECT * FROM PUORDH。理论上,在知道表名之后,是不需要写SQL属性的,因为可以生成默认的SELECT语句,出于效率的考虑,可以简化对SQL的编写,比如上面的窗体,它只读取PUORD的Ref No.和Vendor,这时,如果自动生成的SQL语句还是SELECT * FROM PUORDH,则效率损失很多,其它的不需要的字段也被读取到内存中。所以,FlextDataTable的SQL属性,可来用优化SQL性能,当它的值是空时,窗体设计器会自动生成SELECT * FROM PUORDH。
再来看一下,读取主从表的情况。如上图所示,主表PUORDH,明细表PUORDD,它们的关系存在于数据库中,是一对多关系。这时,数据绑定的关键代码是这三行
this.bsH.DataSource = this.DataSet;
//this.bsD.DataSource=this.DataSet; this.bsD.DataSource =bsH; this.bsD.DataMember = "FK_PUORDD_PUORDH";
表头的数据仍然是DataSet,初试化窗体时,根据FlexDataTable表名和它的SQL属性,读取数据到DataSet中。从表的数据如果是DataSet,没有错,但是没有正常显示出主表与从表的数据关联数据,它会选出所有的从表数据。所以,后面两行代码是绑定从表数据的关键。窗体设计器在生成CS源代码时,则需要生成正确的主从数据关系绑定代码。
在生成主从数据关系方面,XSD是首选的方案,参考下面的调用代码,它可以生成包含关系的XSD文件
string connectionString = "data source=(local);initial catalog=Emp5;integrated security=SSPI;persist security info=False;packet size=4096"; string destinationFile = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, xsdName); List<string> tables = new List<string>(); foreach (IComponent comp in host.Container.Components) { if (comp is FlexDataTable) { tables.Add(comp.Site.Name); } } DatasetGeneratorForm dlgXsd = new DatasetGeneratorForm(connectionString, destinationFile, false, "CS", tables); dlgXsd.GenerateXsdSource();
窗体设计器正是从这个生成的XSD文件中获取到表与表之间的关系,以生成上面提到的主从表的数据绑定代码。
在窗体设计器生成代码方面,CodeDom是首选的代码生成技术。这主要归咎于它可以以一套代码,同时生成C#,VB.NET两套代码,这种灵活性比硬编码的字符串拼凑,效果要好很多。代码示例如下
//this.DataAdapter = da; CodeVariableReferenceExpression da = new CodeVariableReferenceExpression("da"); CodePropertyReferenceExpression thisda = new CodePropertyReferenceExpression(new CodeThisReferenceExpression(), "DataAdapter"); CodeAssignStatement as1 = new CodeAssignStatement(thisda, da); method.Statements.Add(as1);
为了写一个赋值语句,竟然用了四行代码,而且CodeDom调试时,也不可以看到它生成的目标代码的模样。这与调试带参数的SQL语句的体验差不多,无法得知最终要发送到服务器的SQL语句的模样。好在有MSDN和大量的在线资源,可以很容易的获取到一个C#语句如何用CodeDom表达出来。而且大部分情况是,你写对了CodeDom语句,感觉对了,它运行时的结果,就是对的。微软的人真了不起,竟然创造了CodeDom这种威力巨大的代码生成技术。
再来看一下窗体间互操作的代码,打开一个窗体,可以用这条语句
=Open("PURCHASE") 或是=Open("PURCHASE",this)
带一个参数的Open方法,会以参数为XML文件名,拼凑成PURCHASE.XML字符串,到系统的指定目录中查找PURCHASE.XML窗体定义格式文件,读取并转化成窗体类型,显示出来;带二个参数的Open方法,会在系统指定的目录中,查找PURCHASE.dll程序集文件,反射它的类型成员,调用并显示了出来。这里也有个约定,窗体设计器生成的源代码文件,只包含一个类型定义,然后经过CodeDomProvider编译,生成窗体的程序集文件。
继续看窗体设计器设计的窗体,它的另外几个按钮的Click事件的写法
保存按钮的Click事件的写法是:=Save();
删除按钮的Click事件的写法是:=Delete(bsH,bsD) ;
创建按钮的Click事件的写法是:=New(bsH,bsD);
在这里,完全不需要指定主外键,或是表与表之前的关联关系,所以这些,都由后台程序自动判断。这样做,也没有效率。读取数据的操作是把它读到一个DataSet中,然后依照FlexDataTable表名依次分配与绑定;保存操作会把数据写回到数据库中,依照DataSet中数据是否发生改变,这个写回数据库的过程,效率很低。当然,大多数的情况,是用来查看数据库中的数据,而不是编辑数据,依照这个标准,窗体设计器也达到了它的目的。
把这几条技术整合到一起,就完成了窗体设计器的开发。我在思考这些关系时,通常零乱的,不完整的,设计的过程也是代码演化,思路调整的过程。也许你不知道我这里讲的是什么内容,或许你有更好的实现方法,欢迎反馈。