目录第一章总体介绍51.1基本结构.......................51.2单据模型.......................71.3BillUIBuffer.....................91.4加载界面.......................101.5UI状态.........................101.6按钮..........................121.6.1自定义按钮..................131.6.2扩展状态...................161.6.3设置子按钮..................171.7事件处理.......................211.7.1按钮事件...................211.7.2编辑事件...................231.8业务动作处理.....................241目录目录1.9Putitalltogether...................25第二章如何制做卡片型单据272.1卡片型单据的类结构.................272.2卡片型单据界面示例.................282.3开发卡片型单据的步骤................292.3.1前期准备...................302.3.2快速搭建类框架................302.3.3开发出第一个卡片单据............302.3.4完善功能一:数据加载............322.3.5完善功能二:对数据的增、删、改操作...362.3.6完善功能三:前后台校验...........402.3.7变体一:单表体的卡片型单据........472.3.8变体二:单表头的卡片型单据........502.3.9变体三:虚拟主子表的卡片型单据.....512.3.10多子表的卡片型单据.............60第三章如何制作列表型单据693.1列表型单据的类结构.................693.2列表型单据界面示例.................703.3开发列表型单据的步骤................70用友软件c⃝20052目录目录3.3.1真正主子表的列表型单据..........703.3.2虚拟主子表的列表型单据..........74第四章如何制作管理型单据794.1管理型单据的类结构.................794.2管理型单据界面实例.................804.2.1真正主子表的管理型单据..........814.2.2虚拟主子表的管理型单据..........86第五章树卡片类型单据935.1树卡单据:树的数据和单据数据的关系.......945.2树卡型单据的类结构.................955.3树卡型单据的界面示例................965.4开发树卡型单据的步骤................965.4.1标准的树卡片型界面.............965.4.2多子表的树卡片型单据............1015.4.3单表的树卡片型单据.............101第六章树管理类型单据1036.1树管理型单据:树的数据和单据数据的关系....1036.2树管理型单据的类结构................104用友软件c⃝20053目录目录6.3开发树管理型单据的步骤..............1056.3.1标准的树管理型单据.............1056.3.2多子表的树管理型单据............1106.3.3虚拟主子表的树管理型单据.........1116.3.4单表头的树管理型单据............114第七章列表卡片类型单据1177.1列表卡片型单据的类结构..............1177.2列表的数据和单据数据的关系............1187.3制作列表卡片型单据的步骤.............118第八章坚持模型驱动的单据开发方法125用友软件c⃝20054第一章总体介绍UI工厂是一套基于NC-UAP的UI开发框架。它的产生是通过对大量的业务节点的总结,把常见的UI进行分类,分别抽取其中公有的代码,形成不同的UI基类。UI工厂综合运用了单据模板,查询模板,打印模板,实现对这些基本构件的运用的最优化。UI工厂提供了UI相关的常见任务的推荐实现方法,比如通过ButtonManager来统一管理界面按钮的状态,使代码更清晰可读,等等。1.1基本结构经常可以看到这样的节点,UI相关所有的代码都写在一个ClientUI类中,而类的规模通常在2000行左右,这使得日后对这些代码的工作比较困难。而且每个UI有很多相似或者相同的功能,如果所有的代码都写在了一个类中,那么这些相似或者相同的代码就只能通过拷贝的办法来实现代码的“重用”。这样的做法带来的很严重的代码冗余更增加了代码维护的难度和工作量。UI工厂解决上述问题的基本策略就是:细分职责,高度重用。仔细分析客户端的各种功能可以发现,有些功能间有较强的相互关联,而这些功能与其他功能间的关联却是很弱的。对这些有较强关联的功能一般称其具有一定的内聚性(Cohension).UI工厂中把客户端的各种功能根据其内聚性分成若干组。针对每组功能,都抽象出一个Class来负责这部分“职责”。比如有51.1基本结构第一章总体介绍一个Buffer来专门负责缓存多张单据和翻页等相关任务;ButtonManager负责处理创建按钮以及维护按钮的状态。UI�BillUIBuffer�BtnManager�Business�Delegator�IController�Event�Handle�Business�Action�HYQueryDlg�图1.1:UI工厂主要类关系图图1.1描述了UI工厂中为完成客户端的各种功能所涉及的主要的类。图里的名称更多的是在描述一个角色的名称而不是实际的类名。可以把这个图想象成一个公司的组织机构图,公司分成了若干的部门,每个部门各司其职,又相互协调。掌握这些类的功能以及如何对其扩展是理解UI工厂的关键。UI继承ToftPanel,功能节点注册时需注册该类,是一个节点的入口类。在UI工厂的框架中,相当于Mediator的角色。BillUIBuffer前台业务数据的缓冲。ButtonManager按钮管理器,负责按钮的创建和运行期状态控制。BusinessDelegator业务委托类,负责和后台进行交互。除了BusinessAction中的任务,其他所有和后台交互,即对XXXBOClient调用都必须放到该用友软件c⃝20056第一章总体介绍1.2单据模型类中,其他类必须通过这个Delegator与后台交互。1IController界面控制器。不要把它和MVC模式中的“C”之间划上等号。它在这里相当于一个配置文件的作用。在使用具体的模式时都有对应的IController实现,使用者需要根据实际情况补充其中的信息。EventHandler按钮事件处理器。所有对按钮事件的处理都在这个类中实现。BusinessAction业务动作处理类。处理保存、审批等“业务”动作。具体参见1.8HYQueryDlg查询对话框。支持增加常用条件页签。1.2单据模型NC中的多数节点所实现的功能,从技术的角度上看都是针对“单据”的相关操作。这里的“单据”泛指所有能表示为主子表模型的业务数据,比如订单,合同等等。MasterTable�PK�pk_master�pk_customer�pk_deliverdate�DetailTable�PK�pk_detail�FK1�pk_master�pk_invmandoc�number�图1.2:主子表数据模型图1.2就是一个最典型主子表的关系数据库模型(DataModel)。主表的一行记录对应于子表的多行记录。主表和子表之间通过主外键相互关联。1当然这只是一种规范性质的限制。没有任何办法能阻止使用者违反这个规范,就像没有办法能防止有人故意写出有Bug的代码。但是这里强烈地建议严格按照这个规范来执行。用友软件c⃝200571.2单据模型第一章总体介绍对于这样的数据模型NC是利用如图1.3所示的对象模型(ObjectModel)来实现对其的建模。+setParent(CircularlyAccessibleValueObject)�+getParent()�+setChildrenVO(CircularlyAccessibleValueObject[])�+getChildernVO()�AggregatedValueObject�DetailTableVO�MasterTableVO�CircularlyAccessibleValueObject�1:1�1:n�图1.3:主子表对象模型由于主子表模型是NC中最常见的数据组织形式,也是流程平台目前唯一支持的数据模型,所以图1.3所示的模型是UI工厂的缺省配置。当然通过一些开关和参数的设置就可以支持一些变化的形式。最常见的变化形式有以下几种:主子表也就是UI工厂的默认形式。这种情况不需特别的配置。只需实现IControllerBase.getBillVOName()方法,通过一个String数组返回聚合VO、主表VO、子表VO的ClassName即可。多子表有多个子表.这种情况需要继承专门的多子表基类。在ICon-trollerBase.getBillVOName()的方法里依次返回聚合VO、主表VO、子表VO1、子表VO2...子表VOn的ClassName即可。其中聚合VO需要实现IExAggVO接口。单表头没有表体的卡片。实际实现时是把聚合VO的子表当成NULL处理。需要让UI的Controller实现ISingleController接口。并在isSingleDe-tail方法中返回false;用友软件c⃝20058第一章总体介绍1.3BILLUIBUFFER单表体没有表头的卡片,一般用于一些基本档案节点。实际实现时是把聚合VO的主表当成NULL处理。需要让UI的Controller实现ISingleController接口。并在isSingleDetail方法中返回true;可以概括地说,UI工厂中对主子表之外的数据模型的支持在实现上是将其作为主子表模型的一些特例来处理的。明白这一点对于理解UI工厂的整个体系非常重要。下文中我们用单据来指代所有能表示为主子表的数据模型。1.3BillUIBuffer在很多节点中需要对多张单据进行浏览、操作;在执行完一个查询任务后往往返回的结果也是很多张单据;这就要求客户端有个单据的缓存区,用户可以操作其中的任何一个单据而不必去每次都去后台查询相应的数据。BillUIBuffer被设计出来完成这个任务。这是一个“线性”的缓冲,其内部实现是把多个单据保存在一个ArrayList中。有一个名为CurrentRow的指针,其所指的单据就是当前用户所选的单据,如果想通过程序选择特定的单据,只需调用BillUIBuffer.setCurrentRow即可。对于一些特殊的界面比如TreeCard或者ListCard界面,一个“线性”的缓冲无法满足需要。这种界面需要根据用户所选择的树节点或List中的Item来决定卡片界面所显示的数据。对于这种情况UI类增加了一个HashMap来实现一个对照表的功能。首先根据用户所选择的数据查找对照表,得到对应数据在BillUIBuffer中的索引,然后用该索引作为参数调用setCurrentRow方法,来显示正确的数据。BillUIBuffer和UI共同实现了一个Observer模式。前者作为一个Observ-able的子类允许后者对其的”Observing”。当BillUIBuffer的数据发生改变时,比如setCurrentRow被调用,它会通知UI刷新界面同步数据。对单据进行浏览的功能,比如上一页,下一页等就是通过调用setCurrentRow来实现用友软件c⃝200591.4加载界面第一章总体介绍的。BillUIBuffer在UI工厂中的角色不能直接和MVC模式中的Model划等号,其在UI工厂中的角色主要是一个Buffer,而不是一个Model。当对单据进行编辑时,通过BillUIBuffer.getCurrentVO得到VO并不是您在界面上看到的数据,这是因为当进行编辑操作时,界面上的数据和BillUIBuffer实际是“脱钩”了的。只有编辑完,保存后,BillUIBuffer才得到更新。1.4加载界面UI工厂的AbstractBillUI对加载界面做任何的封装。真正的界面的加载工作都是由具体的子类,比如卡片界面来实现的。NC中的UI大都是基于单据模板实现的。要使用单据模板便少不加载模板,设置模板各项参数等“苦差事”。于是BillCardPanelWrapper和BillListPanelWrapper被设计出来专门处理卡片式和列表式的单据模板的加载及设置工作。另外这两个Wrapper提供了一些单据模板自身不够完善的功能,比如按行执行公式等等。详细的内容可以去查看其代码。上述的两个Wrapper是UI工厂的两个重要的基本工具类,但是其也可以被独立于UI工厂使用。只需提供合适的Controller,Wrapper就能完成单据模板的加载和设置。通过调用getBillCardPanel可以得到单据模板类的实例,这个Panel可以被放到其他任何Panel或者Dialog中。参考BillCardUI的代码可以获得更详细的信息。1.5UI状态为了方便控制UI的各个控件的可用(Enable)与不可用(Disable),UI工厂为UI设计了一些状态。整个UI的运行过程就象一个状态机,一开始处于一个初始状态,在一些事件(Event)的触发下跳转到下一个状态。这里用友软件c⃝200510第一章总体介绍1.5UI状态的事件主要指用户的一些操作,比如新增(Add),编辑(Edit),在界面选中了一个审批态的单等等,当然也可以是由程序内部触发的一些动作,比如程序自动触发的刷新(Refresh)。图1.4描述了UI的部分状态变迁。OP_INIT�OP_ADD�OP_EDIT�OP_NOTEDIT�Add�Query�Add�Edit�Cancel�Cancel�图1.4:状态变迁示意图为了对UI控制更精细,把UI的状态分成了3类:UI相关状态包括初始(OPINIT),编辑(OPEDIT),新增(OPADD),非编辑(OPNOTEDIT)等状态。完整的列表可以参考IBillOperate的定义。数据相关状态包括自由(FREE),提交(COMMIT),审批通过(CHECK-PASS),审批未通过(NOPASS)等状态。完整的列表可以参考IBill-Status的定义。扩展状态很些业务可能需要一些特殊的状态,而这些状态在设计UI工厂时没有考虑在内。比如有些合同需要一个终结态,来表示合同终止执行。对于这种情况可以通过自定义的扩展状态来解决。用友软件c⃝2005111.6按钮第一章总体介绍UI上所有控件的可用与不可用都与这些状态挂上钩。比如对于“编辑”按钮,可以分别设置其能被设为“可用”的UI相关状态有哪些,数据相关状态有哪些,扩张状态有哪些。这样在运行时,程序就能自动地根据当前的状态设置按钮的可用与不可用。需要注意的是,这些状态存在优先级的关系,UI相关状态最低,扩展状态最高。1.6按钮UI工厂通过ButtonManager统一管理按钮的创建和状态。具体的业务节点不负责创建各个ButtonManager的实例,而是通过一个ID号来向ButtonManager注册一个按钮,ButtonManager会对所有该节点注册的按钮生成一个实例,业务代码可以使用按钮ID向ButtonManager获取对应按钮的实例。具体的业务节点也不负责控制按钮的可用与不可用,而是配合UI状态(1.5节)来设置按钮的“可用策略”的。对每一个按钮可以设置当UI处于何种状态时该按钮是可用的。当程序运行时ButtonManager自动根据UI的状态变迁来设计各个按钮的可用与不可用。通常,在刷新单据数据时会根据UI相关状态和扩展状态重新自动设置按钮的“可用策略”,如果单据和平台相关,还需要根据数据相关状态设置按钮“可用策略”。确保后者的方法是使单据UI控制类的isExistBillStatus方法返回true。UI工厂预先注册了很多常见的按钮,比如新增、编辑、保存等等。这些按钮的显示名称,状态栏提示信息,热键以及可用策略都是预置好的,一般各个业务节点不需要修改。如前文所述,每一个按钮有一个ID号与之对应。比如新增为(Add),查询为(Query)完整的列表可以参看IBillButton的定义。业务节点根据实际的需要可以指定需要哪些按钮,当程序初始化时ButtonManager会负责把这些按钮创建出来。比如对于基于“卡片界面”(参见第二章)实现的节点可以在ICardController.getCardButtonAry()指定需要的按钮。表1.1中的代码片断表示该业务节点指定了保存、查询等7个按钮。用友软件c⃝200512第一章总体介绍1.6按钮每个Btn的配置信息保存在ButtonVO中,在ButtonObject.getData可以得到整个ButtonVO,对特定按钮的可用策略的修改可以通过得到该按钮一个实例修改ButtonVO的值表1.1:卡片界面指定按钮代码示例publicint[]getCardButtonAry(){returnnewint[]{IBillButton.Add,IBillButton.Query,IBillButton.Save,IBillButton.Edit,IBillButton.Cancel,IBillButton.Del,IBillButton.Action,IMyButton.Go};}1.6.1自定义按钮有时业务节点会需要一些非预置的按钮,如表1.1中的IMyButton.Go就是一个自定义按钮。为了给节点加入一个自定义按钮需要以下几步:•为自定义自定义定义一个ID号。这个ID号为整型(int),数值必须大于100,且不可于其他自定义按钮重复。一般建议每个产品定义一个专门的自定义按钮ID的常量接口。前面例子中的IMyButton就是一个常量接口。•为自定义按钮实现一个ButtonVO类,定义该按钮的相关属性和可用策略。下面是一个自定义按钮的ButtonVO类的例子。从中我们可以用友软件c⃝2005131.6按钮第一章总体介绍看到这个自定义按钮的ID为IYCCAButton.RemoveButton;名称为“移去材料”;状态栏提示为“移去不分摊材料”。当单据的UI相关状态为初始态和非编辑态、数据相关状态为自由态时该按钮为可用。importnc.ui.trade.base.IBillOperate;importnc.vo.trade.button.ButtonVO;publicclassRemoveBtnVO{publicnc.vo.trade.button.ButtonVOgetButtonVO(){ButtonVObtnVo=newButtonVO();btnVo.setBtnNo(IYCCAButton.RemoveButton);btnVo.setBtnName("移去材料");btnVo.setHintStr("移去不分摊材料");btnVo.setOperateStatus(newint[]{IBillOperate.OP_NOTEDIT,IBillOperate.OP_INIT});btnVo.setBusinessStatus(newint[]{IBillStatus.FREE});returnbtnVo;}}•如果自定义按钮为集团私有,必须设置按钮的所属权限。默认的权限是集团和公司共有。设置集团私有权限的方法如下:btnVo.setBtnAttribute(ATTR_JT_Private);•如有必要,注册自定义按钮的快捷键,如下所示。标准按钮的快捷键系统已经注册,默认的控制符为CTRL健。用友软件c⃝200514第一章总体介绍1.6按钮publicnc.vo.trade.button.ButtonVOgetButtonVO(){ButtonVObtnVo=newButtonVO();btnVo.setBtnNo(IYCCAButton.RemoveButton);btnVo.setBtnName("移去材料");btnVo.setHintStr("移去不分摊材料");btnVo.setOperateStatus(newint[]{IBillOperate.OP_NOTEDIT,IBillOperate.OP_INIT});btnVo.setBusinessStatus(newint[]{IBillStatus.FREE});//定义快捷键btnVo.setHotKey("/");btnVo.setDisplayHotKey("(/)");//定义控制键btnVo.SetModifiers(java.awt.Event.CTRL_MASK);returnbtnVo;}•在Controller中通过自定义ID指定需要使用的自定义按钮。参见表1.1•在EventHandler中添加自定义按钮的事件处理逻辑。参见1.7•重载UI类的initPrivateButton方法,按照以下代码的形式,完成自定义按钮的注册.RemoveBtnVOrmvBtn=newRemoveBtnVO();addPrivateButton(rmv.getButtonVO());用友软件c⃝2005151.6按钮第一章总体介绍1.6.2扩展状态在前面的UI状态(1.5节)中我们简要介绍了单据的状态,引入状态的概念是为了控制按钮,从而控制对单据的操作。系统内置了对操作状态和业务状态的定义,但是对于某些业务这还不够,因为它们需要一些特殊的控制。所以,UI工厂增加了扩展状态以定制这些控制。扩展状态的示例如下面代码所示:/***扩展的单据状态。*/publicinterfaceITestExtendStatusextendsnc.vo.trade.pub.IExtendStatus{publicfinalstaticintSTATUS1=1;publicfinalstaticintSTATUS2=2;publicfinalstaticintSTATUS3=3;}publicclassTestBtnVO{/***XXBtnVO构造子注解。*/publicTestBtnVO(){super();}/***获得当前按钮的业务控制。*创建日期:(2004-2-279:33:58)*@returnnc.vo.trade.button.ButtonVO*/publicnc.vo.trade.button.ButtonVOgetButtonVO(){用友软件c⃝200516第一章总体介绍1.6按钮ButtonVObtnVo=newButtonVO();btnVo.setBtnNo(ITestBtnButton.Button2);btnVo.setBtnName("测试按钮");btnVo.setHintStr("处理状态13业务数据");//设置按钮在状态STATUS1和STATUS3下可使用btnVo.setExtendStatus(newint[]{ITestExtendStatus.STATUS1,ITestExtendStatus.STATUS3});btnVo.setOperateStatus(null);btnVo.setBusinessStatus(null);returnbtnVo;}}1.6.3设置子按钮除了自定义按钮,我们还可以为按钮增加子按钮,如下所示:publicnc.vo.trade.button.ButtonVOgetButtonVO(){ButtonVObtnVo=newButtonVO();btnVo.setBtnNo(ITestBtnButton.Button1);btnVo.setBtnName("单据处理");btnVo.setHintStr("处理业务数据");btnVo.setOperateStatus(newint[]{IBillOperate.OP_NOTEDIT});btnVo.setBusinessStatus(newint[]{ITestExtendStatus.ALL});//为Button1增加两个子按钮:Button2和Button3用友软件c⃝2005171.6按钮第一章总体介绍btnVo.setChildAry(newint[]{ITestBtnButton.Button2,ITestBtnButton.Button3});returnbtnVo;}需要注意的是,系统内置了一种特殊的按钮,它具有多个子按钮,每个子按钮对应着一种单据模板,因此可以利用它实现单据的模板切换功能。这个特殊的按钮ID为IBillButton.NodeKey。利用UI工厂实现模板切换功能很简单,请按照下面的步骤:1.在单据UI控制类的单据按钮定义中加入IBillButton.NodeKey,比如:publicint[]getCardButtonAry(){returnnewint[]{//模板切换按钮IBillButton.NodeKey,IBillButton.Add,IBillButton.Save,IBillButton.Cancel,IBillButton.Edit,IBillButton.Query,IBillButton.File,IBillButton.Print,IBillButton.Refresh};}2.定义模板切换按钮的多个子按钮。这里子按钮的定义需实现IChild-MenuController接口,它的接口定义如下所示:用友软件c⃝200518第一章总体介绍1.6按钮publicinterfaceIChildMenuController{/***显示主键字段。*创建日期:(2004-2-119:05:16)*@returnjava.lang.String*/StringgetIDFieldName();/***获得菜单节点数据。*创建日期:(2004-3-119:58:15)*/SuperVO[]getMenuData();/***显示字段名称。*创建日期:(2004-2-119:00:37)*@returnjava.lang.String*/publicStringgetShowFieldName();}其实现类代码示例如下所示:publicclassTestChildMenuCtlimplementsnc.ui.trade.base.IChildMenuController{publicTestChildMenuCtl(){super();}用友软件c⃝2005191.6按钮第一章总体介绍/***显示主键字段。*/publicStringgetIDFieldName(){return"pk_testhead";}/***获得菜单节点数据。*/publicnc.vo.pub.SuperVO[]getMenuData(){//为模板切换按钮定义了3个子按钮TestheadVO[]child=newTestheadVO[3];TestheadVOa=newTestheadVO();//设置的主键为系统模板定义中nodekey域的值a.setPrimaryKey("00010000000000000001");a.setBillno("图书");child[0]=a;a=newTestheadVO();a.setPrimaryKey("00010000000000000002");a.setBillno("出版");child[1]=a;a=newTestheadVO();a.setPrimaryKey("00010000000000000003");a.setBillno("CD");child[2]=a;returnchild;用友软件c⃝200520第一章总体介绍1.7事件处理}/***显示字段名称。*创建日期:(2004-2-119:00:37)*@returnjava.lang.String*/publicStringgetShowFieldName(){return"billno";}}3.使单据UI控制类重载createChildMenuController方法,如下所示:protectedIChildMenuControllercreateChildMenuController(){returnnewTestChildMenuCtl();}1.7事件处理客户端的业务相关逻辑大多是根据用户的一些操作事件作出相应的响应。UI工厂中的事件可以分成两类:按钮事件和编辑事件。1.7.1按钮事件按钮事件,顾名思义就是指用户对界面上按钮的点击事件。根据NC的UI开发框架,所有的按钮事件响应集中在UI类的基类Toft-用友软件c⃝2005211.7事件处理第一章总体介绍Panel.OnButtonClick方法中处理。UI工厂根据“细分职责”的原则把这部分职责委托给了一个EventHandler的类来处理。如前文所述UI工厂预置了很多业务中常见的按钮,同时也为这些按钮预置了相应的业务处理逻辑。这部分代码主要是在BillEventHandler中,各个不同的具体的子类一般都根据自身的特点对一个方法进行重载。因此业务节点不能直接继承BillEventHandler而是继承相应的子类。比如对于卡片界面,其事件处理类为CardEventHandler。根据业务的需要有时需要重写BillEventHandler中的方法。这些方法的命名原则是在其对应的按钮ID的前面加上“onBo”的前缀。比如“新增(ADD)”按钮的对应处理方法为onBoAdd。掌握了这个规律就很少碰到找不到按钮事件的处理代码的问题了。不过也少部分例外,当一个单据需要能够参照其他单据制单时新增按钮下面会有若干个子按钮,对这些按钮事件的处理代码在onBoBusiTypeAdd中。“合适的就不理它,否则重载它”,但是重载也有它的游戏规则。•扩展而不是改变基类的行为。基类提供的每个方法都有其业务的含义,在重载一个方法的时候不要改变它的业务含义。比如把重载“编辑”按钮的事件处理方法,已让它实现保存的功能是强烈不推荐的。这除了会引起后来的维护者的困惑之外还可能带了隐藏的Bug。•不要“Hack”基类的代码。尽量使用推荐的方法来完成业务逻辑。这个原则在实际工作时可能有点难以把握。这里给出建议是当你发现你的方法过于“技巧性”时,应该考虑一下这个原则。•在不违反上一条原则的情况下尽量利用基类的代码来实现需要的逻辑。基类的代码经过较多的测试,多利用这些代码会使你的生活变得轻松些。用友软件c⃝200522第一章总体介绍1.7事件处理自定义按钮事件处理当然,对于自定义按钮是没有默认的处理逻辑的。所有的自定义按钮的事件的处理最终会到BillEventHandler.onBoElse(intintBtn)中。业务节点可以扩展这个方法对加上不同的自定义按钮处理逻辑。参见表1.2。表1.2:自定义按钮事件处理代码示例protectedvoidonBoElse(intintBtn)throwsException{longbTime=System.currentTimeMillis();switch(intBtn){caseIYCCAButton.ApportionButton:apportion();break;caseIYCCAButton.CancelApportionButton:cancelApportion();break;caseIYCCAButton.ComputeReportDataButton:computeReportData();break;}}1.7.2编辑事件“当这个CheckBox被选中时,那么表体的金额列就不允许用户修改,而是由程序自动计算金额”。“当‘数据来源列’选择了’总帐’时,取数公式列换成总帐的取数参照”。这种需要在用户进行编辑操作时进行一些业务相关的控制是业务节点常见的需求。单据模板提供了两个监听接口BillEditListener,BillEditListener2,使得业务节点可以在用户的编辑动作前、后分别插入相关的业务代码。UI工厂中的各个的基类都已实现了这两个接口。业务节点用友软件c⃝2005231.8业务动作处理第一章总体介绍只需重载父类的相关方法,加上相关的业务逻辑即可。•afterEdit–当单据模板的表头或者表体的某个项目被编辑后会触发该方法。前面的两个例子都可以通过重载该方法来实现。–重载频度:高。•bodyRowChange–当选中卡片模板的表体,列表模板的表头或表体的不同的行时会触发该方法。BillManageUI中在该方法中实现列表界面的表头选中一行数据后,显示其子表数据。–重载频度:低。•beforEdit–当单据模板的表头或者表体的某个项目被编辑前会触发该方法。–重载频度:低。1.8业务动作处理这里的业务动作指的是IBusinessController中所定义的业务操作。包括保存,删除,提交,审批,其他,等等。这些动作有一个共同的特点,那就是它们都是以整个“单据”为操作对象。其他不符合这个条件的不应算作这里的业务动作。比如类似“在新增单据时从后台读取一些默认的数据”的操作不应算作这类业务动作,这种任务应该通过BusinessDelegator进行。22为什么这么划分?这种设计主要是为了方便实现业务逻辑的自动测试。“以整个单据为操作对象”的业务动作一般就是实现业务逻辑的动作,把这些动作集中到一个接口中有利于将来通过Decorator模式实现一个可以把实际的业务动作“录制”下来的业务动作Decorator,利用一个脚本回放这个过程,加上一下数据校验,就能实现业务逻辑的自动测试。这些目前还未实现完毕。现在所要知道的就是这些动作是“特殊”的。用友软件c⃝200524第一章总体介绍1.9PUTITALLTOGETHER目前UI工厂中提供了IBusinessController的两个实现类:BusinessAction和BDBusinessAction。•BusinessAction。这个类是为需要利用流程平台的单据提供的。其中封装了对流程平台的调用。•BDBusinessAction。这个类是为无需利用流程平台的单据提供的。一般把这类功能节点称为“基本档案型”。UI的Controller中有一个getBusinessActionType方法。当实现一个基本档案型的节点时,那么请在这个类的方法中返回IBusinessActionType.BD,否则返回IBusinessActionType.PLATFORM。UI工厂将根据该方法的返回值设置好不同的IBusinessController实现类。如果现有的业务方法类无法满足需要,可以实现IBusinessController接口,创建一个新的业务处理类。并重载EventHandler中的createBusinessAction方法,在其中返回新的业务处理类的实例。1.9Putitalltogether前面已经把UI工厂的概况做了一个描述,下面做一个总结性的介绍,阐述实现一个节点一般性的步骤是如何的。•Step0业务分析,数据库设计。选择节点UI适用的模式,比如卡片界面或者管理界面等。•Step1平台信息注册。登录NC,利用二次开发工具注册UI的相关信息。根据单据是否使用流程平台注册的信息有所区别。–使用平台。需要注册的信息包括:单据类型、VO对照、单据动作,单据动作组,单据动作执行脚本、功能节点注册。如果需要单据间的参照关系,还要注册数据交换,单据参照信息表。用友软件c⃝2005251.9PUTITALLTOGETHER第一章总体介绍–不使用平台。需要注册的信息包括:单据类型、功能节点注册。UI工厂有一个附属的工具RegTools.xls,可以不用登录NC,通过填写一个Excel文件,生成上面的注册信息所对应的脚本文件,(不包括单据动作执行脚本)。单据类型的注册时需要提供一个“审批流检查类”,这实际时审批流执行审批等动作时需回调业务代码的一个接口。目前提供了两种做法。–如果业务节点的数据库字段命名与UI工厂缺省的情况相同,3则审批流检查类统一注册为“nc.bs.trade.business.HYSuperDMO”即可。–如果字段命名和规范不同,则UI必须提供一个IBillField的实现,比如FooBillField。后者必须实现Singleton模式。另外需提供一个nc.bs.trade.checkflow.AbstractPFCheckFlow的子类,比如FooCheckFlow,实现方法createBillField,在其中返回上述的FooBillField的实例。如果一个产品的遵循了统一的字段命名原则,则不必每个节点都分别提供这个两个类,所有节点使用统一的实现即可。•Step2制作单据模板,制作查询模板。•编写代码。一定需要的实现两个类UI和Controller。其他一些类是否实现取决于业务是否是一般性的,如果基类功能不能满足要求就可以重载之。一般EvnetHandler和Delegator是需要重载的,BusinessAction很少被重载。•Step5Run&Debug。3单据状态vbillstatus,审批人vapproveid,审批日期dapprovedate,审核批语vapprovenote用友软件c⃝200526第二章如何制做卡片型单据卡片型单据是UI工厂中最基本的单据,与其他类型的单据比较起来,它的界面形式以及对VO数据的组织相对简单。卡片型单据的应用场景是:单个聚合VO的数据在界面上展示这个聚合VO,可以由标准的表头数据+表体数据组成,也可以是单表头或单表体。2.1卡片型单据的类结构BillCardUI�AbstractBillUI�ICardController�BillEventHandler�CardEventHandler�SampleCardEventHandler�SampleCardUI�SampleCardController�卡片型单据统一的界面基类是BillCardUI,统一的事件处理基类是CardEventHandler,统一的界面控制接口为ICardController。在上面的272.2卡片型单据界面示例第二章如何制做卡片型单据图中,可以看到具体的单据界面类SampleCardUI继承了BillCardUI,具体的单据界面控制类SampleCardController实现了ICardController接口,具体的单据按钮事件处理器SampleCardEventHandler继承了CardEven-tHandler。2.2卡片型单据界面示例1.单表体的卡片型单据界面2.表头+表体的卡片型单据界面用友软件c⃝200528第二章如何制做卡片型单据2.3开发卡片型单据的步骤3.多子表的卡片型单据界面2.3开发卡片型单据的步骤这里将介绍开发卡片单据的一般步骤,主要侧重于讲解通用性较强的问题。用友软件c⃝2005292.3开发卡片型单据的步骤第二章如何制做卡片型单据2.3.1前期准备与传统的单据开发方式一样,基于UAP平台,需要做以下准备:•以数据库表结构为蓝本,利用工具自动生成单据VO类。•在NC二次开发工具/单据类型管理中对单据的相关配套类进行注册。比如单据UI类,前台校验类等。•在NC单据模板设置工具中对该单据的显示模板进行设置。2.3.2快速搭建类框架前面在卡片型单据类结构中介绍过,只需要从卡片单据的相关基类继承,即可生成初步的类框架。由于UI工厂在基类对许多功能进行了缺省实现,因此只需要配置几个简单的信息(通过方法的重载)即可生成最简单的界面。2.3.3开发出第一个卡片单据有了上面生成的类框架,马上可以开发出最基本的卡片单据。1.完善SampleCardController界面控制类是必需的。因为它保存着单据的VO信息,以及单据的主子表主键信息。在开发第一个单据时,我们做最简单的实现。•重载getBillType()方法返回在平台里给此单据注册的单据类型。•重载getBillVoName()方法用友软件c⃝200530第二章如何制做卡片型单据2.3开发卡片型单据的步骤返回单据的VO信息。它的返回值是一个字符串数组,其中,数组的第一个元素是聚合VO的类名,第二个元素是主表VO的类名,剩下的元素是子表VO的类名(可能是多子表)。以下面的代码为例:publicString[]getBillVOName(){returnnewString[]{SampleVO.class.getName(),SampleHeadVO.class.getName(),SampleBodyVO.class.getName()}}其中,主表VO为SampleHeadVO,子表VO为SampleBodyVO,装载子表和主表VO的聚合VO是SampleVO。•重载getPkField()方法返回主表的主键,也就是SampleHeadVO的主键。•重载getChildPkField()方法返回子表的主键,也就是SampleBodyVO的主键。2.完善SampleCardEventHandlerEventHandler类用来响应按钮事件,所有按钮事件在基类都有缺省实现,因为此时我们不需要加入任何特殊的按钮控制方法,于是可以暂时不修改此类。实际上,在这种情况下,完全可以不创建事件处理类。3.完善SampleCardUI用友软件c⃝2005312.3开发卡片型单据的步骤第二章如何制做卡片型单据•重载createController()方法该方法得到界面控制类。生成一个SampleCardController实例,并返回。通过这几步,实际已经利用UI工厂创建了最简单的单据。结果如下:这是目前单据的雏形,下面我们将一步步为它添加更多的功能。2.3.4完善功能一:数据加载数据加载分为几个部分:1.初始化单据模板的数据,比如对ComboBOX数据的初始化,对参照数据的初始化。实现这个功能,需重载SampleCardUI的initSelfData方法,并在方法内完成对单据模...