幫助你書寫軟件報告之JasperReports 開發技巧

JasperReports 是一個基於Java的開放性報告工具,它可以在Java軟件環境中創建簡單報告。要使用JasperReports, 你需要到SourceForge下載一個JAR文件或源代碼。報告結構本身非常基本,並且只有商業產品的一小部分功能。雖然它不像其相應的商業產品那樣完 美無暇,但這些基本功能已可滿足建立自述性報告的需要。

要建立報告,首先你必須非常瞭解XML,因爲它需要大量的手寫譯碼和編輯。或者你也可以到JasperReports下載一個開放資源的報告設計軟件。

JasperReports的模板過於簡單,所以你不要指望它能夠設計出複雜的報告。你可以將其與其它工具結合使用來建立複雜一點的報告,但那是非常困難的(即使是前文所提到的那些設計軟件)。

 JasperReports支持PDF、HTML、XLS、CSV和XML文件輸出格式。JasperReports與其它軟件的連結非常簡便有效,並且大多數的Java開發者能夠快速的掌握預覽、打印和輸出報告的技術。


有些報表可能需要對同種類型的數

開發技巧

1.      Dynamic Element Formatting
有些報表可能需要對同種類型的數據根據重要性不同而顯示不同的風格。例如,要在訂單列表中加亮顯示金額超過100$的訂單,就像下面的表格中顯示的一樣:
OrderID
City
Date
Value
10251
Buenos Aires
07/15/2001
98.64
10263
Paris
09/19/2001
106.75
11320
Caracas
10/19/2001
88.10
而 文本域的現實格式不能動態改變,我們如何實現這種功能呢?解決辦法是:在同樣的位置放兩個文本域來顯示相同的內容,但是設置不同的顯示格式,一個正常顯 示,一個加粗並加亮顯示紅色,同時還要爲他們設定<printWhenExpression>元素,讓他們可以根據條件表達式來切換。這裏的 表達式就是$F{value} < 100(正常顯示的文本域)和$F{value} >= 100(加亮顯示的文本域)。

2.       i/n頁(Page i of n)的實現

很多時候我們都需要在報表的開頭就顯示哪些需要計算完整個報表才能得到的值,就像這種頁碼形式每一頁都要顯示,但總頁數卻要在整個文檔計算完後才能得到。而引擎是一頁一頁的填充報表的,總頁數只能在到達報表結束時才能計算出來,怎麼實現這種形式的頁碼呢?

非常簡單。報表引擎允許我們指定文本域表達式的準確執行時機,要顯示總頁數,我們只需要在頁面上放置一個使用PAGE_NUMBER報表變量的文本域,並指定該變量的計算時機爲"Report",這樣報表會在整個報表結束時在執行該表達式,而這時PAGE_NUMBER才表示整個報表的總頁數。

在報表設計文檔中實現該功能如下:

<textField evaluationTime="Now">
  <reportElement x="280" y="10" width="300" height="20"/>
  <textElement textAlignment="Left">
    <font fontName="Helvetica" size="14"/>
  </textElement>
  <textFieldExpression class="java.lang.Integer">
    " " + $V{PAGE_NUMBER} + " / "
  </textFieldExpression>
</textField>
<textField evaluationTime="Report">
  <reportElement x="580" y="10" width="275" height="20"/>
  <textElement textAlignment="Left">
    <font fontName="Helvetica" size="14"/>
  </textElement>
  <textFieldExpression class="java.lang.Integer">
    $V{PAGE_NUMBER} + ""
  </textFieldExpression>
</textField>

第一個<textField>evaluationTime"Now",也是該屬性的默認值,表示在報表填充當前區域或列(column)時就處理該表達式,這得到的是當前也的值。要同時顯示當前頁和總頁數,第二個<textField>就必須將evaluationTime設置成"Report",這樣該表達式只有在報表結束時才處理。

evaluationTime屬性有五個可選值:NowReportPageColumnGroup。具體含義輕參考速查手冊

3.       區域內容大於一頁

有時一個特定的區域內容可能大於一頁,我們必須在區域內部結構中找到一個合適的位置來分頁。當然這種問題並不設計報表所有的區域類型,我覺得頁或者列的頭尾區域應該不會大於一頁,但是誰知道呢。

如果標題區域和summary區域大於一頁,你應該將該區域內容設置放到子報表中,並按下面將要介紹的技巧處理該子報表。對於頁頭或列頭也用同樣的方法,,但是對於頁尾和列尾就不行了,因爲引擎用固定高度來處理它們,對於它們的元素則不允許拉伸。

經過上面的排除法,剩下的就只有組頭組尾和數據區域了。組頭和組尾可以相同對待,我們先從數據區域入手。

如果我們有一個數據區域包含太多的元素使得我們必須分成多頁來顯示,我們就需要找到一個合適的方法來分頁,但是報表引擎沒有提供"分頁符"這樣的元素,怎麼辦呢?

報表引擎只爲<group>元素提供了isStartNewPage屬性來明確設定分頁位置。也許我們可以用它來解決這些問題?

通常,報表引擎在被填充區域無法擠在剩餘的頁空間裏時會創建一個新頁或列,這也是我們要在報表設計被編譯時確認區域是否可以在頁內完全顯示原因,以使區域不會再比頁更高。沒有這樣的限制,報表引擎將會很困惑(confused)併發生控制之外的動作。因此,爲了解決這個問題,我們首先要找到一個如何通過該區域高度檢查的方法。

我 們可以通過將數據區域的內容分割到多個區域中,使每個區域的高度都小於頁高,這樣報表設計就合法了。用什麼樣的區域來分割呢?當然是組頭和組尾區域了。我 們可以將數據區域的一些元素放置在特定分組的組頭區域,另一些元素仍然放置在數據區域,剩下的元素可以放置在組尾區域。這樣處理唯一的條件是組頭尾區域必 須總是伴隨着數據區域一起顯示,三個區域的行爲看起來像一個普通的大數據區域。這是最簡單的劃分方法,因爲我們可以創建一個"無用"分組把數據源中的每條 數據分爲一組,這樣的分組表達式可以像下面這樣定義:

<groupExpression>$V{REPORT_COUNT}</groupExpression>

如果你有一個巨大的數據區域,一個無用分組沒法滿足你的要求,你可以加入任意多個你認爲需要的無用分組,所有的分組表達式都是一樣的"無用"表達式。

問題解決了。

噢, 差點忘了當組頭和組尾區域大於一頁是的情況!沒問題,同樣的方法就可以了。你只需要創建一個新的分組,使用和需要分割分組同樣的分組表達式,並將原來的組 頭和組尾元素分割放置到新分組的組頭和組尾中,這兩個分組會用同樣的組邊界就像一個分組一樣,所以分組元素可以任意放置在兩個分組的組頭和組尾中。

這樣區域高度檢查也可以通過了。

4.       創建HTMLXLSCSV格式有好的報表。

創建報表時,引擎使用報表元素的絕對位置來排列每一頁的內容,絕對位置排列報表元素可以對輸出文檔的內容進行完全控制。PDF格式支持在一個文檔頁上按絕對位置排列文本和圖形,這也是它被廣泛應用於各種平臺的原因。你可以完全相信,一個PDF文檔一經創建,不管用什麼平臺的瀏覽工具都將得到同樣的顯示結果。報表引擎使用的文檔格式(net.sf.jasperreports.engine.JasperPrint對象)也使用絕對位置排列頁面元素。每個元素的絕對位置和大小通過x, y, widthheight屬性來確定。

然而,其他其他文檔格式如HTMLXLS等並不支持按照絕對位置來排列文本和圖形元素,這些文檔的內容都被安排在表格(grid or table)結構中。當然,有人也許會說HTML元素的絕對位置排列可以藉助CSS來實現,但是CSS的標準功能定義在各種瀏覽器之間差異太大,這樣同樣的HTML文檔就不能在任何場合有同樣的顯示結果了。

因此報表引擎內建的用來產生HTMLXLS和、CSV文檔的導出工具使用了特殊的算法來來排列文檔中的元素到特定的表格。當報表設計非常複雜或使用了很大的模塊,從絕對位置排列到表格的轉換會產生帶有很多無用行和列的複雜表格,這些行和列都是爲了填充元素之間的空白或者爲了完成元素的對齊格式而加入的。

這裏有幾個非常簡單的原則來指導大家用導出工具得到最優化的HTMLXLSCSV文檔。

A. 最小化表格中的行和列的數量(最小化切割)。

你必須最大限度的將你的元素放在座標軸上(表格線上),併除去元素之間的空白。如下兩圖所比較:


a) 低劣的格式


b) 表格有好的格式

B. 避免重疊報表元素

在報表產生是確認沒有元素互相重疊。因爲如果有兩個元素有一部分重疊,而在結果表格結構中它們卻沒有辦法公用一個單元格,這樣將會得到無法預料的結果。

C. 放棄使用頁頭和頁尾

尤其是當你想得到單頁文檔時,確認你禁用了頁頭和頁尾。你也可以最小化頁面頂端和底部的空白,以便在導出HTMLXLSCSV文檔時,你的文檔可以被顯示在一個沒有分頁符的單獨區塊中。

5.       Excel調色板

報表引擎允許你爲報表元素設定任意的顏色。然而對於導出到XLS格式的報表,你必須知道XLS文件只支持一個有限的顏色集合。下面是一個Excel的調色板,列出了40XLS文件可以使用的顏色。如果你最終要將報表導出成XLS文件,確定你只使用了這些顏色:

   
 
#FFFF00 YELLOW
 
#969696 GREY_40_PERCENT
 
#99CC00 LIME
 
#CC99FF LAVENDER
 
#FFFF99 LIGHT_YELLOW
 
#008000 GREEN
 
#C0C0C0 GREY_25_PERCENT
 
#339966 SEA_GREEN
 
#FF9900 LIGHT_ORANGE
 
#FF0000 RED
 
#003366 DARK_TEAL
 
#FFFFFF WHITE
 
#99CCFF PALE_BLUE
 
#00FFFF TURQUOISE
 
#008080 TEAL
 
#000080 DARK_BLUE
 
#FFCC00 GOLD
 
#33CCCC AQUA
 
#333300 OLIVE_GREEN
 
#808080 GREY_50_PERCENT

 
 
 
#003300 DARK_GREEN
 
#808000 DARK_YELLOW
 
#FF00FF PINK
 
#FF99CC ROSE
 
#3366FF LIGHT_BLUE
 
#FF6600 ORANGE
 
#993300 BROWN
 
#993366 PLUM
 
#800080 VIOLET
 
#333399 INDIGO
 
#000000 BLACK
 
#00CCFF SKY_BLUE
 
#333333 GREY_80_PERCENT
 
#CCFFFF LIGHT_TURQUOISE
 
#CCFFCC LIGHT_GREEN
 
#666699 BLUE_GREY
 
#0000FF BLUE
 
#00FF00 BRIGHT_GREEN
 
#800000 DARK_RED
 
#FFCC99 TAN

如果你的報表設計中使用了這些顏色之外的顏色,報表引擎將會採用特殊的算法找到一個RGB值最接近的顏色來替換,但是替換的結果往往會出乎預料:)

6.       子報表的返回值

你可以通過使用特殊參數當作容器從子報表返回值。這裏有一個例子:

問題:我想從在主報表中得到子報表中的記錄總數。

解決辦法:

在主報表中,定義一個特殊的容器參數,用它來存放將從子報表返回的總記錄數:

<parameter name="ReturnedValuesMap" class="java.util.Map">

  <defaultValueExpression>

    new java.util.HashMap()

  </defaultValueExpression>

</parameter>

將該參數傳給子報表,以使它可以被子報表用來存放我們想要的值:

<subreportParameter name="ReturnedValuesMap">

  <subreportParameterExpression>

    $P{ReturnedValuesMap}

  </subreportParameterExpression>

</subreportParameter>

在子報表模板中,聲明該容器參數。雖然它們使用的名字相同,主報表的參數和子報表參數是完全不同的實體。

<parameter name="ReturnedValuesMap" class="java.util.Map"/>

在子報表中,爲summary區域的一個不可見直線元素添加一個無效的<printWhenExpression>表達式來將我們需要返回的值放到容器中。

<line>

  <reportElement x="0" y="0" width="0" height="0">

    <printWhenExpression>

      ($P{ReturnedValuesMap}.put(

        "MY_RETURNED_VALUE", $V{REPORT_COUNT}) == null

        )?Boolean.FALSE:Boolean.FALSE

    </printWhenExpression>

  </reportElement>

</line>

當然這個功能也可以在scriptlet來實現,之所以用上面的方法只是因爲簡單。

返回到主報表,如果要顯示該返回值,只需要從從其參數中取出來:

<textField evaluationTime="Group" evaluationGroup="DetailGroup">

  <reportElement x="335" y="50" width="175" height="15"/>

  <textFieldExpression class="java.lang.Integer">

    $P{ReturnedValuesMap}.get("MY_RETURNED_VALUE")

  </textFieldExpression>

</textField>

你也許會問爲什麼文本域使用了evaluationTime="group"。這是因爲子報表的返回值有一個問題:它們是在當前區域的所有元素都被處理之後才返回的。如果你想在子報表所在的主報表區域顯示它的返回值,你最終將看到它們返回的太晚了。

所以,爲了能在主報表中的子報表所在的同一區域顯示返回值,我們必須延遲文本域表達式的處理時機。這是通過加入一個無效的分組讓表達式在分組結束時處理來實現的,而這個無效分組的效果就是數據源中的每條記錄就是一組,而且它自己沒有組頭和組尾區域。

<group name="DetailGroup">

  <groupExpression>$V{REPORT_COUNT}</groupExpression>

</group>

這個無效分組達到了我們的目的,因爲我們的子報表和文本區域都放置在主報表的數據區域,並且它對每條記錄分組。如果子報表放置在不同的報表區域中,這個無效分組需要根據實際情況調整。如果你不需要在子報表所在區域顯示返回值,就不需要這樣的無效分組。

7.       僞造標題和summary區域

標題和summary區域時報表的特殊區域,它們並不跟隨它們所在頁的頁頭和頁尾,當它們超出當前頁時尤其明顯。

很多時候,我們需要讓標題區域在第一頁緊跟在頁頭區域之後,讓summary區域在每一頁跟隨這頁頭和頁尾。

我們可以通過創建僞造的標題和summary區域來實現。僞造標題可以是一個組頭區域,這是一個將整個報表看成一個組的無效分組,它有一個空分組表達式。組頭只在報表開始時打印一次。僞造summary區域同樣使用這種無效分組的組尾區域。

- 作者: iceshape 2004年11月6日, 星期六 20:55 加入博採

Trackback

你可以使用這個鏈接引用該篇文章 http://publishblog.blogchina.com/blog/tb.b?diaryID=210802

如果你的報表設計中使用了這些顏色之外的顏色,報表引擎將會採用特殊的算法找到一個RGB值最接近的顏色來替換,但是替換的結果往往會出乎預料:)

6.       子報表的返回值

你可以通過使用特殊參數當作容器從子報表返回值。這裏有一個例子:

問題:我想從在主報表中得到子報表中的記錄總數。

解決辦法:

在主報表中,定義一個特殊的容器參數,用它來存放將從子報表返回的總記錄數:

<parameter name="ReturnedValuesMap" class="java.util.Map">

  <defaultValueExpression>

    new java.util.HashMap()

  </defaultValueExpression>

</parameter>

將該參數傳給子報表,以使它可以被子報表用來存放我們想要的值:

<subreportParameter name="ReturnedValuesMap">

  <subreportParameterExpression>

    $P{ReturnedValuesMap}

  </subreportParameterExpression>

</subreportParameter>

在子報表模板中,聲明該容器參數。雖然它們使用的名字相同,主報表的參數和子報表參數是完全不同的實體。

<parameter name="ReturnedValuesMap" class="java.util.Map"/>

在子報表中,爲summary區域的一個不可見直線元素添加一個無效的<printWhenExpression>表達式來將我們需要返回的值放到容器中。

<line>

  <reportElement x="0" y="0" width="0" height="0">

    <printWhenExpression>

      ($P{ReturnedValuesMap}.put(

        "MY_RETURNED_VALUE", $V{REPORT_COUNT}) == null

        )?Boolean.FALSE:Boolean.FALSE

    </printWhenExpression>

  </reportElement>

</line>

當然這個功能也可以在scriptlet來實現,之所以用上面的方法只是因爲簡單。

返回到主報表,如果要顯示該返回值,只需要從從其參數中取出來:

<textField evaluationTime="Group" evaluationGroup="DetailGroup">

  <reportElement x="335" y="50" width="175" height="15"/>

  <textFieldExpression class="java.lang.Integer">

    $P{ReturnedValuesMap}.get("MY_RETURNED_VALUE")

  </textFieldExpression>

</textField>

你也許會問爲什麼文本域使用了evaluationTime="group"。這是因爲子報表的返回值有一個問題:它們是在當前區域的所有元素都被處理之後才返回的。如果你想在子報表所在的主報表區域顯示它的返回值,你最終將看到它們返回的太晚了。

所以,爲了能在主報表中的子報表所在的同一區域顯示返回值,我們必須延遲文本域表達式的處理時機。這是通過加入一個無效的分組讓表達式在分組結束時處理來實現的,而這個無效分組的效果就是數據源中的每條記錄就是一組,而且它自己沒有組頭和組尾區域。

<group name="DetailGroup">

  <groupExpression>$V{REPORT_COUNT}</groupExpression>

</group>

這個無效分組達到了我們的目的,因爲我們的子報表和文本區域都放置在主報表的數據區域,並且它對每條記錄分組。如果子報表放置在不同的報表區域中,這個無效分組需要根據實際情況調整。如果你不需要在子報表所在區域顯示返回值,就不需要這樣的無效分組。

7.       僞造標題和summary區域

標題和summary區域時報表的特殊區域,它們並不跟隨它們所在頁的頁頭和頁尾,當它們超出當前頁時尤其明顯。

很多時候,我們需要讓標題區域在第一頁緊跟在頁頭區域之後,讓summary區域在每一頁跟隨這頁頭和頁尾。

我們可以通過創建僞造的標題和summary區域來實現。僞造標題可以是一個組頭區域,這是一個將整個報表看成一個組的無效分組,它有一個空分組表達式。組頭只在報表開始時打印一次。僞造summary區域同樣使用這種無效分組的組尾區域。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章