|
Posted on 2005-01-07 18:26 海天一鷗 閱讀(2975) 評論(4) 編輯 收藏 所屬分類: 報(bào)表專題
JasperReport和iReport是不錯的Java報(bào)表工具. 在實(shí)際項(xiàng)目中, 本人用它們開發(fā)了20個Report, 涉及SubReport, Image, Graph, 積累了一些經(jīng)驗(yàn). 尤其是關(guān)于Export到Excel方面, 文檔上也很少提及, 純粹是摸索出來的, 有的問題還是通過讀源代碼才解決的. 此貼并非入門教程, 差不多算是筆記吧, 以問答形式記錄.
iReport 安裝 下載,解壓iReport 0.4.0 (推薦src版本) 確認(rèn)JDK是1.4以上 把JDK /lib下的tools.jar拷貝到{ireport_home}/lib目錄中 運(yùn)行 對于下載的Binary版本,只能運(yùn)行/bin/startup.bat 對于下載的Src版本,可以通過ant iReport運(yùn)行(先安裝ant) 如果運(yùn)行startup.bat,出現(xiàn)java.lang.NoSuchMethodError錯誤,一般是JDK版本太低。如果確認(rèn)已安裝了1.4或以上,檢查path系統(tǒng)變量,看看有1.3的JRE是不是排在前面(比如安裝了Oracle的客戶端,往往有1.3的JRE),如果出現(xiàn)Class Not Found,檢查classpath。對于通過ant的方式運(yùn)行,一般都沒什么問題,所以推薦下載src版本 JasperReport 常見問題 .jrxml vs .jasper 如果在運(yùn)行時載入.jrxml, 那么每次調(diào)用還得編譯, 不如預(yù)先編譯成.jasper.不過預(yù)先編譯的jasper,必須用同樣版本的JasperReport載入,而且靈活性差些. 不過對于大部分報(bào)表,還是預(yù)先編譯成jasper方便 如果批量編譯jrxml 用Ant很容易解決
.....
如何使用圖片? 很容易,用Image控件就可以了. 在Image Express里面可以用String來表示圖片的路徑, 或者用InputStream, File對象.不過不管用File還是String對象, 都不得不用絕對路徑, 這顯然很不靈活. 解決辦法是,穿入一個$P的參數(shù),表示圖片所在的目錄,然后用$P和文件名拼接出完整的絕對路徑. 更好的方法是用InputStream, 例如this.getClass().getResourceAsStream("logo.jpg") ,這時只要把圖片放在當(dāng)前.jasper所在的目錄就可以了,不必考慮什么參數(shù),什么路徑了 顯示非數(shù)據(jù)庫字段變量 顯示如運(yùn)行日期等,可以直接在Text Field里面輸入new java.util.Date(), 然后把Pattern設(shè)成如mm/dd/yyyy. 動態(tài)控制某些Field是否顯示 每個Static Text, Text Field甚至整個Band的屬性里面都有Print When Expression, 比如設(shè)成new Boolean(!$P{isDisplay}.equalsIgnoreCase("yes")), 那么只有當(dāng)參數(shù)display的值為yes的時候才顯示 使用Sub Report, 如何使用相對路徑 見1.3, 和使用圖片類似, 用InputStream或者傳入?yún)?shù) Query里面如何使用參數(shù) $P!{xxx} 或者 $P{xxx} 后者只能用于類似PreparedStatement參數(shù)綁定, 而前者可替換Sql的任意部分. 在需要動態(tài)排序的時候, 前者特別有用. 比如 select a,b,c from t order by $P!{orderClause} 不管用$P還是$P!, SQL最終是以PreparedStatement方式執(zhí)行的, 不必太擔(dān)心性能問題 注意:參數(shù)是不能嵌套的, 比如$P{a} =''$P{b}'' , $P{b}=''value'', 不要指望$P{a}能被替換成''value'' 如何使用圖表(Graph) JasperReport本身沒有圖表功能, 只有顯示Image的功能(見4.3). iReport里有個Graph向?qū)? 其實(shí)質(zhì)是通過jFreeChart生成Image. 更另外, 更直接的做法是放一個Image控件, Image Express Class設(shè)置成java.awt.Image, 在Image Expression里通過自定義的類返回java.awt.Image對象. 例如''GraphProvider.getImage($P{REPORT_DATASOURCE},title, subtitle.....)''. GraphProvider是自己的類, public static Image getImage(JRDataSource, ....) 如果顯示多個圖表 在一張報(bào)表上顯示一個圖表和顯示多個圖表是不同的. 假設(shè)Query是select name,price,qty from xxx, 第一張圖顯示name-price, 第二張圖顯示name-qty, 如果還是按3.8的方法, 第二張圖根本顯示不出來! 為什么? 因?yàn)閭魅氲氖荍RDataSource, 而JRDataSource僅僅是對ResultSet的簡單封裝, 在第一張圖處理完后, 游標(biāo)已經(jīng)到了eof位置了, 在開始處理第二張圖的時候,就必然拋出游標(biāo)耗盡的異常! 怎么辦?? 自己寫個JRDataSourceAdapter, 把JRDataSource對象里面的值預(yù)先保存到一個Collection (相當(dāng)于一個Offline的數(shù)據(jù)集), 然后把這個Collection傳個getImage方法. 具體是, 建一個Variable mydate, 類型是java.util.Map, Calculation Type- System, Initial Value Expression是JRDataSourceAdapter.JRDataSource2Map($P{REPORT_DATA_SOURCE},new String[]{"NAME","PRICE","QTY"},new Class[]{java.lang.String.class,java.lang.Double.class,java.lang.Double.class}), JRDataSource2Map是自己寫的一個Adapter. 然后在Image的Expression里面換成如''GraphProvider.getImage(mydata,title, other params...), 當(dāng)然得修改getImage方法 Export到Excel的問題 如何去掉報(bào)表頭等 直接把不需要的Band刪除(把其高度設(shè)為0). 如果僅僅是export到Excel的時候不需要報(bào)表頭, 而輸出到PDF等仍然需要保留, 那么使用print when expression, 見4.4 如果讓Excel看起來整齊 不要有空白地方! 首先把所有的Field設(shè)成一樣高, 對齊! 把所在Band的高度也設(shè)成和Field一樣高, 讓Field正好放入Band. 然后調(diào)整Field的寬度, 讓每個Field都相鄰,沒有空隙. 最后,記得設(shè)置參數(shù): exporter.setParameter(JRXlsExporterParameter.IS_REMOVE_EMPTY_SPACE_BETWEEN_ROWS, Boolean.TRUE);
如何保留GridLine 首先, 設(shè)置參數(shù)exporter.setParameter(JRXlsExporterParameter.IS_WHITE_PAGE_BACKGROUND, Boolean.FALSE); 然后,把每個Field或者Static Text框的''Transparent''屬性都勾上 如何使字段名只顯示一次 如果把字段名放在ColumnHead區(qū)域, 那么輸出到Excel, 會每個Page都顯示一遍. 在設(shè)計(jì)Report時候, 一般會設(shè)定Page大小. 然而對于Excel, 這個Page設(shè)定仍然存在,而且往往很討厭, 因?yàn)樵贓xcel里, 通常希望得到連續(xù)的數(shù)據(jù), 然而Jasper仍然會''自作多情''進(jìn)行分頁. 比如說, 設(shè)計(jì)JasperReport的時候, 設(shè)定page size為Letter, Portrait, 那么輸出到Excel的時候每隔大約30行(具體取決于Field的高度), page header, column header, column foot, page foot 會被重復(fù)一次, 而且還附帶一個高度為0的Excel Row, 表示Page Break的地方. 把字段名放在title band里, 可以解決字段名重復(fù)的問題, 當(dāng)然page header也不要顯示了. 如果需要, 可以把title band的print when expression設(shè)成只有輸出Excel的時候才顯示 為什么Excel里面的數(shù)據(jù)是從第二行,第B列開始顯示的? 因?yàn)榈谝恍泻偷贏列分別是用來表示page top margin 和 page left margin的. 對于Excel來說, 純粹多余. 解決方法是把page margin 設(shè)成0. 不過如果這個report還需要以PDF等顯示, 那么設(shè)成0就不好看了. 最好能動態(tài)的改變page margin. 當(dāng)然,這個改變只能在外部(調(diào)用Report的地方) 進(jìn)行, 在設(shè)計(jì)Report的時候是無能為力的. 不幸的是, JasperReport類居然沒有setMargin的方法,只有g(shù)etter. 折中的方法只能是reflect了. 代碼示意如下: //use reflect to set the private field of JRBaseReport java.lang.reflect.Field margin = JRBaseReport.class.getDeclaredField( "leftMargin"); margin.setAccessible(true); margin.setInt(myRpt, 0); margin = JRBaseReport.class.getDeclaredField("topMargin"); margin.setAccessible(true); margin.setInt(myRpt, 0); margin = JRBaseReport.class.getDeclaredField("bottomMargin"); margin.setAccessible(true); margin.setInt(myRpt, 0); 如何去掉Excel中隱藏的行? 如前說述, 由于page break的關(guān)系, Excel中每隔幾十行,就有一個高度為0的row, 即使把page botom margin設(shè)為0, 把page footer去掉都沒有辦法. 唯一的解決辦法是把page height設(shè)為很大. 同5.5一樣, 不得不使用reflect: java.lang.reflect.Field pageHeight = JRBaseReport.class.getDeclaredField( "pageHeight"); pageHeight.setAccessible(true); pageHeight.setInt(myRpt, Integer.MAX_VALUE);
文檔 哪里有文檔? JasperReport有份Ultimate Guide, 不過不是免費(fèi)的, 和jFreeChart一個德行. 不過網(wǎng)上有流傳, 寫的還可以, 60多頁, 不過也沒詳細(xì)到哪里去. 如果下載源代碼版, 那么看看自帶的Demo也不錯. SF的論壇也是問問題的最好地方 源代碼 僅供參考(reportProvider--一個Servlet, GraphProider, JRDataAdapter都是普通類) /** *
Title: ReportProviderServlet
* Description: Servlet to generate Jasper reports
* Copyright: Copyright (c) 2004
* Company: *****
* @author zephyr * @version 1.0 */ package xyz;
import net.sf.jasperreports.engine.*; import net.sf.jasperreports.engine.base.*; import net.sf.jasperreports.engine.export.*; import net.sf.jasperreports.engine.util.*;
import org.apache.log4j.*;
import java.io.*;
import java.sql.*;
import java.util.*;
import javax.servlet.*; import javax.servlet.http.*;
public class ReportProviderServlet extends HttpServlet { private static Logger log = LogManager.getLogger(ReportProviderServlet.class);
//Initialize: Setup DataSourceManager public void init() throws javax.servlet.ServletException { String prefix = getServletContext().getRealPath("/"); String file = getInitParameter("data-source-file");
DataSourceManager.configure(prefix + file);
log.info("initialized successfully!"); }
//Process the HTTP request public void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String reportClass = request.getParameter("reportClass");
log.debug("Running Report:" + reportClass);
boolean isExcelFormat = false;
if (reportClass == null) { throw new IllegalArgumentException("Jasper Class Unspecified"); }
String reportFormat = request.getParameter("reportFormat");
if (reportFormat == null) { reportFormat = "jasperPrint"; }
try { JasperReport myRpt = JasperManager.loadReport(this.getClass() .getResourceAsStream("/jasperReports/" + reportClass + ".jasper"));
//set ReprintHeaderOnEachPage=false for Excel Format isExcelFormat = reportFormat.equalsIgnoreCase("excel");
if (isExcelFormat) { //use reflect to set the private field of JRBaseReport //No margin for excel format, max pageHeight java.lang.reflect.Field margin = JRBaseReport.class.getDeclaredField( "leftMargin"); margin.setAccessible(true); margin.setInt(myRpt, 0);
margin = JRBaseReport.class.getDeclaredField("topMargin"); margin.setAccessible(true); margin.setInt(myRpt, 0);
margin = JRBaseReport.class.getDeclaredField("bottomMargin"); margin.setAccessible(true); margin.setInt(myRpt, 0);
java.lang.reflect.Field pageHeight = JRBaseReport.class.getDeclaredField( "pageHeight"); pageHeight.setAccessible(true); pageHeight.setInt(myRpt, Integer.MAX_VALUE);
//Don't print group header on each page if (null != myRpt.getGroups()) { for (int i = 0; i < myRpt.getGroups().length; i++) { myRpt.getGroups()[i].setReprintHeaderOnEachPage(false); } } }
Map params = new HashMap(10); Enumeration enu = request.getParameterNames();
while (enu.hasMoreElements()) { String key = (String) enu.nextElement(); params.put(key, request.getParameter(key).toUpperCase().replaceAll("'", "''")); log.debug(key + "=" + request.getParameter(key)); }
log.debug("Before Filling");
OutputStream httpOut = response.getOutputStream();
Connection conn = DataSourceManager.getConnection(request.getSession());
JasperPrint rptPnt = JasperManager.fillReport(myRpt, params, conn);
conn.close();
if (reportFormat.equalsIgnoreCase("jasperPrint")) { response.setContentType("application/octet-stream"); JRSaver.saveObject(rptPnt, httpOut); } else if (reportFormat.equalsIgnoreCase("pdf")) { response.setContentType("application/pdf"); response.setHeader("Content-Disposition", "attachment;filename=\"" + reportClass + ".PDF\""); JasperManager.printReportToPdfStream(rptPnt, httpOut); } else if (reportFormat.equalsIgnoreCase("excel")) { response.setContentType("application/vnd.ms-excel"); response.setHeader("Content-Disposition", "attachment;filename=\"" + reportClass + ".XLS\"");
JRXlsExporter exporter = new JRXlsExporter();
exporter.setParameter(JRExporterParameter.JASPER_PRINT, rptPnt); exporter.setParameter(JRExporterParameter.OUTPUT_STREAM, httpOut); exporter.setParameter(JRXlsExporterParameter.IS_REMOVE_EMPTY_SPACE_BETWEEN_ROWS, Boolean.TRUE); exporter.setParameter(JRXlsExporterParameter.IS_ONE_PAGE_PER_SHEET, Boolean.FALSE); exporter.setParameter(JRXlsExporterParameter.IS_WHITE_PAGE_BACKGROUND, Boolean.FALSE); exporter.exportReport(); } else if (reportFormat.equalsIgnoreCase("html")) { JRHtmlExporter exporter = new JRHtmlExporter(); response.setContentType("text/html");
Map imagesMap = new HashMap();
request.getSession().setAttribute("IMAGES_MAP", imagesMap);
exporter.setParameter(JRHtmlExporterParameter.IMAGES_MAP, imagesMap); exporter.setParameter(JRHtmlExporterParameter.IMAGES_URI, "image.jsp?image="); exporter.setParameter(JRExporterParameter.JASPER_PRINT, rptPnt); exporter.setParameter(JRExporterParameter.OUTPUT_STREAM, httpOut);
exporter.exportReport(); }
log.debug("Report Exported"); } catch (Exception ex) { log.error("Error Occured", ex); } } }
/** *
Title: JRDataSourceAdapter
* Description: Converting JRDataSource to Mapped ArrayList
* Copyright: Copyright (c) 2004
* Company: *****
* @author zephyr * @version 1.0 */ package xyz;
import net.sf.jasperreports.engine.*; import net.sf.jasperreports.engine.design.*;
import java.util.*;
public class JRDataSourceAdapter { public static Map JRDataSource2Map(JRDataSource dataSource, String[] fieldNames, Class[] fieldClasses) throws JRException { HashMap result;
if (fieldNames.length != fieldClasses.length) { throw new JRException("Number of Field Name & Class unmatch"); }
JRDesignField[] fields = new JRDesignField[fieldNames.length];
result = new HashMap(4);
for (int i = 0; i < fieldNames.length; i++) { fields[i] = new JRDesignField(); fields[i].setName(fieldNames[i]); fields[i].setValueClass(fieldClasses[i]); result.put(fieldNames[i], new ArrayList()); }
do { for (int i = 0; i < fields.length; i++) { Object value = dataSource.getFieldValue(fields[i]); ((ArrayList) result.get(fields[i].getName())).add(value); } } while (dataSource.next());
return result; } }
/** *
Title: GraphProvider
* Description: Generate JFreeChart Image
* Copyright: Copyright (c) 2004
* Company: ****
* @author zephyr * @version 1.0 */ package xyz;
import net.sf.jasperreports.engine.*; import net.sf.jasperreports.engine.design.*; import net.sf.jasperreports.engine.export.*;
import org.jfree.chart.*; import org.jfree.chart.axis.*; import org.jfree.chart.plot.*;
import org.jfree.data.*;
import java.awt.*; import java.awt.image.*;
import java.io.*;
import java.util.*;
public class GraphProvider { public static Image getImage(Map dataSource, String fieldNameX, String fieldNameY, String chartName, String titleX, String titleY, boolean isBarChart, int imageWidth, int imageHeight) throws JRException { JRDesignField fieldX = new JRDesignField(); fieldX.setName(fieldNameX); fieldX.setValueClass(java.lang.String.class);
JRDesignField fieldY = new JRDesignField(); fieldY.setName(fieldNameY); fieldY.setValueClass(java.lang.Double.class);
ArrayList periods = (ArrayList) dataSource.get(fieldNameX); ArrayList values = (ArrayList) dataSource.get(fieldNameY);
DefaultCategoryDataset categoryDs = new DefaultCategoryDataset();
for (int i = 0; i < values.size(); i++) { Object obj = values.get(i); double dataValue = 0;
if (obj != null) { dataValue = ((Double) obj).doubleValue(); }
categoryDs.addValue(dataValue, null, (String) periods.get(i)); }
JFreeChart c = null;
if (isBarChart) { c = ChartFactory.createBarChart(chartName, titleX, titleY, categoryDs, PlotOrientation.VERTICAL, false, false, false); } else { c = ChartFactory.createLineChart(chartName, titleX, titleY, categoryDs, PlotOrientation.VERTICAL, false, false, false); }
c.getTitle().setFont(new Font("Arial", Font.BOLD, 16));
NumberAxis axis = (NumberAxis) c.getCategoryPlot().getRangeAxis(); axis.setAutoRange(true);
TickUnitSource tickUnits = NumberAxis.createIntegerTickUnits(); axis.setStandardTickUnits(tickUnits);
return (c.createBufferedImage(imageWidth, imageHeight));
} }

Feedback
# re: JasperReport經(jīng)驗(yàn)談 回復(fù) 更多評論
2006-03-20 17:51 by
請問您是怎么生成csv格式的文件的,我這邊每次生成csv格式的報(bào)表,里面的中文就會顯示???,奇怪了,謝謝您能給幫忙看看
# re: JasperReport經(jīng)驗(yàn)談 回復(fù) 更多評論
2006-08-23 16:12 by
回:“請問您是怎么生成csv格式的文件的,。。。”
缺少中文包的緣故。
1,下載iTextAsian.jar包,放在lib下。
2,在代碼中加入以下語句:
exporter.setParameter(JRExporterParameter.CHARACTER_ENCODING, "GBK");
3,設(shè)計(jì)時,在IReport中,設(shè)置字段控件的字體為宋體.
# re: JasperReport經(jīng)驗(yàn)談 回復(fù) 更多評論
2006-08-23 16:15 by
不同格式報(bào)表文件的輸出問題也困擾我好久,終于解決。爽啊!!!!
# re: JasperReport經(jīng)驗(yàn)談 回復(fù) 更多評論
2006-08-24 11:33 by
請教一個問題,我做的是日文的報(bào)表 在生成CSV格式的報(bào)表時,我沒有拿到程序里看,而是直接運(yùn)行后看的 怎么日文字都顯示為?????以前PDF時我設(shè)PDF
FONTNAME 和PDF Enconding 就能正確顯示日文字體 為什么 在CSV時就不能顯示了呢?
比較急 勞駕您了!
|