BOS套打二次开发指南阅读指引:本文目的在于指导EAS二次开发人员,正确书写套打功能的集成代码。有关原理说明,也适合实施人员阅读。理想状态下,套打应该是不需要二次开发再写代码的;但由于历史原因、或特殊需求,可能需要二次开发。1相关概念1.1打印1.1.1套打EAS中的套打,并不仅仅是指在印刷好的票据、单据上打印内容的。更为全面而准确的定义,应该是指:自定义格式的票据、单据、报表等数据的打印。其中,自定义格式是指用户可以自己制作模板。由于名称的沿袭,一直采用套打一词。1.1.2序时簿打印ListUI中表格控件的打印,通常也称为序时簿打印。它是将表格中的数据直接打印输出。有些业务场景,菜单中会同时出现打印与套打,请注意区别。序时簿打印不在本文讨论范围内,有关内容可参考框架及KDTable控件的说明。1.2套打管理中的文件现将EAS系统平台——套打——套打管理中出现各种文件介绍如下。1.2.1KDF(kdrs-form)KDF系列的Form格式,是传统的套打模板格式,对应*.kdrs-form的模板文件。不推荐使用(模板由新的格式——R1代替,老用户已存在的KDF模板可继续执行),以后将不再提供技术支持。1.2.2ReportOne(r1-print)ReportOne系列的打印格式,是新的(用于取代KDF格式的)套打模板格式,对应*.r1-print的模板文件。以下或简称为R1。另有文档介绍模板规则。1.2.3套打元数据*.bos-query。业务集成时定义了一些字段名称,供模板设计时绑定用,和BOS的元数据Query没有必然的关系(视集成代码,可能会对应一个Query,也可能完全没有关系)。如果使用R1模板,允许没有套打元数据,因为在模板设计器中可以直接引用BOS的Query。(请注意,直接引用Query的情况,只是模板中有一个引用信息,未必执行时就查询该Query,仍然要视集成代码而定。参考集成开发的说明。)1.2.4套打动态查询*.note-dynquery。是在EAS执行期在套打管理中提供的对BOSQuery的增量修改,可作为R1套打模板的元数据。(与直接引用Query相同,执行时依赖于集成代码是否做了实现。)1.3代码相关概念1.3.1KDNoteHelpercom.kingdee.bos.ctrl.report.forapp.kdnote.client.KDNoteHelper类提供了统一的套打调用API。1.3.2数据提供者套打控件只识别取数接口(现在也提供查询Query的缺省实现DefaultNoteDataProvider,且推荐使用),具体的取数实现都是各业务集成时各自实现。对于实现取数接口的、给套打执行时提供数据的类,称之为DataProvider——数据提供者。2集成开发2.1套打调用业务开发中,通常界面上会有[打印]/[打印预览]按钮,在框架的EditUI类中,会有actionPrint_actionPerformed或actionPrintPreview_actionPerformed方法与之对应,套打的调用代码写在这样的方法或类似的方法(ListUI可能不同)中。请注意,在ListUI中,我们还提供了对表格控件的打印,通常称之为序时簿打印,请注意区别。以下接口都是KDNoteHelper类的。2.1.1打印API:publicvoidprint(StringtemplateType,ObjectdataProvider,Componentowner,booleanisShowPrinterDialog)参数说明:templateType业务对应的路径,即套打管理中模板所在文件夹。(注:在框架中,由getTDFileName()方法指定。)dataProvider数据提供者对象,参考数据提供。owner父窗口或其上的一个控件,用以实现弹出窗口的模态。[isShowPrinterDialog]可省略。打印前是否弹出选择打印机的对话框。标准业务通常省略(缺省为true)。参考直接打印。使用示例:KDNoteHelperhelper=newKDNoteHelper();helper.print("/FI/GL/Voucher",dataProvider,ctrl);2.1.2预览API:publicvoidprintPreview(StringtemplateType,ObjectdataProvider,Componentowner)参数说明:templateType业务对应的路径,即套打管理中模板所在文件夹。(注:在框架中,由getTDFileName()方法指定。)dataProvider数据提供者对象,参考数据提供。owner父窗口或其上的一个控件,用以实现弹出窗口的模态。使用示例:KDNoteHelperhelper=newKDNoteHelper();helper.printPreview("/FI/GL/Voucher",dataProvider,ctrl);2.1.3直接打印在某些定制的业务中,用户希望录入完一个单据后点一下打印按钮就将内容直接输出到打印机。此处,“直接”指不需要弹出选择模板和打印机的对话框作人机交互。其中,不用选择打印机是靠打印API中,第4个参数isShowPrinterDialog为false指定的,使用时自动启用系统默认的打印机。不用选模板,依赖于用户第一次使用弹出选择模板的窗口时,将目标模板记为“缺省模板”来实现。图“直接打印”依赖于将模板记为缺省模板2.2数据提供者2.2.1套打取数接口packagecom.kingdee.bos.ctrl.reportone.r1.print.data;importcom.kingdee.jdbc.rowset.IRowSet;/***@sinceEAS5.4*/publicabstractclassAbstractPrintDataProvider{publicabstractIRowSetgetData(R1PrintDataSourcedataSource)throwsException;}该抽象类的实现,称为数据提供者。套打模板中一个“数据源”,在执行过程中,系统会通过此数据提供者找集成环境要数据。如果模板中存在多个数据源,执行时也都是通过这个唯一的接口取数。实现代码中通过dataSource.getId()识别不同数据源。换句话说,数据源名称是集成环境预先定义好的,模板中必须按此命名。当然,如果模板中直接引用Query,而取数实现又对查Query做了支持(例如使用了DefaultNoteDataProvider),则名称无关紧要。通常多数据源存在“主-从”结构,即有依赖的情况,主数据源被依赖的字段值发生变化,从数据源会被要求重新取数。所以,即使是模板设计时逻辑上的一个“数据源”,也可能是多次由getData取得RowSet。此时,从数据源的取数是必须通过dataSource.getAssociateParameter().getValue()取得所依赖的值作过滤的。getData方法的参数,描述了模板中的数据源信息。publicclassR1PrintDataSource{/**数据源名称*/publicStringgetId()/**取得表示数据源依赖于其它数据源的参数*/publicR1PrintDataParametergetAssociateParameter()/**取得指定名称的参数*/publicR1PrintDataParametergetParam(Stringid)/**引用BOSQuery的信息,MetaDataPK的fullName*/publicStringgetReference()/**取得所有排序字段(目前只是分组字段)*/publicR1PrintDataSortItem[]getSortFields()/**创建BOS查Query所需的EntityViewInfo,*它包括了动态查询、模板中指定的排序等信息。*注意它不包含参数信息(如getAssociateParameter)。*/publicEntityViewInfocreateWrappedBosRuntimeInfo()}2.2.2查Query的缺省实现(推荐使用)com.kingdee.bos.ctrl.report.forapp.kdnote.client.DefaultNoteDataProvider类是抽象类AbstractPrintDataProvider的实现,提供了查Query的功能。例1:kdNoteHelper.printPreview(path,newDefaultNoteDataProvider(dataIds),ctrl);这是最简单的使用方法,创建实例后直接使用。其中,dataIds是单据的”id”字段的值,java.util.List或java.util.Set。它可以是EditUI一个单据的id,也可以是ListUI选中的一批单据的id。此种写法要求模板中一定要直接引用Query。如果是多数据源的情况,依赖关系中参数名称一定要当前数据源作为“外键”的字段。例2:为了兼容模板中引用套打元数据(无Query信息)的方式,通常应该指定缺省的Query。DefaultNoteDataProviderdataProvider=newDefaultNoteDataProvider(dataIds);dataProvider.setMetaDataPK("com.kingdee….XxxQuery");kdNoteHelper.printPreview(path,dataProvider,ctrl);例3:多数据源的情况,对每个数据源都指定缺省的Query,且为从数据源的依赖关系指定作为“外键”的字段(则模板中数据源做依赖关系的参数名称可以随意写)。此例中,假设从数据源名称为"subData1",字段为"parentId",下同。dataProvider.addMetaDataPK("subData1","com….XxxQuery1");dataProvider.addForeignKey("subData1","parentId");例4:(自定义过滤条件)dataProvider.addCustomFilterMaker("subData1",newDefaultNoteDataProvider.ICustomFilterMaker(){publicvoidcustomize(FilterInfofilter,R1PrintDataParameter[]params){filter.getFilterItems().add(newFilterItemInfo("status",MultiApproveStatusEnum.SUBMIT_VALUE)));}});ICustomFilterMaker是DefaultNoteDataProvider的内部接口,允许在查Query的时候通过代码指定固定的过滤条件。其实在DefaultNoteDataProvider内部,主数据源也已经包含了如下的过滤条件:filter.getFilterItems().add(newFilterItemInfo("id",dataIds,CompareType.INCLUDE));所以在上面出现的例子中,可以在构造函数中传dataIds。如果主数据源不是要过滤"id"字段,也可以通过setCustomFilterMaker接口修改。例5:(代理)可以将dataProvider作为代理,而将真实的取数任务转发给另外一个实现。DefaultNoteDataProviderdataProvider=newDefaultNoteDataProvider(dataIds);AbstractPrintDataProvideranotherDataProvider=newMyDataProvider();dataProvider.addProxyTarget("subData1",anotherDataProvider);例6:(性能优化)在具有主从关系的多数据源中,主数据源被依赖字段值的变化(很可能是每一行)都会导致每个从数据源取一次数。如果每次都是通过远程执行一次查询,在广域网的环境中,多次的RPC可能成为性能的瓶颈。DefaultNoteDataProvider提供了批量查询的功能,可以通过开关开启。请注意,如果使用了代理,则批量不能生效。dataProvider.setBatchSupported(true);dataProvider.setNumberOfEachBatch(100);2.2.3取数的自定义实现有些时候,查Query不能满足业务场景的取数需求,那么可以写自已的数据提供者。publicclassMyDataProviderextendsAbstractPrintDataProvider{publicIRowSetgetData(R1PrintDataSourcedataSource)throwsException{if("主数据源ID".equals(dataSource.getId())){//可能会以选中数据的ID做过滤条件查数据//构造RowSetreturnrowset;}elseif("从数据源ID".equals(dataSource.getId())){R1PrintDataParameterparm=dataSource.getAssociateParameter();StringparmValue=null;//参数值if(parm!=null){parmValue=parm.getValue().toString();}//以参数值为过滤条件取数据,构造RowSet……returnrowset;}//其它分支}}自行构造RowSet的参考代码:DynamicRowSetdrs=newDynamicRowSet(3);//3列drs.setColInfo(1,"field1","field1",Types.VARCHAR,null);drs.setColInfo(2,"field2","field2",Types.DECIMAL,null);drs.setColInfo(3,"field3","field3",Types.BINARY,null);for(inti=1;i<=10;i++)//10行{drs.moveToInsertRow();drs.updateString("field1","第"+i+"行");drs.updateBigDecimal("field2",newBigDecimal(i));drs.updateBytes("field3",newbyte[0]);//可支持图片drs.insertRow();}drs.beforeFirst();2.3其它常用接口2.3.1打印次数控制publicintgetMaxPrintTimes(StringtemplateType)throwsKDRSException在套打管理中,可以对一个业务单元(文件夹)设置数据的最多打印次数。此方法用于获取该设置值。publicbooleanisPrintTimesControllable(StringtemplateType)如果在一个业务路径上设置了打印次数控制,该静态方法将返回true。publicintgetCopies()取得打印设置中,一次打印的份数。publicvoidsetCopies(inttimes)设置一次打印的份数。publicvoidaddKDNoteActionListener(IPrintActionListenerlistener)添加打印事件,可获得打印前处理事情的机会。publicvoiddiposePrint()取消打印通常的实现逻辑:数据的实际打印次数由业务开发自行管理。每次打印前,判断已打份数是否达到最多打印次数(getMaxPrintTimes),达到了不能打,取消打印(diposePrint)。再判断已打份数加上将打份数(getCopies)是否超过最多打印次数,超过则调整当前打印份数(setCopies)。记录已打次数。以上逻辑都是在IPrintActionListener的beforePrint事件中完成。注意,beforePrint发生在点了打印按钮(可能是业务界面上的打印,也可能是预览后在预览界面中点打印)后数据传送给打印机之前,打印时是与外部设备(打印机)通讯,由于技术上的原因,目前不存在“打印后”或“打印顺利完成”事件。2.3.2组织过滤有些客户,不同的组织使用不同的打印模板。于是在同一业务中,可能存在数十个甚至更多的模板,使用时很不方便。系统提供了选择模板时可以组织过滤的功能。模板设计者在套打管理界面中,可以将模板“绑定当前组织”。使用者在选择模板时,就可以过滤出当前组织对应的模板。API:publicvoidsetOrgFilterMode(booleanisSelected,booleanisEnabled)控制选择模板时出现的组织过滤勾选框状态。参数说明:isSelected组织过滤勾选框是否勾上。缺省为false;自动启用用户的上次操作状态。可置为true;每次进入都勾上,用户的上次操作状态不生效。isEnabled缺省为true。允许置为false,让控件禁灰。使用示例:KDNoteHelperhelper=newKDNoteHelper();helper.setOrgFilterMode(true,true);helper.print("/FI/GL/Voucher",dataProvider,ctrl);图组织过滤3模板发布二次开发基本上不需要关心模板发布的问题。如果是开发时制作的模板,保存在开发时连接的数据库中,做完后导出、导入到客户的真实场景中即可。可能需要适当做些备份,因为套打管理中对模板的删除功能是不能恢复的。由研发中心开发人员制作的出厂模板(也称标准模板),以文件的形式发布在服务端的deploy\ctrlhome\server\storage目录下。初始数据库是不带模板的。服务器启动后,套打第一次被连接,系统自动将所有标准模板导入到数据库中。视环境,此时可能会有几十秒钟的等待,通常被实施人员承受。然后系统自动在deploy\ctrlhome\server\storage\eas\kdnote下创建一个upgrade.xml的文件,用以标识经过了“第一次”。此后,所有模板的存储、使用,都是通过数据库。而模板的补丁依然是以标准模板的方式发布,用户(或实施人员)需要在套打管理中,通过“从标准模板导入”功能进行导入。这样看似不够自动化的方式最大限度保证了用户自定义模板(包括对标准模板的修改)的安全,不会因为升级、打补丁等原因使其丢失。注意到前面所述第一次连接的处理方式,有二种特殊情况:一、研发经常做的,一个服务器连多个数据库。后续增加的初始库,不能自动得到模板。可在套打管理中手工做“从标准模板导入”解决。二、重新安装服务器使标识文件丢失,或版本升级,会增量式(只处理新增资源)地自动导入所有新增模板,此时数据库中已存在的模板是安全的。对于以前发布的现在修改内容的模板,须手工做“从标准模板导入”才能覆盖。修订记录:作者日期备注Along2009-3-3创建