2023年2月

每个人身上,都有着独一无二的经历,也有着不一样的成长历程。回顾一下,从大学时期参加校网络中心从事开发工作,到目前在社会上的风雨兼程,也走过十多年的开发路程了,黄金岁月,青春年华,都在这期间度过。

养成经常写写博客的习惯,也将近10年,每篇文章,都体现自己某一刻的体会或者想法,博客十年,也是自己的技术十年,总结了无数的开发心得和开发思路,或者有时候也很欣喜的介绍一下自己的劳动成果,辛苦与愉悦,伴随着时间慢慢沉淀。

由于热衷技术的原因,博客内容一般围绕某个技术点,或者某个主题进行介绍,逐渐也形成了几个内容较多的标签,也是自己一直以来坚持的成果,回首过去,还是能看到沉淀下来的一些成绩。

1、代码生成工具的探索

虽然05年就注册加入了博客园,不过05年正处于奔波忙碌的年代,在上海的一家外资公司做软件外包的开发工作,公司管理严格,禁止发送任何信息到外面,因此多数情况下在休息时间看看一些文章,了解一些技术的动态,自己业余时间则主要沉醉于第二版代码生成工具的开发工作,之前的第一版主要是在C#代码中糅合了需要生成的代码,能解决一些工作,但维护的工作量很大,容易出错。因此后来碰到了NVelocity的模板化技术的时候,非常激动,花了很多时间在这方面做研究,然后把它综合到代码生成工具里面,实现实现逻辑和模板的分离

初期的博客发表,可以追溯到06年期间,当时已经回到了广州,公司管理也没有那么严格苛刻,因此也有时间介绍一下自己的东西,偶尔有空写下博客,虽然量不多,当时主要就是围绕代码生成工具来完成自己的一些工作,而且也因为能够减轻公司日常的开发工作,获得公司的一个年度奖,奖金很少,但还是很开心。

2、开发架构的学习和提炼

随着开发经验的积累,感觉高效的开发和架构很重要,使用代码生成工具开发项目起来,能达到事半功倍的效果,因此在项目开发的时候,一方面提炼完善较为通用的开发框架,一方面整合到自己的代码生成工具里面,反复的循环,力求达到最为完美的状态。有时候看着,操作几下就能完成新项目搭建的大量工作,心里还是感觉到非常的欣慰,虽然这期间的完善再完善,花费时间很多,提炼标准的架构模板也是较为繁琐的工作。

3、公用类库的整理

随着时间的推移,自己对很多常用的类库逐渐的也积累的更多,有时候因为开发的需要,自己也开发一些辅助的类库用来使用到框架的架构当中,如支持多数据库的数据查询类,配置管理类等等,以后参考很多项目,把能够提炼出来的东西进行综合整理、合并,然后添加注释,增加很多测试使用的例子,就是力求在以后方便参考使用。公用类库的整理过程很漫长,从开始整理到最终的写博客介绍,中间经历了4~5年的时间,整理过程也可以是博取众长了,把很多同一主题的类库进行比较综合,挑出最好的实现方法,提炼出来,然后也尽可能的添加完善的注释。

4、Visio二次开发

在这段时间工作过程中,接触了Visio的二次开发工作,由于当时的公司项目主要就是利用VB进行Visio的开发,因此除了开发前端展示的基于Web的项目外,也维护着VB6的项目,由于自己对Visio的二次开发很感兴趣,因此也花了大量进行对Visio的模型和接口进行了解。因此这期间常常在.NET项目和VB的项目进行切换,感觉很别扭,总是希望以后能够在语言的方面一统江山。后来偶尔一次机会发现C#也能用来开发Visio的二次开发项目,而且微软新版本的VisioSDK也带有了C#的一些例子代码,因此就花了大量的业余时间对这些英文的帮助进行学习,并逐渐进行了各种代码开发和测试。花了差不多进一年的业余时间,加上项目对Visio的深厚了解,终于把整个Visio的二次开发项目完整的用C#进行了重新开发,并且增强了很多Visio的应用特性,自己在这期间也把Visio的知识了解的较为深刻,因此借着热度把很多Visio的知识,用C#代码的方式,写了很多这方面的博客,也算是填补了国内的这方面知识的空白了。由于这方面只是的缺乏,以至于很多年后,还有很多客户联系我获取这方面的源码和帮助,偶尔也到处讲讲课什么的,这是后话。

5、Web项目和Socket项目

后来转转到了另外一家公司,也基本是从事Web项目的开发工作,而我业余时间主要搞Winform开发一些共享软件,这样就出现了一个很有趣的情况,白天弄Web项目,晚上或者周末有空就搞下自己的WInform项目,由于得益于微软对这两方面的抽象,基本上除了界面层以下的内容,都是通用的,因此我的Winform开发架构和Web开发框架底层都是一样格局的,仅仅是界面层的不同。回顾这么多年的开发经历,感觉除了历史原因外,一般企业的项目都要求使用Web开发,这期间的2年左右的时间,都主要在带着一些手下在忙着Web项目的开发工作,一个用于停车场行业平台的管理,接入了全广州市的业务审批流程和展示这个行业的数据。后来,公司需要在停车场方面接入实时空车位的信息,因此需要接入一个终端的硬件设备进行数据采集,为了统一进行数据采集,建立了一个以TCP/IP链接的Socket数据处理平台,这是很多年终于在所任职过的公司上线的一个Winform项目,处理来自几百个终端设备的数据接入,这个时候,也基本上把Socket编程的技术了解的很深刻透彻,这期间,自己亲力亲为过很多有意义的事情,如定义接入的Socket协议、调试终端的串口数据接入,和很各地方的设备厂商进行沟通、调试等,后来定义的Socket协议经过反复的完善调整,也顺利通过《广州市标准化研究院》的验收工作,也算正式参与了一项标准化的起草工作了。

在最后这家公司任职时间较长,虽然每个项目的开发、维护周期很长,但还是参与了几个项目的开发工作,不过由于技术没有太大的变化,基本上沿用了WebForm的这样方式,不过这样也很好。这个模式很好的利用了我在WebForm模式下积累的一些组件模块,如Web分页控件、Web查询控件、上传组件、以及Web的流程处理模块等等,这样基本上保持了较好平稳度和稳定的开发模式,开发的速度也较为高效。不过随着时间的推移,基于JQuery的Web项目,基于MVC技术的项目已经越来越流行了,但是纵观其他项目组,并没有人领衔这个技术潮流,也许项目开发本身稳定很重要,技术探索也需要时间和积累。因此我离职后,一直就希望开发一个基于JQuery+MVC4+EasyUI的项目框架,后来也就形成我自己的框架,作为Web项目的参考,这是后话。在这家公司的环境还是很好,同事的相处氛围也还是很不错,以至于我离职后还经常和他们一起打打球,吃吃饭,很多同事关系也算是朋友关系了。

6、技术总结

在最后这家离职后,一直希望整理完善自己的东西,想把这几年的东西,好好梳理,好好总结,因此感觉有很长一段时间,自己的工作显得更加繁忙。希望藉此打造出项目开发过程中的一些基础模块,模块之间以积木方式进行组合,即使对于互联网的网络环境,我们一样能够利用WCF技术,把它整合到Winform界面上,因此就总结了自己的几个Winform框架模式:传统Winform开发框架、WCF开发框架、混合型开发框架,除了底层的变化为,组合上依旧是采用积木方式进行组合,下面的模块分布图,就是我目前已经完成的各个模块,也许未来会整合更多,但是这些模式依旧能够很好的进行组合、拆分,每个模块可以独立完成,根据需要进行整合。

随笔背景
:在很多时候,很多入门不久的朋友都会问我:我是从其他语言转到C#开发的,有没有一些基础性的资料给我们学习学习呢,你的框架感觉一下太大了,希望有个循序渐进的教程或者视频来学习就好了。

其实也许我们每天面对的太多东西了,觉得很多都稀松平常了,即使很细微的地方,可能我们都已经形成习惯了。反过来,如果我们切换到其他领域,如IOS、android,那么开始我们可能对里面很多设计的规则不甚了解,开始可能也是一头雾水。

本篇想作为我的《循序渐进开发WinForm项目》系列的开篇,主要介绍数据库设计方面注意的一些事项,从而方便项目框架的搭建和开发过程。

1、数据库表设计

俗话说万层高楼从底起,开发应用项目,数据库的设计很重要,它可能是业务对象,业务流程的综合设计,好的数据库设计可以减少后期的重复返工,提高开发效率。

我们以一个简单的数据库表进行设计讨论,一步步分析其中的关系。

1)表和字段名称

一般表名称,根据不同的业务关系,我们可以使用不同的前缀进行区分,使用前缀,可以非常方便区分不同的业务表,如我自己一般基础表使用 “TB_” 定义前缀,权限系统表使用"T_ACL_"定义前缀,工作流表使用“TBAPP_”,业务表使用"T_"等,这样对于区分不同的业务,方便管理很有好处。

字段名称方面,我们可以约定一些规则,如约定主键使用ID;一般来说,ID作为主键,可以使用自增长的整形字段,也可以使用GUID的字符型字段,如果为了方便兼容不同的数据库且方便迁移或者开发基于网络方面的应用,我建议还是使用GUID的字符型字段,使用这种类型的字段,我们从创建数据的时候,就可以知道这个记录的主键,对于我们维护父子表等关系非常有利。

字段的命名,建议一简单为主,如客户名称,直接使用Name来命名即可,不需要使用CustomerName这样啰嗦的名称。

由于如果采用字符型的ID主键,那么我们如果需要正确排序的时候,可能需要增加一个CreateTime的日期类型,方便我们根据日期进行排序。

如果这个表还有一个外键的引用,建议统一命名标准,我一般使用“表名称_ID这样的名称,如User_ID、Contact_ID等相似的名称作为外键,不需要表的前缀。

2)数据库的模型设计

数据库的模型设计,我们建议在第三方的数据库设计工具上进行设计,如PowerDesigner这样的设计工具,使用工具设计数据库有很多好处,一个是可以高效率进行调整,二是根据需要生成不同的数据库类型Sql语句,三是可以全局了解各个表之间的关系等等。

使用PowerDesigner这样的数据库设计工具,能够在很大程度上提高我们数据库的设计效率。

2、项目框架的生成

设计好数据库后,我们通过代码生成工具进行整个项目框架的生成,这样对于我们在开发新项目上有很好的好处,里面的项目层级、DLL的 引用关系,已经处理好了,这样对我们非常方便。不过大多数情况下,我们都是增量开发较多,也就是我们可能前面已经完成了一些其他业务的开发,可能新增一个两个表,或者一批业务表的处理,这样也没关系,我们把新生成的代码复制到项目即可,由于项目生成的时候,指定了主命名空间和相关的表前缀,这样我们生成后的代码就方便阅读很多,减少累赘和出错的机会。

WInform开发框架,常见的分层模式,可以分为UI层、BLL层、DAL层、IDAL层、Entity层、公用类库层等等

这个分层,在Web项目或者WInform项目(包括WPF项目)这些分层都是可以重用的,这样我们就不用重复处理界面一下的逻辑,针对性的开发我们需要的界面层即可。

DAL层根据不同的需要,扩展支持不同的数据库类型,每个数据库类型,对应一个数据库访问实现层即可,它们实现IDAL层的接口,称之为数据库访问接口实现层。

如果我们选择开发混合型开发应用,我们可能还会有一些WCF的服务逻辑层、WCF服务层、客户端调用层、界面层(后面再介绍)等方面,如下的项目结构所示。

3、项目代码分析

通过代码工具,我们已经可以完整生成基础的项目框架了,下面我们来分析下项目的源码,从而知道整个框架的架构和代码的层次是如何的。

刚才我们看到,生成的项目里面,已经包含了实体类,我们以开篇介绍的一个表生成的代码来进行研究分析。

生成的实体类代码如下所示


    /// <summary>
    ///客户信息/// </summary>
[DataContract]public classCustomerInfo : BaseEntity
{
#region Field Members private string m_ID = System.Guid.NewGuid().ToString(); //编号 private string m_Name; //姓名 private int m_Age = 0; //年龄 private string m_Creator; //创建人 private DateTime m_CreateTime; //创建时间 #endregion #region Property Members /// <summary> ///编号/// </summary> [DataMember]public virtual stringID
{
get{return this.m_ID;
}
set{this.m_ID =value;
}
}
/// <summary> ///姓名/// </summary> [DataMember]public virtual stringName
{
get{return this.m_Name;
}
set{this.m_Name =value;
}
}
/// <summary> ///年龄/// </summary> [DataMember]public virtual intAge
{
get{return this.m_Age;
}
set{this.m_Age =value;
}
}
/// <summary> ///创建人/// </summary> [DataMember]public virtual stringCreator
{
get{return this.m_Creator;
}
set{this.m_Creator =value;
}
}
/// <summary> ///创建时间/// </summary> [DataMember]public virtualDateTime CreateTime
{
get{return this.m_CreateTime;
}
set{this.m_CreateTime =value;
}
}
#endregion}

View Code

其中我们看到下面的代码,里面使用了基类
BaseEntity
,这个是所有生成的实体类的基类,基类BaseEntity只是一个实体类的声明,没有什么属性,使用这个实体类基类,只是为了整个框架更好管理和控制。BaseEntity来源于公用类库,已经封装在里面了。

    /// <summary>
    ///客户信息/// </summary>
[DataContract]public classCustomerInfo : BaseEntity
{

另外,我们看到,实体类有注释,这些注释来自数据库的备注信息,包括字段的注释也是来自数据库的备注说明信息。

还有类的定义里面,还看到了
[DataContract]
的标签,以及类的属性
[DataMember]
,这个是WCF技术里面传输数据的协议声明,我们目前开发的应用,一般都是基于.NET4.0的了,因此包含这个属性方便我们在开发网络版项目的时候用到,一般情况下忽略即可。

我们继续看看实体类的其他部分代码:

        #region Field Members

        private string m_ID = System.Guid.NewGuid().ToString(); //编号
        private string m_Name; //姓名
        private int m_Age = 0; //年龄
        private string m_Creator; //创建人
        private DateTime m_CreateTime; //创建时间

        #endregion

我们看到,对于字符型的ID主键字段,代码生成的时候,已经自动添加默认属性值(GUID:System.Guid.NewGuid().ToString() )的了,这样我们创建实体类的时候,这个ID的值就已经生成了。

好了,基于篇幅的原因,下次继续介绍项目框架代码的各部分组成,以及他们之间的关系,注意的实现和内在约定等内容。

随笔背景
:在很多时候,很多入门不久的朋友都会问我:我是从其他语言转到C#开发的,有没有一些基础性的资料给我们学习学习呢,你的框架感觉一下太大了,希望有个循序渐进的教程或者视频来学习就好了。

其实也许我们每天面对的太多东西了,觉得很多都稀松平常了,即使很细微的地方,可能我们都已经形成习惯了。反过来,如果我们切换到其他领域,如IOS、android,那么开始我们可能对里面很多设计的规则不甚了解,开始可能也是一头雾水。

本篇继续上一篇《
循序渐进开发WinForm项目(1) --数据库设计和项目框架的生成
》,继续介绍如何循序渐进开发Winform项目,继续分析介绍Winform的项目代码,从而让我们更加了解其中的分层和项目框架的组成等内容。

1、数据访问接口的定义

上面我们分析了实体类的定义,本节继续分析其他部分的内容,如数据访问接口成的定义如下所示。

namespaceWHC.TestProject.IDAL
{
/// <summary> ///客户信息/// </summary> public interface ICustomer : IBaseDAL<CustomerInfo>{
}
}

这里面的代码很简单,没有多余的代码行,那么里面究竟发生了什么呢,其中的IBaseDAL又是什么定义呢?

其实,IBaseDAL就是定义了很多我们开发用到的基础接口,如标准的增删改查,以及衍生出来的一些其他接口,如分页查询,条件查询等接口内容。这个ICustomer就是用来定义一些除了标准接口不能实现外的业务接口。

IBaseDAL通过传入一个实体类,从而方便给基类接口提供强类型的数据类型指定,提高我们的开发效率,减少出错的机会。

我们可以在VS里面查看IBaseDAL的定义,如下所示:

可以看到里面很多相关的接口定义,有返回实体T的,也有返回List<T>的,还有DataTable类型等等,这些基础接口,经过我们多个项目的应用实践,已逐步稳定并能够提供很好的接口支持,方便我们快速调用处理。

即使我们在没有实现任何业务接口的情况下,仅仅利用标准的基类API,也基本上能够完成绝大多数的数据操作功能了。

2、数据访问接口实现类的定义

我们分析完IDAL的数据访问接口成的定义后,继续了解一下,如何基于这个接口进行访问层的实现设计的。数据访问的实现层在项目中的位置如下所示(以基于SqlServer的DALSQL层进行分析)。

它的类代码定义如下所示。

namespaceWHC.TestProject.DALSQL
{
/// <summary> ///客户信息/// </summary> public class Customer : BaseDALSQL<CustomerInfo>, ICustomer
{

数据访问接口实现层和接口定义层一样,都有一个基类,如基于SqlServer实现的基类为BaseDALSQL,这个基于SqlServer的数据访问基类,它也是继承自一个超级基类(大多数的实现在这里)AbstractBaseDAL。他们之间的继承关系如下所示

而我们刚才在项目工程的图里面看到,BaseDALSQL、IBaseDAL、AbstractBaseDAL这些类库由于具有很大的通用性,为了减少在不同的项目中进行复制导致维护问题,因此我们全部把这些经常使用到的基类或者接口,抽取到一个独立的类库里面,为了和普通的DotNET公用类库命名进行区分(WHC.Framework.Commons),我们把它命名为WHC.Framework.ControlUtil。

BaseDALSQL基类的定义如下所示。

这样做的好处是,在所有的模块里面,避免复制导致的版本维护问题,同时也减少代码的重复生成,增量生成的全部代码,可以一次性复制到整个项目工程里面,而不会导致基础类库的替换,因为这些基类不在生成目录里面,所有生成的类文件,都是和业务表相关的,如下所示。

具体的数据访问实现类(如Customer),它把数据库信息转换为实体类,有一个函数,在代码生成的时候已经生成;同时在把实体类的属性保存到数据库也有一个类似CRM的映射关系,从而实现可空的字段获取和更新操作。

        /// <summary>
        ///将DataReader的属性值转化为实体类的属性值,返回实体类/// </summary>
        /// <param name="dr">有效的DataReader对象</param>
        /// <returns>实体类对象</returns>
        protected overrideCustomerInfo DataReaderToEntity(IDataReader dataReader)
{
CustomerInfo info
= newCustomerInfo();
SmartDataReader reader
= newSmartDataReader(dataReader);

info.ID
= reader.GetString("ID");
info.Name
= reader.GetString("Name");
info.Age
= reader.GetInt32("Age");
info.Creator
= reader.GetString("Creator");
info.CreateTime
= reader.GetDateTime("CreateTime");returninfo;
}
/// <summary> ///将实体对象的属性值转化为Hashtable对应的键值/// </summary> /// <param name="obj">有效的实体对象</param> /// <returns>包含键值映射的Hashtable</returns> protected overrideHashtable GetHashByEntity(CustomerInfo obj)
{
CustomerInfo info
= obj asCustomerInfo;
Hashtable hash
= newHashtable();

hash.Add(
"ID", info.ID);
hash.Add(
"Name", info.Name);
hash.Add(
"Age", info.Age);
hash.Add(
"Creator", info.Creator);
hash.Add(
"CreateTime", info.CreateTime);returnhash;
}

3、业务逻辑层的实现分析

分析完成了数据访问层的接口和实现类后,我们来进一步看看业务逻辑层的实现分析,由于数据访问层的本意是基于特定的数据库实现,因此业务逻辑层就是抽象不同的数据库,让它们根据配置,指向不同的数据库实现类,从而实现多数据库的支持。

namespaceWHC.TestProject.BLL
{
/// <summary> ///客户信息/// </summary> public class Customer : BaseBLL<CustomerInfo>{public Customer() : base()
{
base.Init(this.GetType().FullName, System.Reflection.Assembly.GetExecutingAssembly().GetName().Name);
}
}
}

业务逻辑层的代码也很简单,在构造函数里面Init一下即可,之所以使用这个Init操作,其实为了确定BLL层的业务对象名称和指定在哪个程序集里面进行构造的需要,让给基类进行必要的创建工作。

在BaseBLL的Init函数里面,我们根据子类传入的相关参数,由于我们约定了数据访问类的命名空间,因此只根据数据库配置的不同需要,替换部分名称,就可以具体的构造出一个数据访问类了。

            #region 根据不同的数据库类型,构造相应的DAL层AppConfig config= newAppConfig();string dbType = config.AppConfigGet("ComponentDbType");if (string.IsNullOrEmpty(dbType))
{
dbType
= "sqlserver";
}
dbType
=dbType.ToLower();string DALPrefix = "";if (dbType == "sqlserver")
{
DALPrefix
= "DALSQL.";
}
else if (dbType == "access")
{
DALPrefix
= "DALAccess.";
}
else if (dbType == "oracle")
{
DALPrefix
= "DALOracle.";
}
else if (dbType == "sqlite")
{
DALPrefix
= "DALSQLite.";
}
else if (dbType == "mysql")
{
DALPrefix
= "DALMySql.";
}
#endregion this.dalName = bllFullName.Replace(bllPrefix, DALPrefix);//替换中级的BLL.为DAL.,就是DAL类的全名 baseDal = Reflect<IBaseDAL<T>>.Create(this.dalName, dalAssemblyName);//构造对应的DAL数据访问层的对象类

这样精确构造出来的数据库访问访问对象,并把它转换为基类接口,那么就可以在BaseBLL类里的基类接口进行调用了。

而构造业务对象,通过BLLFactory<T>的泛型工厂,更能够精确构造出对应的业务对象类,这样构造出来的对象具有强类型,非常方便使用。

以上就是业务逻辑层,数据访问层和数据访问接口层的设计关系,为了高效进行开发工作,我们一定要使用强类型的接口调用,这样可以大大减少出错机会,而返回的基类接口,由于传入了特定的具体类型T,也能够构造出强类型的列表或者对象。因此,合理利用泛型,能够是我们的开发体验更加美好,更加高效。

随笔背景
:在很多时候,很多入门不久的朋友都会问我:我是从其他语言转到C#开发的,有没有一些基础性的资料给我们学习学习呢,你的框架感觉一下太大了,希望有个循序渐进的教程或者视频来学习就好了。

其实也许我们每天面对的太多东西了,觉得很多都稀松平常了,即使很细微的地方,可能我们都已经形成习惯了。反过来,如果我们切换到其他领域,如IOS、android,那么开始我们可能对里面很多设计的规则不甚了解,开始可能也是一头雾水。

本篇继续上一篇《
循序渐进开发WinForm项目(2)--项目代码的分析
》,继续介绍如何循序渐进开发Winform项目,继续介绍Winform界面层的项目设计以及相关代码的组成部分,使得我们逐渐了解一个完整的开发方案过程。

1、界面层的项目搭建

我们从前面两篇随笔的介绍中,可以大概了解到界面层以下所发生的事情,本篇继续这一趋势,介绍界面层的开发内容。

首先我们创建一个Windows Application项目,使用VS2010(其他版本也可以)创建的Windows应用程序项目,然后把它们的项目目标框架和输出类型修改一下,如下所示。

然后删除自动生成的所有窗体和类库代码,界面最后成为如下的内容。

这样做的本意,是把这个界面部分作为一个独立的模块,我们在启动程序框架里面就可以很方便把它包含进来使用,因为我们知道,我们每次开发的业务可能都只是属于一个大的项目中的某部分模块,按模块的划分来进行开发,有方便管理维护、方便整合使用等特点。

最终在主体界面中整合的是一个个模块,每个模块里面又有不同的窗体界面可供使用,大概的界面模块组合图形如下所示。

2、业务模块的Winform界面的生成

从第一小节中可以搭建一个干净的界面项目模块,但是里面还没有任何的内容,这就需要我们进一步生成或者手工编写我们所需的界面内容了。

为了加快效率,减少界面控件名称的琐碎设置,减少对象赋值的代码编写,我们一般建议采用工具来进行界面的生成,如下界面所示。

使用代码生成工具生成的界面,命名和数据库字段名称对应,统一风格,减少出错的几率,提高开发效率。

通过以上设置的界面,设置我们在列表界面里面的查询条件内容,以及需要显示的字段内容;和编辑、新增对象界面的数据内容,然后我们就可以生成相关的窗体界面了。

单击“Windows界面代码生成”后,生成的窗体界面文件如下所示,分为了两个部分,一个是列表界面,一个是数据编辑界面,通过手工的做法,我们也建议把新增、编辑数据这样合并处理,这样可以减少大项目模块的代码文件。

把相关的界面内容复制到刚才创建的空项目工程上,并添加相关的类库应用,如框架需要支持的界面基类,公用类库等必备的程序集,这也是我们常见的开发操作了,再次不在赘述。

最后界面层的项目截图如下所示。

3、窗体界面的调整处理

一般来说,自动生成的界面,可能或多或少需要进行手工的一些处理,如可能一些类库没有添加应用,关联的项目没有添加进来等原因,或者是数据类型转换的问题,不过一般问题很小,稍微处理下就能解决的。

下面我们来对自动生成的窗体界面进行一定的调整,以方便我们的数据显示和处理。

自动创建的列表界面打开如下所示。

粗看我们应该觉得还是挺标准的列表界面,但是为了更好看,更统一,我们还是需要进行一些微调,如由于年龄是数值型,这里查询条件有两个作为一个区间进行组合查询的;另外,每个查询条件的控件宽度默认是会自动缩放的,由于条件较少,我们还是把它设置为固定宽度较好,最终我们稍微美化调整后的窗体界面如下所示。

搞定列表界面,下面我们再来看看数据的编辑界面部分,数据编辑界面生成的效果如下所示(由于版面的原因,我把它进行了一定的缩小)。

我们看到,里面的字段说明,控件的名称等内容,都已经合理安排好了,基本上设计数据库的时候,指定的字段备注就能正确生成出来了。

这里演示的表字段比较少,因此可能排版布局方面没有很好的演示效果,不过我们知道DevExpress本身的LayoutControl布局很强大,我们可以随意调整里面控件的位置。

对于控件的类型,也可以进行不同类型的转换,如可以吧文本类型,转换为数值类型的输入控件等等。

这样简单的界面可能很多读者会说,这个我自己添加几个控件就可以了,也很快的,但是如果对于需要显示的界面控件(数据库字段很多)的情况下,如果控件显示内容、控件名称、布局位置,全部都要手工操作调整,那么这个工作量是非常惊人的。

我在开发自己的一个项目程序《客户关系管理系统》的时候,里面的业务很多表的字段都不少,我就只需要拖动,调整下位置或者增加一些特别的控件进行合理展示即可,不需要去调整太多内容,这样可以为我节省大量的界面开发时间,更重要的是,减少出错的几率。

如对于《客户关系管理系统》客户联系人这个表,字段不少,生成的界面很多控件,默认都是单排的,我根据需要,把他们进行再次排列,并增加一个TabControl选项卡的控件,以便放置更多的内容,和进行必要的区分,详细调整后的界面效果如下所示,这个就是一个复杂界面调整的一个好的案例说明了。

4、整合业务逻辑层到界面项目

我刚才在设计编译界面层的时候,是在界面项目引用的地方,把业务逻辑层的项目工程包含到引用里面了,如下所示。

这样的引用操作是没什么问题,但是我们的项目组件如果拆分的较多,每个界面还需要带着一个业务逻辑的程序集,那么程序集DLL的数量就会翻倍,不便于管理。

我的意思就是把他们全部进行整合,避免文件过于零散,但是最好避免到处复制文件的局面,这样增加维护成本,不是我们希望看见的。

基于上面的两种考虑,我们可以在界面项目里面添加文件“链接”的操作,也就是代码文件的影子,编译的时候,他们是编译整合到程序集里面的,但是我们维护的时候,它们所有的”链接“引用,都是指向同一个文件,不会造成多处复制的问题。

最终的项目构成如下所示,并顺利可以通过编译,这样我们在使用这个Customer的业务模块的时候,只需要一个界面部分的程序集(WHC.TestProject.UIDx.DLL)即可。

下一篇我们继续介绍界面模块如何整合到主体项目工程里面,进行使用等操作。

随笔背景
:在很多时候,很多入门不久的朋友都会问我:我是从其他语言转到C#开发的,有没有一些基础性的资料给我们学习学习呢,你的框架感觉一下太大了,希望有个循序渐进的教程或者视频来学习就好了。

其实也许我们每天面对的太多东西了,觉得很多都稀松平常了,即使很细微的地方,可能我们都已经形成习惯了。反过来,如果我们切换到其他领域,如IOS、android,那么开始我们可能对里面很多设计的规则不甚了解,开始可能也是一头雾水。

本篇继续上一篇《
循序渐进开发WinForm项目(3)--Winform界面层的项目设计
》,继续介绍如何循序渐进开发Winform项目,继续介绍Winform界面模块如何整合到主体项目工程里面,进行使用等操作,使得我们逐渐了解一个完整的开发方案过程。

1、窗体界面的集成使用

上篇介绍了如何利用工具进行Winform界面层窗体的快速生成,并进行适当的调整,已达到合理布局,显示美观等的效果,本篇继续这一主题介绍下去,上篇我们开发好的独立界面模块,如何在主体项目中集成使用呢?

首先我们把生成的界面层DLL复制到项目工程中,然后在主项目工程中添加相关的应用,如下所示。

然后,我们需要做的就是,在主体界面模块里面添加一个功能按钮的入口,如下所示是我在我的框架界面启动模块里面添加一个按钮的效果。

然后在按钮的单击事件里面,添加下面的代码即可。

        private void tool_Customer_ItemClick(objectsender, DevExpress.XtraBars.ItemClickEventArgs e)
{
ChildWinManagement.LoadMdiForm(
this, typeof(WHC.TestProject.UI.FrmCustomer));
}

其中
ChildWinManagement
是公用类库里面一个辅助类,用来在多文档的情况下进行窗体的展示,传入一个MDI的Parent的窗体对象引用,另外一个是构造显示的窗体类型,它会根据类型来判断是否已经实例化了,如果存在就打开,否则就创建一个新的窗体病显示出来主界面里面。

启动界面,后看到的效果如下所示(我们在后台添加一些测试数据后)。

双击数据出来的编辑界面如下所示。

这样,我们在还没有添加任何代码和逻辑实现的情况下,基本的界面已经出来了,而且相关的数据存储和显示的功能已经存在,我们所需要做的就只是细化里面的内容即可。

2、窗体界面的在插件化框架的集成使用

第一节中介绍的是传统方式的界面模块的继承,开发框架本身也还提供了另外一种方式的界面模块集成方式,插件化的模块化集成。我们通过把相关的DLL复制到运行的目录下,并且在数据库里面配置好相关的Winform模块信息后,就可以在主界面中调用出来是用来。

关于插件化的框架实现的介绍,大家可以看看我前面写的一篇博客文章《
Winform开发框架之插件化应用框架实现
》。

首先我们配置菜单的时候,登陆权限管理系统,添加相关的菜单项目,如下所示。当然,如果你有自己的菜单管理模块,自己通过自己的手工设置好相关的信息即可。

好,搞定菜单的动态配置后,我们重新登陆下系统的主界面,看看有无变化了。

从主界面的Ribbon工具栏,我们可以看到,里面已经新增了一个客户管理(红色部分)的内容了,这个位置就是我们刚才新增菜单的位置。单击菜单按钮,那么就会展现出来客户管理的内容了。

整个主界面框架,加上打开的客户管理界面,整体的效果是一个多文档的界面效果。

3、集成登陆用户信息

前面几篇的随笔,主要就是介绍给我们认识如何快速开发一个模块,并且集成到系统框架里面进行使用,我们甚至还没有开始编码,就已经给我们处理好很多细节上的东西,基本上就已经完成一个业务小模块的展示工作了。

完成本文的前面两个小节,不知道你们有没有发现,我们好像还没有真正的整合登陆的用户信息呢?在独立的系统模块开发过程中,我们如何整合登陆的用户信息呢?

我们重新回到开发的业务模块的界面项目里面看看原来的编辑界面代码。

这里面对于保存新增的数据,我们调整一下,把它的创建的人员和时间在代码
FrmEditCustomer.cs
里面调整成合理的代码,记录人员和当前时间。

        /// <summary>
        ///编辑或者保存状态下取值函数/// </summary>
        /// <param name="info"></param>
        private voidSetInfo(CustomerInfo info)
{
info.Name
=txtName.Text;
info.Age
=txtAge.Value.ToString().ToInt32();
}
/// <summary> ///新增状态下的数据保存/// </summary> /// <returns></returns> public override boolSaveAddNew()
{
CustomerInfo info
= tempInfo;//必须使用存在的局部变量,因为部分信息可能被附件使用 SetInfo(info);

info.CreateTime
=DateTime.Now;
info.Creator
= LoginUserInfo.ID.ToString();//为了更好管理,我们这里存储用户的ID,而非名称 try{#region 新增数据 bool succeed = BLLFactory<Customer>.Instance.Insert(info);if(succeed)
{
//可添加其他关联操作 return true;
}
#endregion}catch(Exception ex)
{
LogTextHelper.Error(ex);
MessageDxUtil.ShowError(ex.Message);
}
return false;
}

其中红色部分就是我们新增的内容,我在代码里面存储
当前登陆用户
的ID:LoginUserInfo.ID.ToString()。

这里的LoginUserInfo是窗体基类的一个属性,这个属性通过两种方式获得,一个是通过用户在调用窗体显示前进行指定,一种是通过基类自动把缓存里面的用户对象赋值。

如下面的代码就是界面基类BaseForm的部分代码。

namespaceWHC.Framework.BaseUI
{
/// <summary> ///常规界面基类/// </summary> public partial classBaseForm : DevExpress.XtraEditors.XtraForm, IFunction
{
public event EventHandler OnDataSaved;//子窗体数据保存的触发 publicBaseForm()
{
InitializeComponent();
//为了保证一些界面控件的权限控制和身份确认,以及简化操作,在界面初始化的时候,从缓存里面内容(如果存在的话)//继承的子模块,也可以通过InitFunction()进行指定用户相关信息 this.LoginUserInfo = Cache.Instance["LoginUserInfo"] as LoginUserInfo;
this.FunctionDict = Cache.Instance["FunctionDict"] as Dictionary<string, string>
;
}

这些用户和功能的信息来源于登陆主界面的时候,我们把它们进行了缓存,方便基类窗体进行获取。

                    Portal.gc.LoginUserInfo =Portal.gc.ConvertToLoginUser(info);
Cache.Instance.Add(
"LoginUserInfo", Portal.gc.LoginUserInfo);//缓存用户信息,方便后续处理 Cache.Instance.Add("FunctionDict", Portal.gc.FunctionDict);//缓存权限信息,方便后续使用

第二种方式指定当前用户信息的步骤,是通过基类窗体的InitFunction函数进行指定。

        /// <summary>
        ///初始化权限控制信息/// </summary>
        public void InitFunction(LoginUserInfo userInfo, Dictionary<string, string>functionDict)
{
if (userInfo != null)
{
this.LoginUserInfo =userInfo;
}
if (functionDict != null && functionDict.Count > 0)
{
this.FunctionDict =functionDict;
}
}

手工指定当前用户信息的调用代码如下所示。

        private void btnAddNew_Click(objectsender, EventArgs e)
{
FrmEditCustomer dlg
= newFrmEditCustomer();
dlg.InitFunction(
base.LoginUserInfo, base.FunctionDict);//该步骤省略也可以,用户信息以通过基类缓存进行获取 if (DialogResult.OK ==dlg.ShowDialog())
{
BindData();
}
}

一般情况下,我们建议采用第一种,不用多余的代码进行设置指定,只需要在登录的时候,把它放到缓存里面即可,这样界面基类实例化的时候,就会自动获取用户信息了,这个操作类似于Web领域里面的Session操作,只要存储/获取的键值保存一致即可。

好了,我们前面说到,保存的时候,是保存当前用户的ID信息,那么我们在列表展示的时候,默认就会展示用户的ID信息而已,得到的界面效果如下所示。

我们为了更好展示内容,就需要对用户ID的数据进行转义。

由于DevExpress有这样对每行记录进行转义的操作,我们在列表界面上添加一个转义函数。

            this.winGridViewPager1.gridView1.CustomColumnDisplayText += new DevExpress.XtraGrid.Views.Base.CustomColumnDisplayTextEventHandler(gridView1_CustomColumnDisplayText);

数据转义函数里面涉及到对权限系统模块的引用(我们需要把ID转义为FullName(用户全名)),我们把权限模块的DLL引用包含进来即可(因为权限管理模块是所有
界面模块
都可以使用的)。

然后在这个函数里面对当前的Creator进行转义。

        void gridView1_CustomColumnDisplayText(objectsender, DevExpress.XtraGrid.Views.Base.CustomColumnDisplayTextEventArgs e)
{
if (e.Column.ColumnType == typeof(DateTime))
{
string columnName =e.Column.FieldName;if (e.Value != null)
{
if (Convert.ToDateTime(e.Value) <= Convert.ToDateTime("1900-1-1"))
{
e.DisplayText
= "";
}
else{
e.DisplayText
= Convert.ToDateTime(e.Value).ToString("yyyy-MM-dd HH:mm");//yyyy-MM-dd }
}
}
else if (e.Column.FieldName == "Creator")
{
if (e.Value != null)
{
e.DisplayText
= BLLFactory<User>.Instance.GetFullNameByID(e.Value.ToString().ToInt32());
}
}
}

然后复制文件,重新运行主程序即可看到如下界面所示。

至此,我们本小节已经完成了,登陆用户信息的记录和转义的操作了,当然我们系统模块里面,可能还有很多地方需要用到用户信息的或者角色信息的,这个例子只是一个抛砖引玉的操作。

循序渐进开发WInform项目--系列文章导引:


循序渐进开发WinForm项目(4)--Winform界面模块的集成使用


循序渐进开发WinForm项目(3)--Winform界面层的项目设计


循序渐进开发WinForm项目(2)--项目代码的分析


循序渐进开发WinForm项目(1) --数据库设计和项目框架的生成