有园友号召博客园组织一些开源项目,得到大家的积极认可。来博客园五年多,学习到了很多技术,也认识了很多朋友。为感谢大家的帮助,从硬盘的一个角落里,把自己几年前的一个客户关系管理(CRM)项目开源出来。因为涉及到版权原因,已经对实现的业务逻辑和核心部分作了屏蔽处理,主要贡献是它的界面框架,美观实用的界面框架。
如上图所示,窗体标题是CRM,主体采用淡蓝色的背景,包含标准的菜单,工具栏,左边是Navigation Bar,右边是显示界面的主体框架。这种布局也是模拟Outlook的做法,目前还是比较受欢迎的。
界面的语言是英语的,窗体的界面直接手写在界面中,源程序包含英简繁三种语言,在这里做了简化处理,只包含英语的界面。左边的Navigation Bar中的菜单,原来也是在菜单设计器中设计的,这里直接改成在树型中设计,没有实现菜单动态加载。因为我要向您分享的是它的界面框架,而不是它的各个细节的处理,你知道这样做很容易侵犯版权。
进入到代码里面,来分析一下它的实现。请看下图,Visual Studio中的设计效果
为了实现类似Office界面一样的效果框架,请继承于KryptonForm窗体,代码看起来是这样的
public partial class FrmMain : KryptonForm { public FrmMain() { InitializeComponent(); } private void Form1_Load(object sender, EventArgs e) { FrmStart frm = new FrmStart(); frm.TopLevel = false; frm.Parent = kryptonMainPanel; frm.Dock = DockStyle.Fill; frm.Show(); } }
如你所看到的代码,主窗体启动时,加载起始页。右边是个Panel,把窗体加载到Panel里面,需要设置Form.TopLevel=false。
这个软件实现的基本功能是客户关系管理。客户区域,客户信息,用户角色权限,数据导入导出,这几个基本的功能,还实现有报表,采用SQL Server Reporting Services的轻量级RDLC报表技术,浏览客户报表
对于显示RDLC报表,主要的实现代码如下
rpt.LocalReport.ReportPath = AppDomain.CurrentDomain.BaseDirectory + "CustomerReport.rdlc"; BaseOperate boperate = new BaseOperate(); DataTable tbl = boperate.getds(" SELECT ClientID, CName, CStep, CRoot, CType, CTrade, CArea,
CPhone, CFax, CPostCode, CAddress, CEmail, CRemark FROM dbo.tb_ClientInfo ", "tb_ClientInfo").Tables[0]; if (tbl != null) { rpt.LocalReport.DataSources.Clear(); rpt.LocalReport.DataSources.Add(new Microsoft.Reporting.WinForms.ReportDataSource(
"db_CrmDataSet_tb_ClientInfo", tbl)); rpt.LocalReport.Refresh(); } rpt.RefreshReport();
把写好的SQL代码传送到SQL Server,返回数据集DataSet,传到报表中,即可显示RDLC报表的内容。
数据访问技术,采用基本的SQL+DAO实现,没有使用ORM,也没有使用数据绑定技术,所以代码看起来会有些臃肿,而且不必要。如果你对下面的代码感觉到厌恶,请参考我的这篇文章《知识管理系统Data Solution研发日记之十一 数据绑定技术的应用》,它可大大简化把数据绑定到界面,再从界面中写回到数据库中。
private void frmUserManage_Load(object sender, EventArgs e) { DataSet myds = boperate.getds(M_str_sql,M_str_table); dgvUInfo.DataSource = myds.Tables[0]; if (myds.Tables[0].Rows.Count > 0) tsbtnDel.Enabled = true; else tsbtnDel.Enabled = false; } private void tsbtnAdd_Click(object sender, EventArgs e) { opAndvalidate.autoNum("select UserID from tb_User", "tb_User", "UserID", "YH", "1000001", txtUserID); tsbtnSave.Enabled = true; M_int_judge = 0; txtUserName.Text = ""; txtUserPwd.Text = ""; }
如果你有数据绑定的经验,这些代码是不必要的。限制于当时我的技术水平,只做到了这样,的确只有不断的学习才有进步。如果一直停留在初始的技术经验上,可能工作五年和工作一年没有什么区别。这句话有些尖锐,面试的时候经常被面试官提到,经常的对自己说这句话,反思自己技术上的收获,不断进步。
CRM系统的代码的可读价值不高,基本上就是DAO+SQL的组合,业务逻辑和界面混淆,数据访问与业务逻辑混淆,所以不推荐你直接阅读它的代码。我的目标是,你只要从代码中获取你需要的界面框架即可,我要达表达的重点也在于它的界面框架。如果你想把它改良一下,下面我提供的思路可以帮助你做这件事。
1. 界面实现为多国语言,简体中文,英语,繁体。推荐用数据库表的形式来保存多国语言的资源文件。因为系统部署到客户那边后,客户可能需要修改界面的语言。举例说明,关于中心,中央的英语表示,在英国用Centre,美国用常Centre,这是两种文化的文字差异。CRM系统要允许用户修改界面的默认语言定义。存到数据库,再提供一个表操作程序,更新语言资源。
2. 关于实现多国语言的代码,下面的例子很值得你借鉴运用。
private void ApplyLanguageResource() { foreach (Control ctl in Controls) { if(ctrl is Label) { Label lable=ctrl as Label; label.Text=LanguageHelper.GetTranslation(label.Text) } if(ctrl is ToolStripMenuItem) { ToolStripMenuItem strip=ctrl as ToolStripMenuItem strip.Text=LanguageHelper.GetTranslation(label.Text) } //tab control,button... } } this.Text=LanguageHelper.GetTranslation(this.Text) }
再把这个方法放到BaseForm类型中,让其它的类型继承于这个BaseForm类型,于是乎,任何新创建的窗体,都可以获取改变界面语言的能力。这也是我见到的最好的实现多国语言的方案。
这里有一点代码重复的味道,应该是简写成这样才是没有坏味道(重复)的代码
private void ApplyLanguageResource() { foreach (Control ctl in Controls) { //对于设置文字都是Text属性的控件(Label,Button,ToolStripMenuItem),都可以这样写 string originalText=ReflectionHelper.GetProperty(ctrl,"Text"); string tranlatedText=LanguageHelper.GetTranslation(originalText); ReflectionHelper.SetProperty(ctrl,"Text", translatedText); } this.Text=LanguageHelper.GetTranslation(this.Text) }
运用反射技术,实现通用的设置文字语言的属性。代码比上面的简洁很多。
3 经过几年的积累,对于访问SQL Server更多认识理解。相对于使用ORM框架,即使要手写SQL语句访问数据库,也要制作一个SQL代码生成器,以减少键盘的敲打次数。手写SQL最怕的就是增加和减少字段,可能所有的SQL Script都要重新调试一遍。使用ORM框架则几乎可以不改动任何代码。把SQL Script封装到DAO类型中,还应该提供一个DAO的类型工厂,缓存DAO类型,提高性能。我以为这两条经验,可以改善SQL Script+DAO类型项目的效率和可维护性。
4 MIS类型的系统,通常就是主要资料输入,数据查询和报表查询。为三种类型的窗体,可以制作成标准的界面框架。资料输入,包含新增,修改,删除,导入功能,报表查询则只呈现指定的报表(Reporting Service,Crystal Rport)即可,数据查询针对于已经过帐或是以往的历史记录,只读不可修改。这三个窗体做可以做成标准的MIS窗体,以提高开发速度。请参考文章《信息化基础建设 窗体特性》和《信息化基础建设 窗体开发》来了解我是如何实现这三类标准窗体的。
5. 对于左边的Navigation Bar的内容,需要提供一个Menu Designer,以设计它的内容。实用的系统,一般都要考虑到用户的喜好,对界面的定制能力。用户不会轻易认可我们设计的界面,它还需要在此基础上做一些小小的改动,或是个性化设置。个性化源自于ASP.NET 2.O,后来Windows 7操作系统也引入了这个概念,可以定制你喜欢的部分。
6 对于界面的方案的演化,一直以来都有一个规律,跟随的微软的步伐就没有错。Visual C++ 5/6创建了标准的SDI/MDI应用框架,这时间的大量的程序都是模仿WORD/EXCEL的MDI风格。甚至还会给自己的产品名称后面加一个XP,以表示很符合潮流。当Office 2007出世以后,Ribbon界面的风格跟进,一时间许多程序又演化成Ribbon风格的界面样子,第三方控件厂商也都提供Ribbon风格的窗体控件。而现在的,TAB-MDI应该是流行的趋势
这种风格的界面框架,用户操作方便,程序实现上,把以前的SDI/MDI迁移(Migration,Upgrade)到这种TAB-MID界面上来也简单容易。我观察到,有几个公司的上市产品都是这种界面模式,更加坚定了我对这种界面模式的信心。
在今年的上半年,我写过一篇文章,讲解这种界面模式的实现及其源码,请参考文章《Management Console 工具管理类软件通用开发框架(开放源码)》获取它的实现。
请到epn.codeplex.com下载最新的源代码和数据库,代码名称是Paradox CRM。