6.4 高级报表打印方案
6.4.1 生成分组报表
分组报表是根据表中的某个条件在另一张表中查询相关此内容的一系列信息,并将此类信息打印的报表。分组报表在实际中应用广泛。
1.方案分析
本章节将以WebBrowse打印技术演示分组报表,通过药品相关信息表中的药品编号与药品销售表中的药品号相关联查询数据,以达到分组的目的,为了使读者更好的了解此方案,下面给出流程图,如图6.48所示。
图6.48 分组报表打印流程图
2.实施过程
在开发药品后台管理系统时,通常要求以药品种类分组显示药品销售情况,在这种情况下,就可以应用到分组报表打印,如图6.49所示。
图6.49 药品销售管理系统分组报表打印
实例位置:
光盘/mr/6/6.4/6.4.1/11
首先创建两个表,tb_yptable和tb_xsbb,分别为药品相关信息表和药品销售表,数据结构如表6.9、表6.10所示。
表6.9 表格 tb_yptable药品相关信息表
字段名称
|
数据类型
|
描述
|
是否为空
|
id
|
int(4)
|
序列号
|
not null
|
ypid
|
char(10)
|
药品编号
|
null
|
ypname
|
varchar(20)
|
药品名称
|
null
|
ypregion
|
varchar(20)
|
药品产地
|
null
|
ypgg
|
varchar(20)
|
药品规格
|
null
|
ypbz
|
varchar(10)
|
药品包装
|
null
|
表6.10 表格 tb_xsbb药品销售表
字段名称
|
数据类型
|
描述
|
是否为空
|
id
|
int(4)
|
序列号
|
not null
|
ypph
|
varchar(20)
|
药品票号
|
null
|
number
|
int(4)
|
数量
|
null
|
dj
|
money(8)
|
单价
|
null
|
je
|
money(8)
|
金额
|
null
|
khqc
|
varchar(50)
|
客户全称
|
null
|
ypbh
|
varchar(20)
|
药品编号
|
null
|
本实例以药品票号关联两张表,同样也以药品编号进行分组。首先在页面中建立表格,背景设置为表格图片,程序代码如下:
例程6-58 代码位置:光盘/mr/6/6.4/6.4.1/11/tj.jsp
<table width="455" height="54" border="0" align="center" background="Images/tj2_05_01.gif" >
为了取得数据表中的值,创建JavaBean,名称为Tdbb.java,Tdbb.java除了基本的setXXX()和getXXX()方法之外,还有一个根据条件取数据的getReport()方法,此方法的参数是用户输入的产地,getReport()方法返回值类型为集合Collection,方便JSP取值时使用,程序代码如下:
例程6-59 代码位置:光盘/mr/6/6.4/6.4.1/11/src/com/wsy/Tdbb.java
public Collection getRecord(){
Collection ret=new ArrayList();
String sql="select * from tb_yptable";
ConnDB conn=new ConnDB();
ResultSet rs=conn.executeQuery(sql);
try{
while(rs.next()){
Tdbb t=new Tdbb();
t.setId(rs.getString(1));
t.setYpid(rs.getString(2));
t.setYpname(rs.getString(3));
t.setYpregion(rs.getString(4));
t.setYpgg(rs.getString(5));
t.setYpbz(rs.getString(6));
ret.add(t);
}
conn.close();
}catch(Exception e){
e.printStackTrace();
}
return ret;
}
取出药品相关信息表中内容放入Collection中,以便于显示,程序代码如下:
例程6-60 代码位置:光盘/mr/6/6.4/6.4.1/11/tdbb.jsp
Collection ret=(Collection)t.getRecord();
根据药品编号条件将两个表联系在一起,在 SQL语句中用左外连接把两个表联系起来,根据报表编号取得结果集,此方法与getRecord()方法采用了重载技术,具体程序代码如下:
例程6-61 代码位置:光盘/mr/6/6.4/6.4.1/11/src/com/wsy/Tdbb.java
public Collection getRecord(String ypbh){
Collection ret=new ArrayList();
String sql="SELECT t1.ypph, t1.number, t1.dj, t1.je, t1.khqc ,t1.ypbh FROM tb_xsbb t1 LEFT OUTER JOIN tb_yptable t2 ON t1.ypbh = t2.ypid where ypbh='"+ypbh+"'";
ConnDB conn=new ConnDB();
ResultSet rs=conn.executeQuery(sql);
try{
while(rs.next()){
Tdbb t=new Tdbb();
t.setJe(rs.getString("je"));
t.setKhqc(rs.getString("khqc"));
t.setNumber(rs.getString("number"));
t.setDj(rs.getString("dj"));
t.setYpph(rs.getString("ypph"));
t.setYpbh(rs.getString("ypbh"));
ret.add(t);
}
conn.close();
}catch(Exception e){
e.printStackTrace();
}
return ret;
}
在JSP中设置JavaScript打印按钮,程序代码如下:
例程6-62 代码位置:光盘/mr/6/6.4/6.4.1/11/ tj.jsp
<table align="center" id="pay">
<tr class="print">
<td ><INPUT type="button" value="打印设置" id=button1 name=button1 οnclick="setPrint();">
<INPUT type="button" value="打印预览" id=button2 name=button2 οnclick="previewPrint();">
<INPUT type="button" value="打印" id=button3 name=button3 οnclick="window.print();"></td>
</tr>
</table>
注意<head></head>之间要放入<JavaScript>代码,程序代码如下:
例程6-63 代码位置:光盘/mr/6/6.4/6.4.1/11/tj.jsp
<script language="Javascript">
function previewPrint(){
WB.ExecWB(7,1)
}
function setPrint(){
WB.ExecWB(8,1);
}
</script>
注意需在<body>之间放入以下代码,上述JavaScript才可以使用:
例程6-64 代码位置:光盘/mr/6/6.4/6.4.1/11/tdbb.jsp
<OBJECT classid=CLSID:8856F961-340A-11D0-A96B-00C04FD705A2 height=0 id=WB width=0>
</OBJECT>
3.补充说明
在JSP中使用迭代函数列出数据较其他方案取值有一些特别,首先取出药品相关信息表的相关数据,并且打印在JSP界面上,然后根据药品票号取出药品销售信息表的相关数据,也就是使用了嵌套循环的方式实现统计报表功能。
6.4.2 生成主从报表
主从报表又叫做复合报表,即报表本身又包含了一张报表,两张报表的关系可以是从属关系,也可以是并列关系。
1.方案分析
首先要说明的是,子报表也是一张真正的报表,也会编译成相应的*.jasper文件,如果创建一个复合报表,需要创建两张报表,父报表和子报表,父报表是最终用来显示主从报表。在这个方案中,笔者依然使用iReport+JasperReport组件来实现,首先设计父报表,再根据主从报表的关系使用SQL语句查询子报表的数据,然后设计子报表,之后在主报表中放入子报表,具体流程如图6.50所示。
图6.50 打印主从报表流程图
2.实施过程
在开发杰明客户管理系统时,要求输出客户订单详情,包括客户信息,客户订单信息,为了提高系统的运行效率,可以将客户订单信息放入子报表中,以客户编号作为参数从父报表传递到子报表中,在这种情况下,可以应用到主从报表,如图6.51所示。
图6.51 客户订单表
实例位置:
光盘/mr/6/6.4/6.4.2/12
为了演示复合报表的过程,首先创建两个表,分别为客户详细信息表和客户订单表,如表6.11、6.12所示。
表6.11 表格 tb_customer表
字段名称
|
数据类型
|
描述
|
是否为空
|
orderid
|
int(4)
|
订单号
|
not null
|
custname
|
varchar(100)
|
客户名称
|
null
|
customerid
|
varchar(10)
|
客户id
|
null
|
customadd
|
varchar(100)
|
客户住址
|
null
|
表6.12 表格 tb_orderinfo表
字段名称
|
数据类型
|
描述
|
是否为空
|
orderid
|
int(4)
|
订单号
|
not null
|
custname
|
varchar(100)
|
客户名称
|
null
|
address
|
varchar(200)
|
地址
|
null
|
e_mail
|
varchar(100)
|
电子邮件
|
null
|
booklist
|
varchar(300)
|
订单信息
|
null
|
price
|
float(8)
|
价格
|
null
|
time
|
varchar(20)
|
订单时间
|
null
|
num
|
int(4)
|
个数
|
null
|
创建报表名字为fuheReport,在“Data”→“报表查询”输入SQL语句为“select * from tb_customer”,将字段拖入detail区域,为了能让字段名称循环打印,在detail区域输入字段名称,可以根据需要设计风格各异的报表。
设置完成后在iReport组件菜单栏中单击
看是否能正常运行。
新建子报表,命名为subReport,创建一个主从报表传值时使用的参数,右击“Parameters”→“add”选择Parameter,新增参数名称custname,“Default Value Expression”设置为" ",如图6.52所示。
图6.52 设置父报表参数
在“Data”→“报表查询”中输入SQL语句,“select * from tb_orderinfo where custname=$P{custname}”,如图6.53所示。
图6.53 Report query界面
由于子报表最终要在父报表中显示,所以将不必要的栏位高度设置为0,右键单击栏位即可设置,设置完毕编译生成subreport.jasper文件。
子报表设计完成就需要把子报表引入到父报表中。单击
“子报表”图标,在父报表detail栏位适当的位置拖入子报表,此时会弹出对话框设置子报表,如图6.54所示。
图6.54 iReport添加子报表
由于已经制作好了子报表,所以在图6.54中选择"Use an existing report",浏览到已经设置好的子报表的路径,单击next按钮,注意在subreport expession这个页面选择第一个选项,以第一种形式表示子报表的路径。如图6.55所示:
图6.55 iReport添加子报表
单击“Finish”按钮完成设置,如图6.56所示。
图6.56 iReport设计复合报表
接下来开始设置子报表属性,在主报表中双击子报表图表,在弹出的属性窗口中选择”"Subreport"标签页面,在Connection/Data Source expression下拉列表框中选择Use connection expression,如图6.57所示。
图6.57 iReport设置子报表属性
然后选择“Subreport(Other)”页面,设置如图6.58所示。
图6.58 iReport设置子报表属性
还需要增加一个子报表参数,默认值为custname字段,创建名称为custname的参数设置,如图6.59所示。
图6.59 添加custname参数
这样做的目的是把父报表的customer表中的custname字段的值作为参数传入子报表,子报表根据SQL语句检索tb_orderinfo表中对应的数据。
完毕后回到父报表,单击
按钮执行,这时会要求输入子报表所在的位置,单击"Use default",使用SUBREPORT_DIR这个参数的默认值,如图6.60所示。
图6.60 Parameter prompt界面
做完上述的工作后,需要在JSP中读出PDF报表,此时使用JasperReport组件,首先需要将iReport制作好的fuhe.jrxml和subReport.jrxml放入Tomcat安装路径下/Webapps/projectname/reports中,注意同时将光盘中的clients_05.gif、clients_02.gif两张图片放入Tomcat安装路径下/Webapps/projectname/reports路径中,然后使用JasperReport类库中的compileReportToFile()进行编译。程序代码如下:
例程6-65 代码位置:光盘/mr/6/6.4/6.4.2/12/compile.jsp
JasperCompileManager.compileReportToFile(application.getRealPath("/reports/fuheReport.jrxml"),application.getRealPath("/reports/fuheReport.jasper"));
JasperCompileManager.compileReportToFile(application.getRealPath("/reports/subReport.jrxml"),application.getRealPath("/reports/subReport.jasper"));
//分别把主报表和子报表的*.jrxml格式编译成*.jasper格式。
%>
注意:如果使用上述程序,单击
按钮执行,这时会要求输入子报表所在的位置,如图6.60所示。请选择“Use default”,使用子报表参数的默认值,默认值为"",虽然是空值,不过会在下面的JSP中将赋予SUBREPORT_DIR这个参数值,也就是子报表所在的位置。
在父报表中有一个名字为SUBREPORT_DIR的Parameters,在JSP中赋予相应的值,也就是子报表的路径。程序代码如下:
例程6-66 代码位置:光盘/mr/6/6.4/6.4.2/12/zc.jsp
parameters.put("SUBREPORT_DIR",request.getRealPath("/reports/")+"/");
编译成PDF流,编译的时候会要求子报表的路径,由于之前设置了SUBREPORT_DIR为"Use default",所以系统会自动找到子表的真实位置。
例程6-67 代码位置:光盘/mr/6/6.4/6.4.2/12/zc.jsp
byte[] bytes = run.runReportToPdf(fuhereportFile.getPath(), parameters, conn);
执行结果如图6.61所示。
图6.61 JasperReport控制PDF格式输出界面
单击图6.61中的execute连接,主从报表以PDF格式输出。
3.补充说明
做主从报表JasperReport调用唯一的难点就是子报表的路径问题,在主报表中设置一个子报表路径参数,在程序中可以赋予参数实际的值以达到系统找到子报表的目的,但值得注意的是,在动态编译主从报表时,启用“use default”。
本方案中依然在报表中添加图片,以参数的方式赋予图片的路径,由于在统计报表中已经详细介绍过这个方法,所以在这里不再赘述。
6.4.3 生成分栏报表
分栏报表是报表中比较常见的报表形式,本章节着重说明分栏报表的制作,很多软件中都包括分栏操作,如Word、Excel等,可见分栏在现实系统中应用多么广泛。分栏报表打印的好处就在于可以节约纸张。
1.方案分析
本章示例依然延用iReport+JasperReport开发模式。
在iReport组件中制作分栏报表首先应该想到的是用什么条件进行分栏,一般情况都是采用分组条件进行分栏设定,然后考虑需要传入什么参数,如果在JSP中调用报表,参数就显得尤为重要,在本方案中,笔者设计传入显示行数的参数以及报表标题参数,此时显示的行数与报表标题都为动态的。在iReport组件中制作报表,设定栏位,创建参数,然后将*.jrxml放入项目中,使用JasperReport类库进行编译,在JSP中给报表参数赋值,最后以PDF形式显示报表。
下面就来看分栏报表实现流程图,如图6.62所示。
图6.62 分栏报表流程图
2.实施过程
在开发多尼工资系统中,会有一个工资表输出打印的操作,由于工资表内容繁多,在一个界面上正常显示分页较多,这时可以选择分栏显示,方便用户浏览,在这种情况下,就可以应用分栏报表,如图6.63所示即为根据员工职位进行分栏显示。
图6.63 iReport设计分栏报表
实例位置:
光盘/mr/6/6.4/6.4.3/13
首先建立一个表,这是一个工资信息表tb_fenlan,数据结构如表6.13所示。
表6.13 表格 tb_fenlan表
字段名称
|
数据类型
|
描述
|
是否为空
|
id
|
int(4)
|
序列号
|
not null
|
name
|
varchar(20)
|
姓名
|
null
|
jbgz
|
money(8)
|
基本工资
|
null
|
jj
|
money(8)
|
奖金
|
null
|
ylbx
|
money(8)
|
医疗保险
|
null
|
yanglao
|
money(8)
|
养老保险
|
null
|
sgbx
|
money(8)
|
事故保险
|
null
|
zfgjj
|
money(8)
|
住房公积金
|
null
|
bb
|
money(8)
|
补助
|
null
|
sfgz
|
money(8)
|
实发工资
|
null
|
fftime
|
varchar(20)
|
发放时间
|
null
|
zw
|
varchar(10)
|
职位
|
null
|
打开iReport,创建新报表fenlan,新建一个参数ReportTitle,选择Use as a Prompt复选框,如图6.64所示。
图6.64 创建ReportTitle参数
将参数ReportTitle拖入title区域。
可以在报表title区域输出表的总记录数,输出总收益数,由于总记录数字是系统自带变量,可以直接引用。打开Variables区域,将Report_count变量拖入title区域,需要将Report_count转化成String形式。
单击
按钮,在title区域画入一个Text Field区域,在属性区域的Epression输入如下内容:
"在报表中有 " + String.valueOf($V{REPORT_COUNT}) + "条记录"
建立名为MaxOrderID参数,表示要用户输入所要显示的行数,将MaxOrderID拖入title区域,如图6.65所示。
图6.65 创建MaxOrderID参数
Title区域设计如图6.66所示。
图6.66 Title区域设计
这时设计分栏效果,选择报表属性,选择Report columns 添入栏位数为3,如图6.67所示。
图6.67 iReport设置栏位
设计分组变量,变量名为zw,以职位分组显示,如图6.68所示。
图6.68 iReport设置组
选择“Start on a New Column”复选框,表示每一次从新列开始。其中放入需要显示的字段,detail区域放入查询出来的结果集。在zwheader区域放入职位名称,将$F{zw}拖入zwheader区域,在zwFooter区域放入分组后的个数,由于这个变量是系统自带,不需要创建,而是手动拖入zwFooter区域即可。此部分设计如图6.69所示。
图6.69 栏位设计
动态执行报表,此时会要求输入表头信息,MaxOrderid 的值,也就是所要显示的个数,如图6.70所示。
图6.70 最大行数添加界面
同时也会要求输出报表标题参数,如图6.71所示。
图6.71 表头添加界面
来看用JSP如何调用报表,还是和以往的形式一样,首先把制作好的报表以及在报表中使用的两张图片放入Tomcat/Webapps/jasperWebappproject/reports路径中。
执行compile.jsp,方可执行打印报表,编译制作好的报表,使报表生成*.jasper形式。程序代码如下:
例程6-68 代码位置:光盘/mr/6/6.4/6.4.3/13/compile.jsp
JasperCompileManager.compileReportToFile(application.getRealPath("/reports/fenlan2.jrxml"),application.getRealPath("/reports/fenlan2.jasper"));
设置表头名称,参数为ReportTitle,需要显示行数,参数为MaxOrderID,图片路径BaseDir,最后以PDF流的形式打印出来。程序代码如下:
例程6-69 代码位置:光盘/mr/6/6.4/6.4.3/13/fenlan.jsp
<%
File reportFile = new File(application.getRealPath("/reports/fenlan.jasper"));
Map parameters = new HashMap();
parameters.put("ReportTitle", "工资表");
parameters.put("MaxOrderID",7);
parameters.put("BaseDir", reportFile.getParentFile());
Connection conn=null;
try { Class.forName("com.microsoft.jdbc.SQL Server 2000.SQLServerDriver");
conn=DriverManager.getConnection("jdbc:microsoft:SQL Server 2000://localhost:1433;DatabaseName=db_FABD06","sa","");
}catch(Exception e) {
e.printStackTrace();
}
JasperRunManager run=new JasperRunManager();
byte[] bytes = run.runReportToPdf(reportFile.getPath(), parameters, conn);
response.setContentType("application/pdf");
response.setContentLength(bytes.length);
ServletOutputStream ouputStream = response.getOutputStream();
ouputStream.write(bytes, 0, bytes.length);
ouputStream.flush();
out.clear();
out=pageContext.pushBody();
ouputStream.close();
%>
3.补充说明
细心的读者可能发现,在iReport中的图片可以自动找到路径,但在JSP调用的时候却提示图片不存在,为了解决这个问题,创建一个名为BaseDir的参数,类型设置为Java.io.File形式,具体如图6.72所示。
图6.72 BaseDir参数设置界面
参数值设置为reportFile.getParentFile(),读者可以打印这个值,正是Tomcat/Webapps/jasperWebappproject/reports。
修改图片的路径为:new File($P{BaseDir}, "JasperReports.gif"),类型为Java.io.File,如图6.73所示。
图6.73 Image属性
6.4.4 生成交叉报表
交叉报表在企业应用广泛,非常适合企业进行员工业务考评等。
1.方案分析
本方案与其他方案相同,首先连接数据库,取出数据表中的数据,为了方便传值,使用JavaBean技术,本方案使用员工业务考评演示交叉报表,为了将员工业绩信息录入数据库中,在JavaBean中设计一个插入数据的方法,该方法的返回值为整型,返回值可以确定是否将数据插入到数据库中,除此之外还有一个查询的JavaBean方法,此方法的返回值类型为Collection集合,在JSP中引用JavaBean,将数据输出到界面上,员工考评表中拥有使用前面两个字段的比较结果动态变化字段,此时就应用到了交叉表的概念,使用交叉表的SQL语句即可完成上述功能,最后使用JavaScript技术实现报表打印,为了更好理解JavaScript实现交叉报表的全过程,首先看一下流程图,如图6.74所示。
图6.74 交叉报表流程图
2.实施过程
在企业内部管理系统中,有时会根据前面员工考评的业绩动态生成结果,例如根据计划销售量与员工实际销售量之间的差额动态生成考评结果,这样方便了用户使用,能高效地完成员工考评工作,在这种情况下,就可以应用交叉报表,如图6.75、图6.76所示。
图6.75 员工考评表录入
图6.76 员工考评报表打印
为了更直观的演示本方案,首先在数据库中创建员工考评表和测评结构表,数据字典如表6.14、表6.15所示。
表6.14 表格 tb_ygkp员工考评表
字段名称
|
数据类型
|
描述
|
是否为空
|
id
|
int(4)
|
序列号
|
not null
|
name
|
varchar(20)
|
姓名
|
null
|
zjld
|
varchar(20)
|
直接领导
|
null
|
zw
|
varchar(20)
|
职务
|
null
|
jhyj
|
float(8)
|
计划业绩
|
null
|
sjyj
|
float(8)
|
实际业绩
|
null
|
dep
|
varchar(20)
|
部门
|
null
|
df
|
varchar(20)
|
得分
|
null
|
表6.15 表格 tb_pingC 测评结构表
字段名称
|
数据类型
|
描述
|
是否为空
|
id
|
int(4)
|
序列号
|
not null
|
pcxm
|
varchar(50)
|
评测项目
|
null
|
qz
|
float(8)
|
权重
|
null
|
df
|
varchar(20)
|
得分
|
null
|
实例位置:
光盘/mr/6/6.4/6.4.4/14
与上面几个章节处理连接数据库方法相同,将数据库连接代码封装在一个类中,名为Conn.java。为了方便取值,创建一个名为Ygkp.java的JavaBean,此类除了对应数据表中字段的setXXX()、getXXX()方法之外,还有一个向数据表中插入数据的insert()方法,此方法的参数为用户录入的员工考评的详细信息数据,返回值类型为int型,在insert()方法中,引用了PreparedStatement类中setString()方法,插入的SQL语句可以写为SQL="insert into tb_ygkp(name,zjld,zw,jhyj,sjyj,dep,df)values(?,?,?,?,?,?,?)",最后的返回值为PreparedStatement类中的executeUpdate()返回的值,1代表插入成功,0代表没有插入成功,具体程序代码如下:
例程6-70 代码位置:光盘/mr/6/6.4/6.4.4/14/src/com/wsy/Ygkp.java
try{
String sql="insert into tb_ygkp(name,zjld,zw,jhyj,sjyj,dep,df)values(?,?,?,?,?,?,?)";
PreparedStatement pstmt=conn.executePUpdate(sql);
pstmt.setString(1, name);
pstmt.setString(2, zjld);
pstmt.setString(3, zw);
pstmt.setString(4, jhyj);
pstmt.setString(5, sjyj);
pstmt.setString(6, dep);
pstmt.setString(7, df6);
i=pstmt.executeUpdate();
}
除此之外还有一个从数据表中取值的getRecords()方法,返回值类型为集合Collection,其中SQL语句为sql="SELECT *, CASE WHEN (jhyj - sjyj) >= 0 THEN CASE WHEN (jhyj - sjyj) > 0 THEN '↓' ELSE '㊣' END ELSE '↑' END AS qk FROM tb_ygkp";代表如果计划业绩量大于实际业绩量,qk字段的数据为"↓",如果计划业绩量小于实际业绩量,qk字段的数据为"↑",同理,如果计划业绩量等于实际业绩量,qk字段的数据为"㊣",最后从数据库中取出的值通过JavaBean传值,保存到Collection中。具体程序代码如下:
例程6-71 代码位置:光盘/mr/6/6.4/6.4.4/14/src/com/wsy/Ygkp.java
public Collection getRecords(){
Collection ret=new ArrayList();
String sql="SELECT *, CASE WHEN (jhyj - sjyj) >= 0 THEN CASE WHEN (jhyj - sjyj) > 0 THEN '↓' ELSE '㊣' END ELSE '↑' END AS qk FROM tb_ygkp";
Conn conn=new Conn();
ResultSet rs=conn.executeQuery(sql);
try{
while(rs.next()){
Ygkp y=new Ygkp();
y.setId(rs.getString(1));
y.setName(rs.getString(2));
y.setZjld(rs.getString(3));
y.setZw(rs.getString(4));
y.setJhyj(rs.getString(5));
y.setSjyj(rs.getString(6));
y.setDep(rs.getString(7));
y.setDf6(rs.getString(8));
y.setQk(rs.getString(9));
ret.add(y);
}
}catch(Exception e){
e.printStackTrace();
}
return ret;
}
在录入员工考评界面中,设计用户选择分数的功能,此时使用了JavaScript,为单选按钮添加事件,当用户选择分数后的单选按钮时,激发了指定的文本框的值,为选择的分数乘以此类评测在总分中的权重,例如,户选择了“团队协调性”中10分,那么此类评测的得分为10*0.1,此时“得分”后面的文本框中的值为1,最后计算出总分数为各个评测所得分数的和。如图6.77所示:
图6.77 员工评测选择分数
当用户录入数据后,需要将数据提交到指定页面,并将结果显示在界面上,在这里笔者使用了JavaScript提交方式,并采用了弹出页面的方式将员工考评信息显示出来。具体代码如下:
例程6-72 代码位置:光盘/mr/6/6.4/6.4.4/14/jcbb.jsp
document.all.form1.submit();
window.open('jcbb2.jsp','advertise','width=700,height=300,top=10,left=20,scrollbars=yes');
在以往的方案中将数据提交上来后使用request.getParameter()取值,这样的方式很繁琐,其实JSP为程序员提供了更为简便的方式,即使用<jsp:setProperty>方式,当在JavaBean中定义的属性值与表单中的文本框的名字相同时,在JSP中使用<property="*">并提交,JSP自动将用户输入的值对应放入JavaBean中,当需要时,使用getXXX()方法即可使用,定义scope为session时,与此JSP有连接关系的界面都可以接收此JavaBean的值,具体代码如下:
<jsp:useBean id="y" class="com.wsy.Ygkp" scope="session"/>
<jsp:setProperty name="y" property="*"/>
在显示数据界面中,使用迭代函数取JavaBean中的值,程序代码如下:
例程6-73 代码位置:光盘/mr/6/6.4/6.4.4/14/jcbb2.jsp
<%
Collection ret=y.getRecords();
Iterator it=ret.iterator();
while(it.hasNext()){
Ygkp ys=(Ygkp)it.next();
%>
<tr bgcolor="#FFFFFF">
<td><%=ys.getName()%></td>
<td><%=ys.getDep() %></td>
<td><%=ys.getZjld() %></td>
<td><%=ys.getZw() %></td>
<td><%=ys.getJhyj() %></td>
<td><%=ys.getSjyj() %></td>
<td>
<%=ys.getQk() %>
</td>
<td><%=ys.getDf6()%></td>
<td><select name="select2">
<option value="1">↓</option>
<option value="2" selected>㊣</option>
<option value="3">↑</option>
</select></td>
<td><select name="select3">
<option value="1">↓</option>
<option value="2" selected>㊣</option>
<option value="3">↑</option>
</select>
显示数据之后,需要在JSP界面上设置打印按钮、打印预览按钮、打印设置按钮,程序代码如下:
例程6-74 代码位置:光盘/mr/6/6.4/6.4.4/14/jcbb2.jsp
<p>
<INPUT type="button" value="打印设置" id=button1 name=button1 οnclick="setPrint();">
<INPUT type="button" value="打印预览" id=button2 name=button2 οnclick="previewPrint();">
<INPUT type="button" value="打印" id=button3 name=button3 οnclick="window.print();">
</p>
为了使用打印设置和打印预览按钮,需要在<head></head>之间设置JavaScript代码,如下:
例程6-75 代码位置:光盘/mr/6/6.4/6.4.4/14/jcbb2.jsp
<script language="Javascript">
function previewPrint(){
WB.ExecWB(7,1)
}
function setPrint(){
WB.ExecWB(8,1);
}
</script>
在打印预览时候,打印按钮会显示在打印界面上,使用CSS来控制打印按钮不显示。程序代码如下:
例程6-76 代码位置:光盘/mr/6/6.4/6.4.4/14/jcbb2.jsp
<style>
@media print{
div{display:none}
}
</style>
当单击打印预览时,结果如图6.78所示。
图6.78 员工考评表打印预览
3.补充说明
交叉报表在实际应用中非常广泛,意在使用现有字段的值,统计或则作一些运算取值作为新的字段显示到JSP页面中去。
本例子使用静态交叉报表,作为静态交叉有着本身的弊端,列数在语句中需要一一指定,不能根据数据动态调整列数。
在本方案中为单选按钮添加事件使用了DreamWeaver完成的,首先在设计页面中点中所要添加事件的单选按钮,选择DreamWeaver右面的“标签检查器” 下拉菜单,单击“+”,选择“设置文本”→“设置文本域文字”,选择所要设置的文本,添入所要显示的值,如图6.79所示。
图6.79 为控件设置事件
6.4.5 生成套打报表
“套打”是现在应用较为广范的一种打印方式。“套打”是指在使用事先印制有凭证、账簿、报表格式、证书等纸张上进行打印。这种打印方式在打印时,无须打印表格的表格线及其他固定的格式内容,有利于加快打印速度,节约打印耗材,延长打印机的使用寿命。
1.方案分析
首先扫描或用PhotoShop等制图软件制作出证书背景,然后从数据库中的读取数据,在Dreamweaver中按证书规格要求放置表格,然后在表格中放置数据,最后运用JavaScript方法打印报表,具体流程如图6.80所示。
图6.80 套打报表流程图
2.实施过程
在开发某学校管理系统时,用户要求按规格打印教师资格证书,为了提高打印效率,可以把证书背景放入图片中,每次只打印教师详细信息,这样不仅提高了工作效率,而且节省资源消耗,在这种情况下,就可以应用到套打报表,如图6.81所示。
图6.81 证书套打
为了更好的演示套打报表,首先创建一个表格,数据结构如表6.16所示。
表6.16 表格 tb_tdbb
字段名称
|
数据类型
|
描述
|
是否为空
|
id
|
int(4)
|
序列号
|
not null
|
zgmc
|
varchar(20)
|
资格名称
|
null
|
zsbh
|
varchar(20)
|
证书编号
|
null
|
rzdw
|
varchar(30)
|
人证单位
|
null
|
zzr
|
varchar(10)
|
持证人
|
null
|
dw
|
varchar(10)
|
单位
|
null
|
zw
|
varchar(10)
|
职位
|
null
|
zc
|
varchar(10)
|
职称
|
null
|
xk
|
varchar(10)
|
学科
|
null
|
fzdw
|
varchar(20)
|
发证单位
|
null
|
rzsj
|
varchar(10)
|
认证时间
|
null
|
实例位置:
光盘/mr/6/6.4/6.4.5/15
首先创建一个与表字段相对应的JavaBean,除了相应的setXXX()和getXXX()方法之外还有个getRecord()方法,用于从数据库中取值,具体实现程序代码如下:
例程6-77 代码位置:光盘/mr/6/6.4/6.4.5/15/src/com/wsy/Tdbb2.java
public Collection getRecord(){
Collection ret=new ArrayList();
String sql="select * from tb_tdbb";
ConnDB conn=new ConnDB();
ResultSet rs=conn.executeQuery(sql);
try{
while(rs.next()){
Tdbb2 t=new Tdbb2();
。。。//取出数据放入JavaBean中
ret.add(t);
}
conn.close();
}catch(Exception e){
e.printStackTrace();
}
return ret;
}
在JSP中调用JavaBean,程序代码如下:
例程6-78 代码位置:光盘/mr/6/6.4/6.4.5/15/tdbb2.jsp
<jsp:useBean id="t" scope="page" class="com.core.Tdbb2"/>
调用JavaBean,以迭代函数形式取出数据库中的值,程序代码如下:
例程6-79 代码位置:光盘/mr/6/6.4/6.4.5/15/tdbb2.jsp
<%
Collection ret=t.getRecord();
Iterator it=ret.iterator();
while(it.hasNext()){
Tdbb2 t2=(Tdbb2)it.next();
%>
设置表格,表格的背景为扫描或则制作的证书图片。程序代码如下:
例程6-80 代码位置:光盘/mr/6/6.4/6.4.5/15/tdbb2.jsp
<table width="500" height="300" border="0" align="center" background="Images/zhengshu.jpg">
添加打印按钮,程序代码如下:
例程6-81 代码位置:光盘/mr/6/6.4/6.4.5/15/tdbb2.jsp
<INPUT type="button" value="打印设置" id=button1 name=button1 οnclick="setPrint();">
<INPUT type="button" value="打印预览" id=button2 name=button2 οnclick="previewPrint();">
<INPUT type="button" value="打印" id=button3 name=button3 οnclick="window.print();">
注意要在<body>区域加入程序代码如下,打印按钮才能使用:
例程6-82 代码位置:光盘/mr/6/6.4/6.4.5/15/tdbb2.jsp
<OBJECT classid=CLSID:8856F961-340A-11D0-A96B-00C04FD705A2 height=0 id=WB width=0>
</OBJECT>
在<head></head>区域加入程序代码如下:
例程6-83 代码位置:光盘/mr/6/6.4/6.4.5/15/tdbb2.jsp
<script language="Javascript">
function previewPrint(){
WB.ExecWB(7,1)
}
function setPrint(){
WB.ExecWB(8,1);
}
</script>
如果在打印时不显示打印按钮,应该使用CSS来控制,在<head></head>区域加入CSS代码,程序代码如下:
例程6-84 代码位置:光盘/mr/6/6.4/6.4.5/15/tdbb2.jsp
<style>
@media print{
.print{display:none}
}
</style>
单击打印预览按钮,结果如图6.82所示。
图6.82 证书套打预览
3.补充说明
背景表格图片可以是扫描可得,也可使用绘图软件画出。
在开发报表打印模块时,用户可以定义CSS样式来指定不需要打印的内容(如表格等元素)。但是,当用户在预览页面打印效果时,会发现网页背景颜色及图像也在打印的范围之内,这并不是想要看到的效果。这时可以通过设置IE浏览器的Internet选项,来指定打印时是否显示网页背景颜色和图像。具体步骤如下:
(1)在浏览网页时,单击IE浏览器上方的“工具”菜单项,选择“Internet选项”命令。
(2)在打开的“Internet选项”窗口中,选择“高级”选项卡,查看“打印背景颜色和图像”复选框是否处于选中状态。如果处于选中状态,则说明在打印时将打印网页的背景颜色及图像;如果处于未选中状态,则说明在打印时将忽略网页的背景颜色及图像。用户可以根据实际情况设置该选项。
图6.83 Internet选项窗口
6.4.6 生成图表报表
在实际的系统中,经常需要进行数据统计分析,将分析结果以图表的形式展现出来,所以图表报表在实际开发中也非常重要,应用也非常广泛。
1.方案分析
在本示例中,采用AWT方式实现图表报表,由于图片报表表现形式繁多,所以在这里只以柱型报表、折线报表以及饼图报表来说明图表报表。
在本方案中,首先根据用户输入时间取出数据库中的横坐标的数据,如果是上旬,取出前15天的数据,如果是下旬,取出后15天的数据,天数作为横坐标,由于纵坐标内容是不变的,所以没有把纵坐标的数据放入数据库中,使用JavaBean中的方法把SQL语句查询的数据放入一个二维数组中,将这个二维数组作为参数赋予到画图类中,使用AWT类库完成图表的绘制。
为了更好的理解本方案,请看流程图,如图6.84所示。
图6. 84 图表报表流程图
2.实施过程
在开发股票网站时,通常需要一个以图表显示股票大盘走势的操作,此时,需要用到图表报表并打印,在这种情况下,就应用到了图表报表,如图6.85、6.86所示。
图6.85 柱形图表
图6.86 折线图表
为了更好的演示图表报表,首先创建一个大盘数据表,数据结构如表6.17所示。
表6.17 表格 tb_chat
字段名称
|
数据类型
|
描述
|
是否为空
|
id
|
序列号
|
int(4)
|
not null
|
date
|
日期
|
char(2)
|
null
|
jg
|
金额
|
money(8)
|
null
|
首先来看柱型图。
实例位置:
光盘/mr/6/6.4/6.4.6/16
与每个方案相同,本方案首先连接数据库,同样将连接数据库代码封装在一个类中,便于操作,为了传值设计一个名为Chat.java的JavaBean,此类除了具有setXXX()和getXXX()方法之外,还存在一个从数据库表中取值的方法getRecords(),它的返回值是一个二维数组。在getRecords()方法内,由于需要显示半个月的数据情况,所以定义一个15行,5列的二维数组,通过行列循环,将从数据表中取出的值赋予到二维数组中,行循环由rs.next()来控制,列循环由二维数组的列数控制,SQL语句中以用户选择时间为条件输出结果集,如果是上旬,查询前15天的结果,如果是下旬,查询后15天的结果,程序代码如下:
例程6-85 代码位置:光盘/mr/6/6.4/6.4.6/16/src/com/wsy/Chat.java
public String[][] getRecords(String sx,String year,String month){
String [][]a=new String [15][5];
ConnDB conn=new ConnDB();
String sql=null;
if(sx.equals("上旬")){
sql="select * from tb_chat where year='"+year+"'and month='"+month+"' and date>15";
}
else{
sql="select * from tb_chat where year='"+year+"'and month='"+month+"' and date<=15";
}
ResultSet rs=conn.executeQuery(sql);
int i=0;
try{
while(rs.next()){
for(int j=0;j<a[i].length;j++){
a[i][j]=rs.getString(j+1);
}
i++;
}
}catch(Exception e){
e.printStackTrace();
}
return a;
}
为了方便画出图表,设计一个画图类,DrawPicture类,此类主要包括设置图表标题、设置图表大小、设置横纵坐标、添加数据、画图等方法,设置图表标题、设置图表大小、设置横纵坐标、画图四个方法是典型的setXXX()方法,目的在于把参数赋予类中相关的属性,在画图方法中引用。
着重看一下画图方法。
名为Draw的画图方法的参数是一个HttpServletResponse类型,没有返回值。首先设置准备工作,清空缓冲区,设置客户端响应类型为图片格式,创建一个指定大小的图片,实例化一个Graphics2D对象,看一下源代码,请读者注意代码中的注释,程序代码如下:
例程6-86 代码位置:光盘/mr/6/6.4/6.4.6/16/src/com/wsy/DrawPicture.java
double percent = pictureWidth / 460.0; //绘图百分比
// 清空缓冲区
response.reset();
// 参数image的意思是设置返回客户端的响应数据类型为图像,参数pictureType为图片格式
response.setContentType("image/png");
// 创建一个指定大小的图像
BufferedImage image = new BufferedImage(pictureWidth, pictureHeight,BufferedImage.TYPE_INT_RGB);
// 创建Java2D对象,Java2D即对二维图表的支持
Graphics2D g2d = image.createGraphics();
绘制图片背景,绘制一个指定大小的长方形,起点为第一行第一列,程序代码如下:
例程6-87 代码位置:光盘/mr/6/6.4/6.4.6/16/DrawPicture.java
//绘制图片背景
g2d.setPaint(Color.WHITE); //设置颜色
g2d.fillRect(0, 0, pictureWidth, pictureHeight); //参数含义(x,y,width,height)
绘制图框,图框大小引用了上面的百分比,程序代码如下:
例程6-88 代码位置:光盘/mr/6/6.4/6.4.6/16/ src/com/wsy/DrawPicture.java
//绘制图框
g2d.setPaint(Color.blue);
int roundSize = (int) (40 * percent);
g2d.fillRoundRect(0, 0, pictureWidth, pictureHeight, roundSize,
roundSize);
接下来开始绘制图表,引用上面的百分比设置大小,程序代码如下:
例程6-89 代码位置:光盘/mr/6/6.4/6.4.6/16/ src/com/wsy/DrawPicture.java
//绘制绘图区
g2d.setPaint(Color.WHITE);
int a = (int) (30 * percent);
int b = (int) (50 * percent);
g2d.fillRect(a, b, pictureWidth - a * 2, pictureHeight - (a + b));
确定图表大小后,开始绘置横纵坐标,设置坐标数值,颜色等,程序代码如下:
例程6-90 代码位置:光盘/mr/6/6.4/6.4.6/16/ src/com/wsy/DrawPicture.java
//绘制坐标轴
g2d.setPaint(Color.BLACK);
g2d.drawLine(a + (int) (40 * percent), pictureHeight - a * 2,
pictureWidth - (a + 10), pictureHeight - a * 2); //x
g2d.drawLine(a + (int) (40 * percent), b + 10, a + (int) (40 * percent),
pictureHeight - a * 2); //y
// //绘制坐标原点
// g2d.setColor(Color.BLACK);
// g2d.setFont(new Font("华文隶书", Font.BOLD, 10)); //设置字体及大小
// g2d.drawString("0", a + (int) (40 * percent) - (int) (20 * percent),
// pictureHeight - a * 2 + (int) (16 * percent)); //设置输出内容及输出位置
// 创建虚线笔划,设置线条颜色
float[] dashes = {3.f};
BasicStroke bs = new BasicStroke(1.0f, BasicStroke.CAP_ROUND,
BasicStroke.JOIN_ROUND, 10, dashes, 0);
g2d.setStroke(bs);
g2d.setPaint(Color.BLACK);
//绘制标准线
int y = pictureHeight - a * 2;
//前面加的1是为了弥补取整时的误差用的
int ySpacing = 1 + ((pictureHeight - a * 2) - (a + (int) (40 * percent)) -
(int) (10 * percent)) / yTitle.length;
for (int i = 0; i < yTitle.length; i++) {
y = y - ySpacing;
g2d.drawLine(a + (int) (40 * percent), y,
pictureWidth - (a + 10), y);
}
//绘制坐标轴文字
g2d.setColor(Color.BLACK);
g2d.setFont(new Font("华文隶书", Font.BOLD, 10));
//x轴
int xTitleLengthAll = 0;
for (int i = 0; i < xTitle.length; i++) {
xTitleLengthAll = xTitleLengthAll + xTitle[i].length();
}
int xSpacing = ((pictureWidth - (a + 10)) - (a + (int) (40 * percent)) -
(int) (10 * percent) - (xTitleLengthAll * 11)) /
(xTitle.length * 2);
int x = a + (int) (40 * percent);
for (int i = 0; i < xTitle.length; i++) {
x = x + xSpacing;
g2d.drawString(xTitle[i], x,
pictureHeight - a * 2 + (int) (16 * percent));
x = x + xTitle[i].length() * 11 + xSpacing;
}
//y轴
y = pictureHeight - a * 2 + 5;
int yTitleLengthMax = 0;
for (int i = 0; i < yTitle.length; i++) {
if (yTitle[i].length() > yTitleLengthMax) {
yTitleLengthMax = yTitle[i].length();
}
}
for (int i = 0; i < yTitle.length; i++) {
y = y - ySpacing;
g2d.drawString(yTitle[i],
a + (int) (40 * percent) - yTitleLengthMax * 6 - 2, //注意:如果Y轴标题包含汉字,则乘11,否则乘6
y);
}
定义图表标题,并设置显示样式,程序代码如下:
例程6-91 代码位置:光盘/mr/6/6.4/6.4.6/16/ src/com/wsy/DrawPicture.java
//定义标题
g2d.setColor(Color.RED);
g2d.setFont(new Font("宋体", Font.BOLD, 16));
//确定标题开始输出的位置,确保居中显示
int outputTitleInX = 0;
int titleLength = pictureTitle.length();
if (titleLength % 2 == 0) {
outputTitleInX = (pictureWidth - titleLength * 17) / 2;
} else {
outputTitleInX = (pictureWidth - titleLength * 17) / 2 + 2;
}
g2d.drawString(pictureTitle, outputTitleInX, (int) (36 * percent));
绘制柱状图形,程序代码如下:
例程6-92 代码位置:光盘/mr/6/6.4/6.4.6/16/ src/com/wsy/DrawPicture.java
//绘制柱状图
//判断数据类型
int[] histogramHigh = new int[data.length];
if (valueOrPercent.equals("percent")) {
//求百分比
double dataSum = 0;
for (int i = 0; i < data.length; i++) {
dataSum = dataSum + data[i];
}
for (int i = 0; i < data.length; i++) {
histogramHigh[i] = (int) (data[i] *
((pictureHeight - a * 2) -
(a + (int) (40 * percent)) -
(int) (10 * percent)) / dataSum);
}
} else {
//求值
int yTitleMin = Integer.parseInt(yTitle[0]);
int yUnit = Integer.parseInt(yTitle[1]) - yTitleMin;
for (int i = 0; i < data.length; i++) {
if (data[i] > yTitleMin) {
histogramHigh[i] = (int) (data[i] - yTitleMin + yUnit) *
ySpacing / yUnit;
} else {
histogramHigh[i] = (int) data[i] * ySpacing / yTitleMin;
}
}
}
//绘制柱状图
g2d.setPaint(Color.GREEN);
int xMiddle = 0;
int xAll = 0;
for (int i = 0; i < histogramHigh.length; i++) {
xMiddle = xAll + xSpacing + xTitle[i].length() * 11 / 2;
xAll = xAll + xSpacing * 2 + xTitle[i].length() * 11;
g2d.fillRect(a + (int) (40 * percent) + xMiddle - 6,
pictureHeight - a * 2 - histogramHigh[i], 12,
histogramHigh[i]);
}
部署图形,写入流中,最后关闭流。程序代码如下:
例程6-93 代码位置:光盘/mr/6/6.4/6.4.6/16/ src/com/wsy/DrawPicture.java
// 部署图形
g2d.dispose();
// 利用ImageIO类的write方法对图像进行编码,生成png格式的图像
ServletOutputStream sos = null;
try {
sos = response.getOutputStream();
ImageIO.write(image, "PNG", sos);
sos.close();
} catch (IOException ex) {
}
}
绘制好图表后,在JSP界面中调用,首先把数据库中的日期,放入数组中,在DrawPicture类中使用setXTitle()载入数组作为横坐标,由于纵坐标值固定,所以,在JSP中给出固定值,在DrawPicture类中使用setYTitle()载入数组作为纵坐标,程序代码如下:
例程6-94 代码位置:光盘/mr/6/6.4/6.4.6/16/ src/com/wsy/DrawPicture.java
for(int i=0;i<a.length;i++){
xTitle[i]=a[i][1];
draw.setXTitle(xTitle);
String[] yTitle = {"1940","1960","1980","2000","2020", "2040", "2060", "2080", "3000", "3020"};
draw.setYTitle(yTitle);
draw.draw(response);
为了避免产生如下异常:
ERROR [Engine] StandardWrapperValve[jsp]: Servlet.service() for servlet jsp threw exceptionJava.lang.IllegalStateException: getOutputStream() has already been called for this response
需要在程序代码中添加程序代码如下:
例程6-95 代码位置:光盘/mr/6/6.4/6.4.6/16/drawPicture.jsp
out.clear();
out = pageContext.pushBody();
上述异常的主要是Web容器生成的Servlet代码中存在out.write("")方法,这个方法与JSP中调用的response.getOutputStream()产生冲突。即Servlet规范说明,不能既调用response.getOutputStream(),又调用response.getWriter(),无论先调用哪一个,在调用第二个时候应会抛出IllegalStateException,因为在JSP中,out变量是通过response.getWriter()方法得到的,在程序中即用了response.getOutputStream,又用了out变量,故出现以上错误。
下面来看折线图。
实例位置:
光盘/mr/6/6.4/6.4.6/17
折线图表和柱形图表绘制基本相同,背景、图框、横纵坐标的绘制和柱形图表相同,只是图表的表现形式不同。请读者注意代码中的注释,来看折线图表代码:
例程6-96 代码位置:光盘/mr/6/6.4/6.4.6/17/ src/com/wsy/DrawPicture.java
//绘制折线
//获取折点x坐标
int[] xValues = new int[data.length];
int xAll = a + (int) (40 * percent);
for (int i = 0; i < data.length; i++) {
xValues[i] = xAll + xSpacing + xTitle[i].length() * 11 / 2;
xAll = xAll + xSpacing * 2 + xTitle[i].length() * 11;
}
//获取折点y坐标
//判断数据类型
int[] yValues = new int[data.length];
if (valueOrPercent.equals("percent")) {
//求百分比
double dataSum = 0;
for (int i = 0; i < data.length; i++) {
dataSum = dataSum + data[i];
}
for (int i = 0; i < data.length; i++) {
yValues[i] = pictureHeight - a * 2 -
(int) (data[i] *
((pictureHeight - a * 2) -
(a + (int) (40 * percent)) -
(int) (10 * percent)) / dataSum);
}
} else {
//求值
int yTitleMin = Integer.parseInt(yTitle[0]);
int yUnit = Integer.parseInt(yTitle[1]) - yTitleMin;
for (int i = 0; i < data.length; i++) {
if (data[i] > yTitleMin) {
yValues[i] = pictureHeight - a * 2 -
(int) (data[i] - yTitleMin + yUnit) * ySpacing /
yUnit;
} else {
yValues[i] = pictureHeight - a * 2 -
(int) data[i] * ySpacing / yTitleMin;
}
}
}
//绘制折线
g2d.setPaint(Color.CYAN);
float[] dashesA = {5.f};
bs = new BasicStroke(2.0f, BasicStroke.CAP_ROUND,
BasicStroke.JOIN_ROUND, 10, dashesA, 0);
g2d.setStroke(bs);
g2d.drawPolyline(xValues, yValues, data.length);
// 部署图形
g2d.dispose();
// 利用ImageIO类的write方法对图像进行编码,生成png格式的图像
ServletOutputStream sos = null;
try {
sos = response.getOutputStream();
ImageIO.write(image, "PNG", sos);
sos.close();
} catch (IOException ex) {
}
下面来看饼型图:
实例位置:
光盘/mr/6/6.4/6.4.6/18
饼状图与柱形图以及折线图有明显不同,就是不需要横纵坐标,程序代码如下:
例程6-97 代码位置:光盘/mr/6/6.4/6.4.6/18/src/com/wsy/DrawPicture.java
//绘制饼状图
double dataSum = 0;
for (int i = 0; i < data.length; i++) {
dataSum = dataSum + data[i];
}
g2d.setFont(new Font("宋体", Font.BOLD, 11));
double startAngle = 0; //开始绘制扇形的角度
double arcAngle = 0; //绘制扇形的角度
int xCenter = pictureWidth / 2; //饼状图圆心的x轴坐标
int x = 0;
int y = 0;
String dataDescribe = "";
for (int i = 0; i < data.length; i++) {
g2d.setColor(Color.getHSBColor((i + 36) * 12, (i + 30) * 12,
(i + 30) * 12));
startAngle = startAngle + arcAngle;
arcAngle = (data[i] * 360 / dataSum);
//绘制饼状图
g2d.fillArc((int) (pictureWidth - (int) (186 * percent)) / 2,
(int) (pictureHeight - (int) (186 * percent)) / 2 +
(b - a) / 2, (int) (186 * percent),
(int) (186 * percent), (int) startAngle, (int) arcAngle);
//绘制定义文字
//定义一个新饼图,与显示的是同心圆,但不显示,绘制的每个扇形角度为显示的一半
Arc2D.Double arc2d = new Arc2D.Double((int) (pictureWidth -
(int) (200 * percent)) / 2,
(int) (pictureHeight - (int) (200 * percent)) /
2 + (b - a) / 2, (int) (200 * percent),
(int) (200 * percent), (int) startAngle,
arcAngle / 2, Arc2D.PIE);
//演示确定定义文字输出位置的圆弧
// g2d.setColor(Color.CYAN);
// g2d.draw(arc2d);
//获取新饼图的终点坐标
Point2D.Double endPoint = (Point2D.Double) arc2d.getEndPoint();
x = (int) endPoint.getX();
y = (int) endPoint.getY();
dataDescribe = (int) (data[i] * 100 / dataSum) + "%";
//如果终点在圆心的左侧,则再向左移动定义文字的长度
if (x < xCenter) {
x = x - (title[i].length() * 11 + dataDescribe.length() * 6) - 8;
}
//输出定义文字
g2d.drawString(title[i] + " " + dataDescribe, x, y);
}
// 部署图形
g2d.dispose();
// 利用ImageIO类的write方法对图像进行编码,生成png格式的图像
ServletOutputStream sos = null;
try {
sos = response.getOutputStream();
ImageIO.write(image, "PNG", sos);
sos.close();
} catch (IOException ex) {
}
3.补充说明
简要介绍一下AWT,Java.awt是创建用户接口、绘图和图像的所有类,例如按钮或滚动条,在AWT中被称为组件,Compomant类是所有AWT组件的根。
用户与组件交互操作时候,一些组件会激发事件,AWTEnvent类与其子类用于表达AWT组件能够激发的事件。
容器是一个可以含有组件和其他容器的组件,容器还可以有一个布局管理器,用于控制容器中的位置,AWT包含有几种布局管理器类和一个可以用来创建自己的布局管理器的接口。
6.4.7 生成隔行变色报表
隔行变色比较简单,基本上就是运用JavaScript+CSS方式实现,隔行变色报表在实际应用中也很广泛,增加了页面的饱和度,使系统的整体效果变的丰满漂亮。
1.方案分析
生成隔行变色表格方案本书采用JavaScript+CSS实现,CSS是相对不错的界面美化语言,在本方案中,首先取数据库中的数据,同时使用SQL取出按各个款项分类累计金额的总和以及4年来各项开支的总和,使用JavaBean的方法将结果放入集合中,方便在JSP中使用,为了取当前行为单数行还是双数行,首先要取得表格中总行数,做总行数与二取余操作,整除即是双数行,没有除尽即是单数行,然后使用CSS控制单行双行的颜色,为了方便读者理解本方案,本示例具体流程如图6.87所示。
图6.87 隔行变色报表打印流程图
2.实施过程
在开发财务系统时,用户要求统计年度支出报表要独特,方便浏览,在这种情况下,就应用到了隔行变色报表,如图6.88所示。
图6.88 隔行变色报表
实例位置:
光盘/mr/6/6.4/6.4.7/19
为了更好地演示本方案,首先创建表tb_ysbb,数据结构如表6.18所示。
表6.18 表格 tb_ysbb
字段名称
|
数据类型
|
描述
|
是否为空
|
id
|
int(4)
|
序列
|
not null
|
gz
|
money(8)
|
工资金额
|
null
|
ly
|
money(8)
|
旅游金额
|
null
|
year
|
varchar(10)
|
年份
|
null
|
gyp
|
money(8)
|
供应品金额
|
null
|
sb
|
money(8)
|
设备金额
|
null
|
下面详细介绍各个部分实现原理。
同以上几个章节相同,需要创建与数据表字段相对应的JavaBean,JavaBean中除了有setXXX(),getXXX()方法之外,还有一个根据id查询各个字段以及这一年中各种开销的总和的方法getRecord(int i),部分程序代码如下:
例程6-98 代码位置:光盘/mr/6/6.4/6.4.7/19/src/com/wsy/Ysbb.java
public Collection getRecord(int id){
Collection ret=new ArrayList();
ConnDB conn=new ConnDB();
String sql="SELECT id,gz, ly, gyp, sb, gz + ly + gyp + sb AS zj FROM tb_ysbb WHERE id = '"+id+"'";
ResultSet rs=conn.executeQuery(sql);
try{
while(rs.next()){
Ysbb y=new Ysbb();
y.setId(rs.getString("id"));
y.setGyp(rs.getString("gyp"));
y.setGz(rs.getString("gz"));
y.setLy(rs.getString("ly"));
y.setSb(rs.getString("sb"));
//y.setYear(rs.getString("year"));
y.setZj(rs.getString("zj"));
ret.add(y);
conn.close();
}
}catch(Exception e){
e.printStackTrace();
}
return ret;
}
实现隔行变色,判断i是否能被2整除,i为在循环中从0开始累加的变量,如果可以整除的话,定义tr的样式为灰色,如果不能整除的话,定义tr的样式为蓝色。程序代码如下:
例程6-99 代码位置:光盘/mr/6/6.4/6.4.7/19/bs.jsp
<tr<%if (y.selectCount()%2==0){ %> style="background-color:#B9EEFE"<%}else{ %> style="background-color:#999999"<%} %> >
JavaBean的方法getRecord()取4年里各项开支的总计,同样以迭代方式输出,此时使用了重载技术,程序代码如下:
例程6-100 代码位置:光盘/mr/6/6.4/6.4.7/19/src/com/wsy/Ysbb.java
public Collection getRecord(){
Collection ret=new ArrayList();
ConnDB conn=new ConnDB();
String sql="SELECT SUM(gz) AS gzzj, SUM(ly) AS lyzj, SUM(gyp) AS gypzj, SUM(sb) AS sbzj FROM tb_ysbb";
ResultSet rs=conn.executeQuery(sql);
try{
while(rs.next()){
Ysbb y=new Ysbb();
y.setGypzj(rs.getString("gypzj"));
y.setGzzj(rs.getString("gzzj"));
y.setLyzj(rs.getString("lyzj"));
y.setSbzj(rs.getString("sbzj"));
ret.add(y);
conn.close();
}
}catch(Exception e){
e.printStackTrace();
}
return ret;
}
添加打印按钮,程序代码如下:
例程6-101 代码位置:光盘/mr/6/6.4/6.4.7/19/bs.jsp
<INPUT type="button" value="打印设置" id=button1 name=button1 οnclick="setPrint();">
<INPUT type="button" value="打印预览" id=button2 name=button2 οnclick="previewPrint();">
<INPUT type="button" value="打印" id=button3 name=button3 οnclick="window.print();">
注意要在<body>区域加入例程6-102,打印按钮才能使用。
例程6-102 代码位置:光盘/mr/6/6.4/6.4.7/19/bs.jsp
<OBJECT classid=CLSID:8856F961-340A-11D0-A96B-00C04FD705A2 height=0 id=WB width=0>
</OBJECT>
单击“打印预览”如图6.89所示。
图6.89 隔行变色报表
3.补充说明
很多读者关心的是如何去掉打印按钮,应用如下代码可以使打印按钮在打印中不显示。
<style>
@media print{
.print{display:none}
}
</style>
在打印按钮的tr中应用print样式:
<tr class="print">