前言:
jasperReport(4.5最新版,下面用ireport代替)大家都很熟悉,但是打印用到j(luò)atoolsPrinter的,應(yīng)該不多吧、這里介紹下struts2的ireport插件,以及jatoolsPrinter打印。
1、首先得準(zhǔn)備支持ireport的幾個常用包,根據(jù)ireport版本的不同,包的新舊也不同,我用的ireport最新版的,所以用到的包也得是最新的,下面還會列出我遇到的包的問題;
a>groovy-all-1.7.5.jar
b>iText-2.1.0.jar[生成pdf文檔用]
c>iTextAsian.jar[語言支持]
d>jasperreport_4.5.0.jar[必備包]
e>poi-3.7.jar[導(dǎo)出excel時候用]
f>commons-collections-3.2.jar
g>commons-beanutils-1.7.1.jar
h>commons-digester-1.7.jar
i>org-netbeans-core.jar[可有可無,當(dāng)你對報表進行樣式處理時候(styles)會用到,比如:奇偶換色等]
列舉幾個包的問題:
Ⅰ、導(dǎo)出excel時候報錯:
org.apache.jasper.JasperException: javax.servlet.ServletException: java.lang.NoSuchMethodError: org.apache.poi.hssf.usermodel.HSSFSheet.setRightToLeft(Z)V
解決方法:你使用的poi-xxx.jar版本過低,換個高版本的jar包即可。
Ⅱ、生成PDF格式文檔時候報錯:
java.lang.NoSuchMethodError: com.lowagie.text.pdf.PdfWriter.setRgbTransparencyBlending(Z)V
解決方法:iText.jar文件過舊,將之改為2.1.X就可以了。
Ⅲ、使用ireport時候報錯:
java.lang.NoClassDefFoundError: org/codehaus/groovy/control/CompilationFailedException
解決方法:添加groovy-all-1.7.5.jar。ps:像這樣的錯誤(java.lang.NoClassDefFoundError)最好解決,一看就是少了Jar包,找到添加上就OK了。
Ⅳ、使用ireport時候報錯:
net.sf.jasperreports.engine.JRRuntimeException: Unknown hyperlink target 0
解決方法:版本不搭配的問題,可能會是當(dāng)前使用的iRrport 的版本高于操作時的包的版本。
Ⅴ、在使用PDF格式預(yù)覽的時候報錯:
net.sf.jasperreports.engine.JRRuntimeException: Could not load the following font :
pdfFontName : STSong-Light
pdfEncoding : UniGB-UCS2-H
isPdfEmbedded : false
解決辦法:添加語言包支持,iTextAsian.jar。ps:這里需要注意,有中文字的field,如果想在PDF格式文檔中正常顯示,一定需要設(shè)置其兩個屬性pdfFontName和pdfEncoding為STSong-Light,UniGB-UCS2-H;否則不顯示。
2、和struts2結(jié)合,肯定得需要一個插件包struts2-jasperreports-plugin-2.0.11.2.jar,這里需要注意它里面的struts-plugin.xml文件中的包并沒有繼承自struts-default,需要把它拿出來改一下
- <package name="jasperreports-default" extends="struts-default">
<package name="jasperreports-default" extends="struts-default">
定義Result類型為jasper時,需要指定三個參數(shù):
1、location:指定.jasper文件的位置.
2、format:指定生成的報表文件的格式,如果不指定,默認(rèn)生成PDF文件(XLS:excel格式,HTML:html格式,CSV,XML,。。。).
3、dataSource:指定一個集合屬性名,JasperReports報表將自動迭代輸出該集合中的數(shù)據(jù)。
項目中,我對該插件進行了改進和升級。如果按照原插件的原理,我每次都需要查詢得到一個結(jié)果集,傳遞給ireport進行顯示。既然ireport設(shè)計器可以直接連接JDBC數(shù)據(jù)源查詢數(shù)據(jù),我們何不把查詢結(jié)果集的事情也交給ireport來做呢。
于是修改插件源代碼,思路如下:
1、首先需要給ireport提供一個數(shù)據(jù)源(c3p0或者dbcp),使其能夠進行數(shù)據(jù)庫操作【屬性名:connection】。
2、再提供一個Map格式數(shù)據(jù),向ireport傳遞參數(shù)【屬性名:reportParameters】。
3、還有兩個參數(shù)location和format,以及dataSource。
核心代碼修改如下:
-
- protected String reportParameters;
- protected String connection;
-
- ValueStack stack = invocation.getStack();
-
- Connection conn = (Connection) stack.findValue(connection);
- if (conn == null){
- Object obj = stack.findValue(dataSource);
- if(obj instanceof JRDataSource) {
- jrDataSource = (JRDataSource) obj;
- }else {
- jrDataSource = new ValueStackDataSource(stack, dataSource);
- }
- }
- ...
- ServletContext servletContext = (ServletContext) invocation.getInvocationContext().get(ServletActionContext.SERVLET_CONTEXT);
- String systemId = servletContext.getRealPath(finalLocation);
- File directory = new File(systemId.substring(0, systemId.lastIndexOf(File.separator)));
- Map parameters = new ValueStackShadowMap(stack);
- parameters.put("reportDirectory", directory);
- parameters.put(JRParameter.REPORT_LOCALE, invocation.getInvocationContext().getLocale());
-
- Map reportParams = (Map) stack.findValue(reportParameters);
- if (reportParams != null) {
- parameters.putAll(reportParams);
- }
- .....
- if (conn == null){
- jasperPrint = JasperFillManager.fillReport(jasperReport, parameters, jrDataSource);
- }else{
- jasperPrint = JasperFillManager.fillReport(jasperReport, parameters, conn);
- }
-
- 然后寫一個公用的Action,代碼如下:
-
-
-
-
- @Component("jasperReportAction")
- @Scope("prototype")
- public class JasperReportAction extends ActionSupport {
-
- private List<?> list;
- private Connection connection;
- @Resource(name="dataSource")
- private PooledDataSource dataSource;
- private Map<String,?> para;
-
- public String execute() throws Exception {
- this.connection = dataSource.getConnection();
- if(connection != null ){
- this.list = null ;
- }
- return SUCCESS;
- }
- setter和getter....
- }
//定義兩個變量,傳遞參數(shù)和數(shù)據(jù)源連接。protected String reportParameters;protected String connection;ValueStack stack = invocation.getStack();//先從stack獲取數(shù)據(jù)源,如果為null,判斷其是否為JRDataSource,若是轉(zhuǎn)換成JRDataSource,否則,再采用ValueStackDataSource方式賦值給JRDataSource接口Connection conn = (Connection) stack.findValue(connection); //獲取提供的數(shù)據(jù)源if (conn == null){Object obj = stack.findValue(dataSource);if(obj instanceof JRDataSource) { jrDataSource = (JRDataSource) obj;}else { jrDataSource = new ValueStackDataSource(stack, dataSource);}}...ServletContext servletContext = (ServletContext) invocation.getInvocationContext().get(ServletActionContext.SERVLET_CONTEXT);String systemId = servletContext.getRealPath(finalLocation);File directory = new File(systemId.substring(0, systemId.lastIndexOf(File.separator)));Map parameters = new ValueStackShadowMap(stack);parameters.put("reportDirectory", directory);parameters.put(JRParameter.REPORT_LOCALE, invocation.getInvocationContext().getLocale());//獲取向ireport傳遞的參數(shù)Map reportParams = (Map) stack.findValue(reportParameters);if (reportParams != null) { parameters.putAll(reportParams);}.....if (conn == null){ jasperPrint = JasperFillManager.fillReport(jasperReport, parameters, jrDataSource);}else{ jasperPrint = JasperFillManager.fillReport(jasperReport, parameters, conn);}然后寫一個公用的Action,代碼如下:/** * @author aokunsang * @date 2012-1-20 */@Component("jasperReportAction")@Scope("prototype")public class JasperReportAction extends ActionSupport { private List<?> list; //相當(dāng)于dataSource,集合屬性,查詢的結(jié)果集[如果想使用list,則connection一定為null,否則沒用] private Connection connection; //數(shù)據(jù)源連接 @Resource(name="dataSource") private PooledDataSource dataSource; //數(shù)據(jù)源 private Map<String,?> para; //傳遞的參數(shù) public String execute() throws Exception { this.connection = dataSource.getConnection(); if(connection != null ){ this.list = null ; } return SUCCESS; } setter和getter....}
struts.xml配置如下:
首先要把插件配置進去,你可以寫個struts-plugin.xml,也可以直接寫到struts.xml中(因為我使用的注解,struts.xml配置文件較少,直接寫進去了)。
- <package name="aa" extends="struts-default">
-
- <result-types>
- <result-type name="jasper" class="xxx.report.JasperReportsResult"/>
- </result-types>
-
- <action name="JR_*_*" class="jasperReportAction">
- <result name="success" type="jasper">
- <param name="location">/reports/{1}.jasper</param>
- <param name="connection">connection</param>
- <param name="format">{2}</param>
- <param name="reportParameters">para</param>
-
- </result>
- </action>
- lt;/package>
<package name="aa" extends="struts-default"> <!-- 設(shè)置JasperReport的type類型 --> <result-types> <result-type name="jasper" class="xxx.report.JasperReportsResult"/> </result-types> <!-- 報表輸出Action --> <action name="JR_*_*" class="jasperReportAction"> <result name="success" type="jasper"> <param name="location">/reports/{1}.jasper</param> <param name="connection">connection</param> <param name="format">{2}</param> <param name="reportParameters">para</param> <!--<param name="dataSource">list</param>--> <!-- 注意:connection和dataSource不能共存。兩個共存時,優(yōu)先級是connection高。 --> </result> </action></package>
Okay,到這里ireport和struts2的結(jié)合就完成了,其難點就在于插件中對數(shù)據(jù)源的修改,接下來說說jatoolsPrinter打印。
3、目前常用的web打印應(yīng)該是applet了吧,不過它需要在IE上安裝幾十兆的jre,我是很不喜歡,我想你的客戶應(yīng)該也不喜歡這個東西吧。
既然說到打印,肯定說的就是套打,簡單打印自不必多說,頁面打印就ok??纯磜eb套打的解決方案:
一、瀏覽器的打印功能菜單
這種方案的優(yōu)勢是不需要對瀏覽器作任何擴充,是最簡單的辦法,但問題也最多,如:
1、不能精確分頁。瀏覽器一般是根據(jù)用戶設(shè)置的頁面大小,web頁面的內(nèi)容多少,來自行決定分頁位置,程序員很難控制。會有頁腳頁眉干擾。
2、不能準(zhǔn)確對齊邊邊距及打印文字。
3、不能解決連續(xù)打印。比如,不是僅打印一張票據(jù),而是連續(xù)一次打印若干個票據(jù)。
二、使用webbrowser控件+ javascript
這實際上,是瀏覽器打印功能菜單的一種程序調(diào)用,與打印功能菜單沒什么兩樣。分頁的問題仍然存在,只不過,可以讓用戶不用去點菜單,直接在網(wǎng)頁中的一個按鈕,或一個鏈接里面調(diào)用罷了。
三 、使用print css
這是一種最理想的實現(xiàn)web套打的方法。這種方法通過在html文檔中,嵌入打印相關(guān)的css樣式,來實現(xiàn)對html文檔輸出打印的控制,比如設(shè)置紙張大小,紙張縱橫方向,打印邊距,分頁等。顯而易見,這種方式成本小,不需要下載任何插件,而且跨平臺性非常好。print css推出已經(jīng)有些時日,但遺憾的是,至今沒有一個廠商的瀏覽器很好地實現(xiàn)了這些標(biāo)準(zhǔn),這使得程序員目前還不能利用print css進行實際的開發(fā)。關(guān)于打印css,參見http://css-discuss.incutio.com/?page=PrintStylesheets
四 、使用pdf文件
用這種方式,就是從服務(wù)器端下載一個pdf文件流,在IE中用adobe插件打開,然后用adobe的打印菜單進行打印,雖然這種方案,也能實現(xiàn)精確套打,但需要下載adobe插件。這是國外報表工具經(jīng)常推薦的一種打印方法,但在pdf不那么普及的中國,這種方案不是最好選擇。
五 、采用純ActiveX
這種方案就是下載一個控件,票據(jù)的數(shù)據(jù)不再以html方式呈現(xiàn),而是呈現(xiàn)在ActiveX中。這種方案的優(yōu)點是打印的精確度高,分頁的可控性好,但缺點也是很明顯的,嵌入ActiveX控件破壞了web應(yīng)用的整體html風(fēng)格,且這樣的控件比較大(一般超過1M),下載頗費時間)。市場上的非java類報表產(chǎn)品,一般都采用這種方案。
六 、采用Applet方式
采用Applet方式,分頁或精確打印,都可以做到完美,但缺點也很明顯,表現(xiàn)在:
1、安裝Applet成本巨大。需要下載十幾M的文件。
2、打印報表時,需要重新向服務(wù)器檢索數(shù)據(jù),效率低。因為Applet方案,一般采用html方式呈現(xiàn)數(shù)據(jù),打印時Applet必須向服務(wù)器檢索同一張票據(jù)的數(shù)據(jù),看上去,是打印了當(dāng)前頁的票據(jù),實際上,Applet根本不會用當(dāng)前html頁的數(shù)據(jù)來打印,而是向服務(wù)器下載數(shù)據(jù)到Applet中來打印。也就是說,打印的話,必須兩次請求,一次html呈現(xiàn),一次用來打印。
3、不能解決連續(xù)打印。比如,不是僅打印一張票據(jù),而是連續(xù)一次打印若干個票據(jù)。
4、不能解決打印機設(shè)置問題,如存在多臺打印機時,總是需要手工選擇,打印到哪一臺打印機。
5、市場上java類的報表工具,一般推薦Applet方式來實現(xiàn)打印。
七 、輕量級的ActiveX打印方式---jatoolsPrinter
這是杰創(chuàng)軟件最新推出的一種最具創(chuàng)意的web套打解決方案。杰表作為一款純java的報表工具,以前提供的web套打方案也是基于Applet的,Applet也做到了盡可能的小(只有24kb),但用戶還是抱怨,因為jre太大,安裝需要耐心。另外,用Applet方式,很難實現(xiàn)客戶端的批量打印功能。
采用jatoolsPrinter,其優(yōu)點是:
1、安裝方便,只有90k。
2、可以通過web腳本來選擇輸出打印機,紙張大小,打印方向等,不必每次手工設(shè)定。
3、支持附件打印問題。
4、說了那么多都是些廢話,最重要的是怎么用它才是真的。ps:需要注意的是,jatoolsPrinter目前是收費的。但是本人找到個破解版的,附件中大家共享下,希望杰創(chuàng)軟件別給封殺了,哈哈。
提供兩個博客大家可以借鑒學(xué)習(xí),很簡單,我這里就不多數(shù)了,說一下自己使用時候遇到的小問題吧、
重點說一下:上面博客寫的是挺好的,但是有點問題,預(yù)覽時候調(diào)用方法為: jatoolsPrinter.printPreview(myDoc); // 打印預(yù)覽 只有一個參數(shù)
<a>、首先需要在打印頁面(下面用index.jsp代替)的head中添加打印控件。<OBJECT ID="jatoolsPrinter" CLASSID="CLSID:B43D3361-D975-4BE2-87FE-057188254255" codebase="jatoolsP.cab#version=1,2,0,2"></OBJECT>
<b>、它需要一個jatoolsP.cab文件[我提供的這個是破解版的,呵呵],需要放在打印頁面同一目錄中(當(dāng)然,你看到有個屬性codebase,可以設(shè)置其路徑位置)。
<c>、在使用ireport的時候,你可能會使用iframe(比如Id和name都是:iframe_ireport)來顯示你的報表數(shù)據(jù),這時需要設(shè)置參數(shù)documents:window.frames['iframe_report'].document,具體參數(shù)設(shè)置如下(還有更多參數(shù)設(shè)置,我提供一個文檔在附近,大家自己學(xué)習(xí)):
var myDoc = {
//settings:{
//pageWidth: 2100, //自定義紙張寬度(單位為十分之一毫米)
//pageHeight: 1400, //自定義紙張高度(單位為十分之一毫米)
//orientation: 1 //打印方向 [1/2 = 縱向/橫向]
// },
documents:window.frames['iframe_report'].document,
enableScreenOnlyClass:true, // 使所有使用 screen-only 樣式類的對象,只在預(yù)覽、顯示時可見,打印時隱藏
marginIgnored: true,
copyrights:'杰創(chuàng)軟件擁有版權(quán) www.jatools.com'
};
如果你使用的是div來顯示,那就直接用documents:document
<d>、在上面的參數(shù)設(shè)置中,你會發(fā)現(xiàn)有個copyrights,這個是版權(quán)所有說明,讓我蛋疼的是必須得設(shè)置成他們公司,其他就會報錯,惡心人的很。
<e>、還有一個重頭戲就是ireport和jatoolsPrinter的分頁打印問題,上面博客中以及說的很明白了,我就不多說了,我只把修改后打包的jar文件放出來,大家下載使用吧。
<f>、接下來就是打印的三種方式:
jatoolsPrinter.printPreview(myDoc); // 打印預(yù)覽
jatoolsPrinter.print(myDoc,true); // 打印前彈出打印設(shè)置對話框
jatoolsPrinter.print(myDoc,false); // 不彈出對話框打印