博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

信息化基础建设 窗体开发

Posted on 2011-11-17 19:33  gczhao  阅读(221)  评论(0编辑  收藏  举报

本文转自:http://www.cnblogs.com/JamesLi2015/archive/2011/06/08/2075046.html

 

 

窗体开发的主题内容

1. 窗体属性设置

2. CRUD与代码生成

3. 高级选项

4. 设计规范

 

使用EPN框架制作员工主档窗体,效果图如下

image

 

属性设置

增加窗体,EmployeeMaster

clip_image004

设置继承的类为EntryForm

[FunctionCode("SAISEM")]

public partial class EmployeeMaster : EntryForm

{

     public EmployeeMaster()

   {

     InitializeComponent();

    }

}

并且添加功能编码SAISEM

在EPN的分类设置选项中,设置窗体的EPN属性如下

clip_image005

这表示,窗体支持新增,修改,删除,拷贝,导入/导出,过帐,增加附件,不支持批核(工作流)

拖动一个EntityCollection到窗体中,并设置它的EntityFactoryToUse对象

clip_image006

依照需要展示的数据层次结构,拖动为数个BindingSource到窗体中。

举例:比如要显示采购单头和采购明细信息,需要拖动2个BindingSource,分别显示采购单头和采购明细

对于本例,需要显示员工表的表头信息和文件(Document)信息,则需要拖动2个BindingSource

组件窗口看起来是这样

clip_image007

然后依次设置BindingSource的数据源

设置employeeBindingSource的数据源

clip_image008

设置employeeDocumentBindingSource的数据源

clip_image009

依据需要显示的字段,放窗体中拖放TextEditor,NumberiEditor,Grid,CheckBox

在Visual Studio 2010中,看起来是这样

clip_image011

设置窗体的数据源属性,MainBindingSource和NavigateBindingSource

clip_image012

如果当前窗体要一次性将所有的Entity读入到窗体的GridView,则将Form的MainBindingSource与navigatorBindingSource都设置为employeeBindingSource。

如果窗体打开时只需显示一条Entity,则将窗体的MainBindingSource设为employeeBindingSource,再添加一个新的bindingSource组件,数据源不要选,留空。将其设为窗体的NavigatorBindingSource。

设置窗体主键属性

clip_image013

设置值如下

clip_image014

至此,F5,窗体已经可以运行,在快速启动栏中输入SAISEM,即可启动窗体,如开篇所展示的效果。

 

代码生成

到目前为止,窗体已经可以运行,但并不可以进行数据的读写,我们还要给它新增加数据读写代码。

再引用开发框架中关于库存项目的一张图

clip_image015

BusinessLogic 业务逻辑,业务实体,这个由代码生成器自动生成

Interface 接口 读写数据和业务逻辑的接口

Manager 实现接口 实现接口

System Administration/Inventory, 界面,放入EmployeeMaster窗体

为此,祭出代码生成工具CodeSmith,帮忙我们完成枯燥容易出错的应用框架的代码。

接口文件IEmployeeManager,将生成的文件拷贝到Interface项目中

clip_image017

实现文件EmployeeManager,将生成的文件拷贝到Manager项目中

clip_image019

窗体文件生成,将生成的文件内容拷贝到EmployeeMaster窗体文件中

clip_image021

代码如下

private IEmployeeManager _EmployeeManager = null ;

private EmployeeEntity _Employee = null ;

protected override void OnShown(EventArgs e)

{

base.OnShown(e);

if (!DesignMode)

{

}

}

protected override void OnLoad(EventArgs e)

{

if(!DesignMode)

this._EmployeeManager = ClientProxyFactory.CreateProxyInstance<IEmployeeManager>();

base.OnLoad(e);

}

protected override void InitNavigator(InitNavigatorArgs args)

{

base.InitNavigator(args);

args.SortExpression.Add(EmployeeFields.EmployeeNo | SortOperator.Ascending);

}

protected override EntityBase2 LoadData(Dictionary<string, string> refNo)

{

base.LoadData(refNo);

string employeeNo = string.Empty;

if (refNo.TryGetValue("EmployeeNo", out employeeNo))

{

IPrefetchPath2 prefetchPath = new PrefetchPath2((int)EntityType.EmployeeEntity);

prefetchPath.Add(EmployeeEntity.PrefetchPathEmployeeDocument);

_employee = _employeeManager.GetEmployee(employeeNo, prefetchPath);

}

else

{

_employee = new EmployeeEntity();

}

return _employee;

}

protected override void BindControls(EntityBase2 entity)

{

base.BindControls(entity);

this.employeeBindingSource.DataSource = entity;

}

protected override EntityBase2 Add( )

{

base.Add();

this._Employee = new EmployeeEntity();

return _Employee;

}

protected override EntityBase2 Save(EntityBase2 entityToSave)

{

base.Save(entityToSave);

EmployeeEntity _Employee = (EmployeeEntity)entityToSave;

this._Employee =this._EmployeeManager.SaveEmployee(_Employee);

return _Employee;

}

protected override void Delete(EntityBase2 entityToDelete)

{

base.Delete(entityToDelete);

EmployeeEntity Employee = (EmployeeEntity)entityToDelete;

this._EmployeeManager.DeleteEmployee(Employee);

}

protected override object Clone(Dictionary<string, string> refNo)

{

base.Clone(refNo);

string employeeNo = string.Empty;

refNo.TryGetValue("EmployeeNo", out employeeNo);

if (string.IsNullOrEmpty(employeeNo))

{

using (ILookupForm lookup = EPN.Common.GetLookupForm("EmployeeLookup"))

{

lookup.SetCurrentValue(CurrentRefNo);

if (lookup.ShowDialog() != DialogResult.OK)

return null;

employeeNo = lookup.GetFirstSelectionValue();

}

}

if (!string.IsNullOrEmpty(employeeNo))

{

this._employee = this._employeeManager.CloneEmployee(employeeNo);

return this._employee;

}

return null;

}

protected override void ReleaseResources( )

{

base.ReleaseResources();

try

{

_Employee = null;

_EmployeeManager= null;

}

catch

{

}

}

到此,已经完成了窗体开发的所有内容。

为加入更多的验证逻辑,生成一些验证,放到验证项目中

clip_image023

比如,需要验证身份证号码必须输入,电话号码要符合当地的编号规则,等等,这些逻辑验证,都可以写在这里

 

高级选项

到此为止,几乎是没有敲任何的代码,我们就完成了员工主档的开发,这样的速度,肯定让你吃惊,也开始疑惑:

1. 业务逻辑写在哪里?

比如,当输入员工的出生日期为1983年时,程序应当自动算出员工的年龄为DateTime.Now- DateTime(1983,10,20), 当输入员工的学历为本科时,它在公司的组织架构级别中,就自动对应22B,而不是大专毕业对应的21B。

参考一开始设计的图,程序从一开始就是界面和逻辑分离的,逻辑直接写到BusinessLogical项目中去,也就是代码自动生成的EmployeeEntity文件。

这里有一个小技巧,代码生成器会为我们生成2个EmployeeEntity文件,一个是EmployeeEntity.cs, 另一个是EmployeeEntity.Logical.cs的文件,前一个文件,是依据数据表的字段,自动生成的文件,后一个文件,是存放自定义逻辑的地方,也就是需要放入逻辑的代码文件。

2. 程序界面上的特殊要求,通过以上生成的代码肯定没有做到。

比如,需要给员工主档添加图片显示,如图中,显示James Bond的图片,这个功能需要手动写。

其次,要给物料主档添加条形码支持,将生成的条形码作为物料编码的依据,这个功能也需要手写。

clip_image024

再比如开篇中的那张图片,Employee No.后面的按钮,是用来给员工智能编码用的。一般会依据员工学历,工作经验,专业的不同来产生相应的员工编码。

3. 逻辑和界面严格分离带来的一些问题。

如果要在逻辑里面根据不同的条件,给用户一个提示,这个会相当麻烦。因为逻辑与界面已经严格分离,绝不允许在逻辑中调用MessageBox.Show, 为了阻止这个行为的发生,在逻辑的References中,直接删除了System.Windows.Forms的引用。

同理,在界面中访问逻辑中的部分代码片段也相当麻烦,有时候在界面中也需要直接访问数据库,比如,在做销售送货时,根据当前用户输入的销售单号,判断这个SO是否已经完成送货,如果已经完成送货,则不允许产生送货单,这个逻辑只有在界面上实现,因为在BusinessLogical中Shipment对象还没有产生,无法做判断。

4. 性能 Performance

在把数据从数据库读到界面时,ORM和ADO.NET性能区别不大,但是,在做保存和更新时,ORM会判断当前已经做出修改的数据,仅仅生成这个被修改数据的UPDATE语句,这样的性能提升是很占优势的。

ADO.NET中,更新员工的语句一般是

UPDATE Name=’James Bond’ , Country=’ England’ WHERE EmployeeNo=’007’

很少可能会这样写,为了更新员工的名字,写一个SQL

UPDATE Name=’James Bond’ WHERE EmployeeNo=’007’

更新员工的国籍,于是又写一个SQL

UPDATE Country=’ England’ WHERE EmployeeNo=’007’

ORM框架会很高兴的帮忙我们判断,究竟有哪些数据被修改(dirty),并且只产生这些数据的UPDATE.

 

窗体规范

CodeSmith已经帮忙我们做好了大量的重复代码,也相当规范。

如果仍然要手写代码,遵守共同的规范,会使代码看起来更统一,规范

控件命名规范

 

控件类型

前缀

举例

Label  

lbl    

lblMessage

Button  

Btn

btnSave

GroupBox

gbx    

gbxMain

GridView

grd

grdSalesOrderDetails

 

仅有这个规范还不行,毕竟遵守起来还是有点困难,EPN框架还运用分词技术,自动为窗体中的控件重命名。

我们的窗体中的控件代码,看起来是这样

private EPN.WinUI.TextEditor txtDeptName;

private EPN.WinUI.Label lblDeptName;

private EPN.WinUI.PictureBox picImage;

private EPN.WinUI.GridView grdSalesOrder;

当为控件绑定属性时,自动为控件重命名。比如,我将TextBox1绑定到数据源EmployeeNo,于是控件被重命名为txtEmployeNo,看控件名称,就可以知道数据源,也知道控件类型,以达到高度的规范统一。

 

窗体的设计标准

1 DialogBox,对话框,一般要能响应ESC和Enter键,也有OK和Cancel2个按钮

2 窗体中控件的Tab order是否合理

3 Short cut key,快键键访问,所有的窗体也需要一致,也不能有冲突。

4 当窗体中需要盛放的数据量较大时,有2种思路

以GroupBox分组,以方便查看

clip_image025

当数据量再增多时,以Tab分组

clip_image026

5 窗体执行时间较长的任务,要使BackgroundWorker,以保证窗体可以接受响应,任务执行完毕后,或用MessageBox.Show显示successfully done,或与VS2010一样,在status bar中显示执行结果.比如在VS2010中成功check in代码后,在status bar中会显示成功签入变更集54498.

同时,cursor也不能放过,像这样写:

this.Cursor=Cursors.WaitCursor;

.....long operation

this.Cursor=Cursors.Default;

6 窗体组织方式,避免MDI forms,推荐tab式 MDI。

必要时,可以这样,把form放到panel中,设置form的TopLevel = false

clip_image028

7 ListView控件,要支持copy,而且可以多行copy

clip_image030

8 Enter和Tab key的作用,都会去下一个tab order的control

clip_image032

9 WinForms程序,最好的可以接受command line的启动方式,这样可以用bat命令启动。而且可以加启动参数。

VS2005、2008,2010都可以从command line启动。还记得常用的deven /resetpackage吗?

10 尽量阻止一台电脑,同时运行程序的两个实例,使用Singletion模式。

11 Target尽量是AnyCPU,而不x86。因为Microsoft Jet Access 数据库没有64bit的版本,宁愿移除对Jet Access的依赖和使用,也不愿把Target改成x86. 自Windows 7以来,64bit的OS已经相当普及,要使自己的程序不会一打开就stop working,就尽量编译成AnyCPU吧。

12 RichTextBox,如果是程序添加内容,需要auto scroll到最行的一行数据。观察VS2010编译的时候,Output窗口

clip_image034

13 对于用户不能修改的数据,不仅要使TextBox为readonly,最好还要使它的背景颜色变成gray,一目了然。

必须输入数据的控件,需要有个统一的提示,像这样

clip_image035

当该控件有值时,右边的红色图片hidden,没有值时,显示图片。这和Ajax中的watermark是相同的原理