如何在JRXML端获取JasperReports合并报表的总页数?
问题回顾
你现在面临的场景是:后端传入List<List<?>>类型的Bean集合,每个子列表对应一个独立的Jasper报表,最终合并成一份报表后,每个子报表的页码都从1开始计数,而且无法获取全局总页数——每个子报表都显示自己的页码范围,而你需要的是跨所有子报表的连续页码和统一总页数。另外由于Jasper打印逻辑不在你的权限范围内,没法从后端计算页数,想知道JRXML端能不能解决这个问题。
先看你当前使用的页码文本框代码:
<textField evaluationTime="Master"> <reportElement x="660" y="14" width="58" height="14" forecolor="#1A75B4" uuid="24876562-c6ab-424d-9ac6-769ef9b54079"> <property name="com.jaspersoft.studio.unit.width" value="pixel"/> <property name="com.jaspersoft.studio.unit.height" value="pixel"/> <property name="com.jaspersoft.studio.unit.y" value="pixel"/> </reportElement> <textElement textAlignment="Right"> <font fontName="Albany WT" size="10"/> </textElement> <textFieldExpression><![CDATA["Page " + $V{MASTER_CURRENT_PAGE}]]></textFieldExpression> </textField> <textField evaluationTime="Master"> <reportElement x="725" y="14" width="50" height="14" forecolor="#1A75B4" uuid="5c06c90b-79f2-450b-9f43-7eb00676871b"> <property name="com.jaspersoft.studio.unit.width" value="pixel"/> <property name="com.jaspersoft.studio.unit.height" value="pixel"/> <property name="com.jaspersoft.studio.unit.y" value="pixel"/> </reportElement> <textElement textAlignment="Left"> <font fontName="Albany WT" size="10"/> </textElement> <textFieldExpression><![CDATA[" of " + $V{MASTER_TOTAL_PAGES}]]></textFieldExpression> </textField>
还有后端生成JasperPrint的代码:
List<JasperPrint> prints = new ArrayList<JasperPrint>(); helperReturnObject.getTemPlatepaths().forEach(t -> { try { int index = helperReturnObject.getTemPlatepaths().indexOf(t); JasperReport jasperReport = null; if (!developMentFlag) { jasperReport = (JasperReport) JRLoader.loadObject(JasperGatewayClass.class.getResourceAsStream(t)); } else { try { jasperReport = (JasperReport) JRLoader.loadObject(new FileInputStream(new File("path"))); } catch (FileNotFoundException e) { e.printStackTrace(); } } JRBeanCollectionDataSource dataSource = new JRBeanCollectionDataSource( helperReturnObject.getBeanCollections().get(index)); JasperPrint jasperPrint = JasperFillManager.fillReport(jasperReport, helperReturnObject.getParameters().get(index), dataSource); prints.add(jasperPrint); } catch (/*JRException | NullPointerException*/ Exception e) { System.out.println(e.getMessage() + "----------------------ERROR----------------"); e.printStackTrace(); } });
JRXML端的可行性分析
先直接给结论:单纯靠JRXML很难实现这个需求。原因是每个子报表都是独立填充的,Jasper内置的MASTER_CURRENT_PAGE和MASTER_TOTAL_PAGES变量都是绑定单个报表实例的,没办法跨多个JasperPrint实例共享全局的页码计数。
如果硬要尝试JRXML方向的思路,你可以考虑把所有子报表的数据合并成一个大的数据源,然后用报表分组(Group)来划分原来的子报表内容,但这种方法有很大局限性:如果你的每个子报表模板差异很大(比如不同的页眉、字段布局),这种方法几乎行不通,而且还要大幅调整数据结构和报表设计,成本很高。
已验证的可行方案(适配页眉带)
既然JRXML端走不通,我找到了一个不需要修改打印逻辑的替代方案:在所有子报表填充完成后,遍历所有JasperPrint的页面,手动替换页码文本。这个方案你已经实现了,我再整理一下细节方便其他人参考:
步骤说明
- 先统计所有子报表的总页数
- 维护一个全局的连续页码计数器
- 遍历每个JasperPrint的每一页,深入到页眉的Frame元素中,找到页码对应的文本框,替换成全局的页码和总页数
完整实现代码
// 第一步:计算所有子报表的总页数 int totPageNumber = 0; for (JasperPrint jp : prints) { totPageNumber += jp.getPages().size(); } int currentPage = 1; // 定义两个标记,用来在JRXML中做占位(需要对应修改JRXML里的页码表达式) final String CURRENT_PAGE_MARKER = "CURRENT_PAGE_MARKER"; final String TOTAL_PAGE_MARKER = "TOTAL_PAGE_MARKER"; // 遍历所有子报表的页面,替换页码 for (JasperPrint jp : prints) { List<JRPrintPage> pages = jp.getPages(); for (JRPrintPage jpp : pages) { List<JRPrintElement> elements = jpp.getElements(); for (JRPrintElement jpe : elements) { // 页眉元素通常嵌套在Frame里,所以先判断是否为Frame类型 if (jpe instanceof JRTemplatePrintFrame) { JRTemplatePrintFrame jpf = (JRTemplatePrintFrame) jpe; List<JRPrintElement> frameElements = jpf.getElements(); for (JRPrintElement element : frameElements) { if (element instanceof JRTemplatePrintText) { JRTemplatePrintText textElement = (JRTemplatePrintText) element; // 替换当前页码标记 if (CURRENT_PAGE_MARKER.equals(textElement.getValue())) { textElement.setText("Page " + currentPage + " of"); continue; } // 替换总页数标记 if (TOTAL_PAGE_MARKER.equals(textElement.getValue())) { textElement.setText(" " + totPageNumber); } } } } } currentPage++; } }
对应的JRXML修改
你需要把原来的页码文本框表达式改成对应的标记:
<!-- 当前页码文本框 --> <textFieldExpression><![CDATA["CURRENT_PAGE_MARKER"]]></textFieldExpression> <!-- 总页数文本框 --> <textFieldExpression><![CDATA["TOTAL_PAGE_MARKER"]]></textFieldExpression>
这个方案的好处是完全不需要触碰打印逻辑,只需要在填充完所有报表后做一次全局的页码替换,完美适配了页眉带的结构,而且支持子报表数量动态扩展的场景,刚好符合你的需求。
内容的提问来源于stack exchange,提问作者HeartATech




