第三方推广:“组头合并格设置不合理”错误的分析

润乾报表中,行属性的类型可以设为”分组表头”,分组表头每页都重复,但是分栏的时候不重复,常用于固定表头。关于组头的使用详情,请参考润乾4.5的用户手册4.4.2节。

但是,表头中往往包含跨行的合并格。跨行的合并格作为表头时,对分组表头的使用不了解的客户认为应该将合并格所在行的”组头级别”设置相同。此时,就会报”组头合并格设置不合理”的错误,如下图所示。

在润乾中,单级分组表头可以包含多行,但是每一行的组头级别必须递增,否则就会报错。在上图中,行号为1的行和行号为2的行中存在合并格,但是这两行的组头级别相同,都为1。当发布的时候,就会出现”组头合并格设置不合理”的错误。

通过修改组头级别就可以修正这个错误。组头级别的设置如下图所示:

要解决第一幅图中的错误,只需要将第一行的”组头级别”设为1,第二行的”组头级别”设为2。修改后发布,在浏览中如下展示如下图所示:

通过api将Excel转化成raq模板

背景说明

Excle文件中的sheet转换成raq模板,在设计器里转换,是非常简单的。下面介绍如何用api代码转换。

应用举例

新建一个类文件,在类文件中写如下代码 :
package com.runqian.test;
import com.runqian.report4.ide.ExcelImporter;
import com.runqian.report4.usermodel.IReport;
import com.runqian.report4.util.ReportUtils;
public class ExcelAsRaq {
public static void main(String[] args) {
String excelFile = “F:/tomcat/Tomcat 5.5/webapps/second/reportFiles/yuangong.xls”;//excel文件的路径
ExcelAsRaq dexcel = new ExcelAsRaq();
try {
IReport iReport = dexcel.excelToReport(excelFile, 0);//excel文件转换,取某个sheet转换成的raq模板,从0开始
ReportUtils.write(”F:/tomcat/Tomcat 5.5/webapps/second/reportFiles/yuangong.raq”,iReport);//保存为raq模板的路径
} catch (Exception ex) {
ex.printStackTrace();
}
}
//将excle文件转换为raq模板,返回的是一个IReport对象
private IReport excelToReport(String excelFile, int sheetNum) throws Exception {
ExcelImporter ei = new ExcelImporter(excelFile);
return ei.getReport(sheetNum);
}
}

这样,在相应路径下就有raq模版了。

分页web报表如何统计当前页与其前几页的信息

Web报表分页显示是最常见的展示方式之一,博计报表可以根据不同的条件来设置报表的分页。然而报表分页也遇到许多新的需求,如:如何只对当前页某列数据进行统计、如何对当前页及其前几页某列数据进行统计以及如何实现对所有页某列数据的统计。

对所有页某列数据统计可以直接使用数据集函数,如:ds1.sum(某字段名)

而对于要求实现当前页及其前几页某列数据统计,博计报表没有提供专门的函数实现,但是可以使用变通的方式,使用手动分组强制分页的方式实现该需求。 这里的手动分组是指一组数据内让其显示固定条数的数据(如10条),用数据总条数/10就是分组的个数(如果总条数%10!=0,需要加1),然后在每组 后面设置强制分页,这样就可实现每页固定显示10行数据的效果。

本文介绍博计报表实现当前页及其前几页某列数据统计的报表设计过程,以例子形式说明。

第一步:

打开设计器,新建一张空白的报表,然后建一个内建数据集ds1,数据集很简单,只包含一个字段,并且也是对该字段进行统计,如下图:

1.png

其中,ID为数据集的字段名,数据类型为字符串,总共增加40条数据。

第二步:

按每页展现10行数据,根据查询数据的总记录条数计算出总共分几页显示,本例中在A1单元格存放计算出的分页结果,表达式为:

=if(ds1.count()%10==0,int(ds1.count()/10),int(ds1.count()/10)+1)

第三步:

根据上步中计算出的分页总页数,手动设置分组,本例中表达式写在A2单元格,为:=list (1 to A1)。这一步的作用就是用分组模拟出分页。

第四步:

按每页显示10条数据,分配查询每页的数据。

本例在B2单元格设置表达式为=list(1 to 10),这里实际作用相当于按行分页,每页10行数据。C3中表达式为=(A2-1)*10+B2,作用为生成所有数据的连续序列号。D4单元格为根据序列号生成的查询字段(ID)表达式。

第五步:

计算数据

计算数据分当前页计算及当前页与前几页数据和。当前页数统计直接使用=sum(D2{}),而当前页与前几页的数据统计则需要用到层次坐标,这里设置表达式为=D3+D4[-1]。

第六步:

设置模拟分页列A列与设置模拟每页显示10条数据列B列为隐藏列。

第七步:

设置第4行为强制行后分页(如下图),实现分页效果。

2.png

报表设计完成后的效果图如下:

3.png

报表发布后,分页效果图如下(第一页):

4.png

然后看一下第二页,检验计算结果(如下图):

5.png

到此可以看到数据统计结果是正确的,实现了当前页与前几页数据求和的要求。

读入导出rat文件

在使用快逸报表的过程中,在设计器中点击预览按钮,或者点击F9按钮,就可以浏览已经设计出来的报表了。既可以检查设计的样式,为进一步修 改报表做准备,也可以对查询出来的数据进行检查,看看是否出现错误。在这时点击保存,会发现保存为一个与raq文件同名的.rat文件。如果浏览的这张报表上没有错误信息,我们也可以把它当做预览结果,浏览数据。由于导出功能可以保存当时的数据信息,就可以对数据实时的情况进行保存,以便日后查看。这种保 存方法既提高了效率,又减少了对内存的占用,所以有些客户会选择这种方式来对数据进行浏览和实时情况的保存。

下面就介绍一下用代码实现保存和读取rat文件的方法。

首先需要引入相关的文件:

<%@ page import=”java.io.*”%>
<%@ page import=”com.runqian.report4.usermodel.*”%>
<%@ page import=”com.runqian.report4.model.*”%>
<%@ page import=”com.runqian.report4.util.*”%>
<%@ page import=”com.runqian.report4.usermodel.graph.*”%>
第一步,读取报表模版
FileInputStream in = new FileInputStream(”C:\\test.raq”); //根据文件名新建文件输入流
ObjectInputStream ois = new ObjectInputStream(in); //新建对象输入流
第二步,计算报表
ReportDefine rd = new ReportDefine2(1, 1);
Context cxt = new Context();
Engine engine = new Engine(rd, cxt); //构造报表引擎
IReport iReport = engine.calc();
第三步,将计算好的ireport对象导入到rat文件,
OutputStream out1 = new FileOutputStream( “C:\\test.rat” );
ReportUtils.write(out1,iReport);
out1.close();

这样就会发现在c盘根目录下有test.rat这个文件。

下面说一下读入的方法:

FileInputStream in = new FileInputStream(”C:\\test.rat”); //根据文件名新建文件输入流
ObjectInputStream ois = new ObjectInputStream(in); //新建对象输入流
IReport r = (IReport) ois.readObject(); //强制转换为IReport实体对象
in.close(); //关闭文件输入流
将读入的文件输出为 html,
String htmlText = ReportUtils.toHTMLString(r,”report2″,request); //把当前页对象输出html语法
out.println(htmlText);
这样就能在页面上浏览效果了。

通过以上方法可以完成导出为rat文件,客户可以根据自己的需求,设置定时器,定时将报表预览的结果导出,既可以作为备份,又可以作为结果浏览。

快逸报表在vista和win7下无法浏览应用的解决办法

对于vista和win7系统,快逸报表有着良好的兼容性,无论是设计器还是实际应用。有些客户在安装报表设计报表的时候没有遇到问题,但 是在这两种系统下会发现无法启动应用,或者打开设计器自带的ie浏览页面后,页面一直在读取,并没有展示出应有的界面。会出现这个问题是因为在这两种系统 下c盘是对users受写保护的,在安装设计器的时候的权限不足,当启动应用的时候,由于没有足够的权限才导致这个问题。

如果重新安装到其他盘符下,就不会有问题了。

如果不重新安装,解决思路如下:使用管理员身份登陆操作系统,给tomcat配置可用的权限,就可以实现应用的访问了。

解决方法:找到$TOMCAT_HOME$conf,把conf文件夹设置为完全控制。

1.png

(右键属性-安全-找到当前登录名-下面选择完全控制)

保存,重启TOMCAT 。

注意:

重要的是”当前登录名”,不是”CREATOR OWNER”,也不是”Administrator”,如果没有当前用户的话,添加一个即可。

快逸报表同时多列纵向扩展

快逸报表中,属性中有对报表中分栏的设置,通过简单的设置可以完成分栏报表的设计,在页面上展示出来分栏的效果。有些客户在进行设计时需要在报表的内部根据相关的条件进行分栏展示,本篇文章就着重介绍一下这种报表的设置方法。

首先先看一下数据库表结构和数据:

1.png

其中,Pname是 姓名,PYEAR 是入职年份。

按照年分分类,然后在每个年份下循序显示所有的姓名,这个报表如何实现呢?

实现思路:先用group()函数对年份进行分组。然后将每个年份下的数据取出来用valueat()函数进行匹配。下面是报表的设计:

2.png

C2中 =ds1.Group(PYEAR),按照年份分组。

C3中 =ds1.count(),主要目的是计算出每个年份下有多少条记录。

B3中 =max(C3{}),取到所有年份下,记录条数最多一个年份的记录数。

B4中 =1 to B3,扩展出需要的行数,以便所有的记录都能显示出来,而且为数据的扩展提供其中一个参数。

C4中 =valueat(ds1.select(Pname),B4-1),使用ds1.select(Pname)函数取得当年的所有记录,然后再用valueat()函数配合B4提供的参数取得数据集内相应的数据。

在web中展示如下:

3.png

这样报表就设计完了,客户可以根据自己的需求隐藏掉相关行,也可以根据需要在数据的左侧匹配上相关的数据,满足客户的各种需求。

行式填报表中填报监听类的使用小例

行式填报表快逸报表提供的一种特殊的填报表,它相对于普通的填报表而言具有以下优点:
1 在网页上可以动态地实现增加、删除、修改行的数据
2 增加行时可以引用定义的流水号,实现流水号在增加行时自动运算(如递增)的功能
3 可以实现在网页上动态地导入不定行的excel数据

如果有这样的需求:某个报表的数据是从物理表A检索出来的,但是由于某些业务需要,物理表B引用了A表中的某些数据,如果要更新A中数据的同时也要 更新B表中的数据,在A表中添加数据的时候直接在A中添加就可以了,在删除物理表A中数据的同时,要检查B表中是否引用了这个要删除的数据,如果引用了也 要同时把B表中的这个值更新为空。

这样的需求,用行式填报表实现最好了,因为都是整条数据的添加、删除。但是如何实现更新A表也要更新B表,删除A表中的数据也要删除B表中的数据,A表中插入新数据但是不影响B表。

快逸报表提交的时候涉及到三种对数据库的操作:插入(insert) 、删除(delete) 、更新(update)。

行式填报表在提交时的具体体现是:
(1) 在原有数据基础上进行了修改,提交时执行的是update
(2) 在原有数据基础上删除删除了某行,提交时执行的是delete
(3) 在原有数据基础上插入新一行数据,提交时执行的是insert

实现方法:用行式填报表中的更新属性实现对A表数据的添加、删除、修改;在填报监听类中实现对B表的更新和删除;然后填报监听类中根据sql语句知道哪一行数据是新增的、修改的还是被删除了,这样就可以实现上面的需求了。

那么就可以通过继承填报监听类AbstractInputListener 在afterSave()或者afterCommit()函数中获取提交的sql语句,然后在返回的sql语句中进行遍历,查看sql语句的类型。

下面介绍一下监听类的写法:

public class testColInput extends AbstractInputListener{
public void afterSave() throws Exception {//afterCommit方法也可以
//得到sql语句
InputSQL[] sql = this.getInputSql();
Object arg[];
for (int i = 0; i < sql.length; i++) {
String sqlStr = sql[i].getSql();
// 得到sql语句中的参数
arg = sql[i].getParams();
if (sqlStr.matches(”insert.*”)) {
System.out.println(”这是插入语句:” + sqlStr);
for(int j=0;j<arg.length;j++){
System.out.print(” 第”+j+”参数:”+arg[j]);
}
System.out.println();
}
if (sqlStr.matches(”update.*”)) {
System.out.println(”这是修改语句:” + sqlStr);
for(int j=0;j<arg.length;j++){
System.out.print(” 第”+j+”参数:”+arg[j]);
//这里遍历物理表B,A表被修改的数据是否被B表引用
}
System.out.println();
}
if (sqlStr.matches(”delete.*”)) {
System.out.println(”这是删除语句:” + sqlStr);
for(int j=0;j<arg.length;j++){
System.out.print(” 第”+j+”参数:”+arg[j]);
//遍历物理表B,A表被删除的数据是否被B表引用
}
System.out.println();
}
}
}
}

总结:通过上面简单的方法就可以实现这个复杂的需求,填报监听类的功能强大可见一斑。

快逸报表日志文件的配置方法

日志系统是一种不可或缺的跟踪调试工具,特别是在无人职守的后台程序以及那些没有跟踪调试环境的系统中有着广泛的应用。 长期以来日志系统作为一种应用程序服务,对于跟踪调试、程序状态记录、崩溃数据恢复都有非常重要的意义。

快逸报表也提供了一套日志系统,只需要简单的配置,不用再开发代码,就可以生成一个完整的日志系统。

下面说一下日志的配置方法:

首先要在WEB-INF下的reportConfig.xml文件中配置日志记录配置属性文件的路径,如下代码:

<config>
<name>logConfig</name>
<value>/WEB-INF/runqianReportLog.properties</value><!–日志文件的路径–>
</config>

快逸的日志记录配置属性文件为runqianReportLog.properties,默认存放在WEB-INF文件夹下,下面说一下配置文件的内容:

//日志级别可为OFF,ERROR,WARN,INFO,DEBUG,ALL, 优先级依次降低,若为OFF则不输出任何
//日志信息;若为INFO,则只输出ERROR,WARN,INFO等级别的信息,依次类推。
log4j.logger.runqianReportLogger=ALL,LOG1,LOG2
//输出日志到系统控制台
log4j.appender.LOG1=org.apache.log4j.ConsoleAppender
//输出文件的格式
log4j.appender.LOG1.layout=org.apache.log4j.PatternLayout
log4j.appender.LOG1.layout.ConversionPattern=[%d{yyyy-MM-dd HH:mm:ss}] %c : [%-5p] %x - %m%n
//日志文件的编码格式,不设置此项在英文操作系统乱码
log4j.appender.LOG1.encoding=UTF-8
//输出日志到指定文件
log4j.appender.LOG2=org.apache.log4j.RollingFileAppender
//可以指定LOG2.File的全路径, 若未指明,则为相对WEB服务器启动文件的路径
log4j.appender.LOG2.File=D:\\newMis2.log
// Append默认值是true,日志记录添加到末尾;false在每次启动时进行覆盖
log4j.appender.LOG2.Append=true
//日志文件的最大字节数
log4j.appender.LOG2.MaxFileSize=10MB
//日志文件的最多备份数
log4j.appender.LOG2.MaxBackupIndex=1
//输出文件的格式
log4j.appender.LOG2.layout=org.apache.log4j.PatternLayout
log4j.appender.LOG2.layout.ConversionPattern=[%d{yyyy-MM-dd HH:mm:ss}] %c : [%-5p] %x - %m%n

总结:日志配置文件的内容大部分按照默认值就可以,客户可以根据自己的需要修改日志得输出级别和日志文件的路径。

快逸报表和aspx结合后在aspx页面实现工具栏的方法

现在有好多客户在.net中用到了快逸报表,主要方法是把快逸报表部署在一个j2ee应用中,.net部署在IIS中,把报表嵌入到 aspx页面中的iframe里面。如果原本aspx页面中一些按钮或者工具,报表页面也有一个工具栏,如何让两部分的工具栏集合在一起呢。本文介绍一种 把报表的工具栏迁移到aspx页面的方法。

首先要在aspx页面中得到iframe的对象,然后通过这个对象得到发布报表tag标签中的name属性值,得到name后就可以调用快逸提供的js方法实现保存、导出word、导出pdf、导出excel、上一页、下一页等功能了。下面列出代码演示一下

发布报表的标签(jsp页面中)

<report:html name=”report1″ //注意标签中name的属性值,在aspx页面中会用到
reportFileName=”<%=report%>”
funcBarLocation=””
needPageMark=”yes”
generateParamForm=”no”
needLinkStyle=”yes”
params=”<%=param.toString()%>”
exceptionPage=”/reportJsp/myError2.jsp”
/>

Aspx页面代码

<head>
<table id=titleTable width=100% cellspacing=0 cellpadding=0 border=0 ><tr>
<td height=”22″ width=100% valign=”center” style=”font-size:13px”>
<table width=”100%”><tr>
<td width=53% align=”center” style=”font-size:13px” >&nbsp;&nbsp;&nbsp;&nbsp;
<td width=”47%” align=”center” valign=”middle” style=”font-size:12px” >
<!–返回到最前页,先得到发布报表tag标签中name属性值,然后用快逸提供的toPage函数–>
<a href=’#’ onClick=’try{document.frames.reportFrame.report1_toPage( 1 );}catch(e){}return false;’>最前页</a>
<!–到上一页–>
<a href=’#’ onClick=’try{document.frames.reportFrame.report1_toPage(document.frames.reportFrame.report1_getCurrPage()-1);}catch(e){}return false;’>上一页</a>
<!–到下一页–>
<a href=’#’ onClick=’try{document.frames.reportFrame.report1_toPage(document.frames.reportFrame.report1_getCurrPage()+1);}catch(e){}return false;’>下一页</a>
<!–到最后一页–>
<a href=’#’ onClick=’try{document.frames.reportFrame.report1_toPage(document.frames.reportFrame.report1_getTotalPage());}catch(e){}return false;’>最后页</a>
<!–保存的按钮–>
<a href=”#” onClick=”document.frames.reportFrame.report1_save();return false;”>保存</a>
<!–导出excel的按钮–>
<a href=”#” onClick=”document.frames.reportFrame.report1_saveAsExcel();return false;”>导出excel</a>
<!–导出word的按钮–>
<a href=”#” onClick=”document.frames.reportFrame.report1_saveAsWord();return false;”>导出word</a>
<!–导出pdf的按钮–>
<a href=”#” onClick=”document.frames.reportFrame.report1_saveAsPdf();return false;”>导出pdf</a>
</td>
</tr></table>
</td></tr>
</table>

</head>
<body>
<% //访问报表的局域网路径
String url = “http://127.0.0.1:6001/demo/reportJsp/test.jsp?raq=/report_1.raq”;
%>
<!–嵌入的iframe–>
<iframe src=”<%=url%>” frameborder=”0″ id=”reportFrame” name=”reportFrame” scrolling=”yes” height=”100%” width=”100%”></iframe>
</body>

页面效果

1.png

总结:如果以后要在asp想上发布快逸报表就可以按照上面的方法实现了,报表的功能条还能放在asp页面上。

后台传递参数时设置数据类型

在实际项目需求中,参数的灵活运用至关重要,快逸报表提供了参数模板和报表模板,节省了用户写form表单的时间。但是,有一些参数并非来自参数模板或者form表单,可能是后台传入,也可能是业务运算后拼接而成,这些参数再传递给报表时就需要设置参数类型,否则可能会出现参数sql异常的 报错。

下面来重现一下这个错误:

一:创建报表模板并设置参数

1.png

数据源为润乾内置demo数据源

数据集为:SELECT 订单.订单ID,订单.货主名称 FROM 订单 WHERE 订单.订单ID in ( ? )

参数名为:arg1 参数类型为:字符串组

二:测试代码

<%@ page language=”java” import=”java.util.*” pageEncoding=”utf-8″%>
<%@page import=”com.runqian.report4.usermodel.Context”%>
<%@page import=”com.runqian.report4.usermodel.ParamMetaData”%>
<%@page import=”com.runqian.report4.model.ReportDefine”%>
<%@page import=”com.runqian.report4.util.ReportUtils”%>
<%@page import=”com.runqian.report4.usermodel.Engine”%>
<%@page import=”com.runqian.report4.usermodel.IReport”%>
<% try{
Context cxt = new Context();
String reportFile = “d:\\Program Files\\reportHome\\webapps\\demo\\reportFiles\\args.raq”;
//定义或拼接为字符串strs
String strs =”10501,10502,10503″;
ReportDefine rd = (ReportDefine)ReportUtils.read( reportFile );
ParamMetaData pmd = rd.getParamMetaData();
String paramOrMocrName = “”;
if(pmd != null){
for(int i = 0, count = pmd.getParamCount(); i < count; i ++ ) { //讲究优化的写法
paramOrMocrName = pmd.getParam(i).getParamName(); //获取参数名
//System.out.println(paramOrMocrName);
//设参数值,因为只有一个参数,所以直接给其赋值了。
cxt.setParamValue(paramOrMocrName,strs);
}
}
//运算报表
Engine engine = new Engine(rd, cxt);
IReport iReport = engine.calc();
//页面显示
String htmlText = ReportUtils.toHTMLString(iReport,”report1″,request);
out.print(htmlText);
}catch (Throwable t ){
}
%>

运行此代码,后台报sql参数异常错误。

三:修改代码

查看API,发现Types类,此类中的getProperData(byte type, java.lang.String val)方法,可以将字符串按照类型转为相应Object,所以只要是知道参数类型,然后做相应转化即可,最终只需要将上面测试代码中的 cxt.setParamValue(paramOrMocrName,strs);修改为 cxt.setParamValue(paramOrMocrName,Types.getProperData(pmd.getParam(i).getDataType(),strs) )即可。

四:修改后运行结果

2.png

五:总结

数据类型是一个需要注意的地方,如果数据类型出错,整个程序就会出问题,这里也是很难注意到的地方,现在专门写出来,希望大家能够注意。