ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

SMARTFORMS复杂表格布局:Template+Loop实战全解析

SMARTFORMS复杂表格布局:Template+Loop实战全解析 做SAP打印开发的人迟早会遇到这么一天需求方拿来一张复杂的业务表格横竖线交错、某些行要跨列、某些单元格要循环输出用SMARTFORMS标准组件怎么摆都别扭。我在做销售订单套打的时候就被这个问题卡了几个小时最终是Template加上Loop的组合把问题彻底解决了。这篇文章想把这条完整的路径写出来包括布局逻辑、实操步骤和我踩过的几个坑尤其会重点讲Loop调试里的动态断点设置这是很多人卡住的地方。如果你正在为复杂表格布局头疼或者刚接触SMARTFORMS想少走弯路可以按这篇文章的思路走一遍。1. 为什么复杂表格布局绕不开TemplateLoop开始讲操作之前有必要先把底层逻辑理清楚。SMARTFORMS里做表格有两种主路径一种是直接拖一个Table节点把字段放进去简单快捷另一种就是用Template画格子再用Loop去生成重复的行。两种方式都能出表格但面对复杂布局时TemplateLoop才是真正的解药。1.1 Table节点在处理复杂表格时的局限Table节点适合规整的、行列结构完全一致的明细表格。它的工作方式是定义一个或多个行模板每行包含若干列然后Loop循环将内表数据逐行填入。好处是开发效率高列宽、对齐、边框都不用手工调。但一旦出现下面几种情况Table节点就尴尬了某些行需要合并单元格比如页眉跨3列显示一个标题同一行内不同列的输出逻辑不一样比如第一列要按分组显示汇总第二列正常输出数据行的数量不是简单的一对一而是一组表头多条明细一行小计这种块状结构反复出现不同区块之间还要插入文本、图片或者页码等独立元素用Table强行实现也不是完全不行但你会陷入不断嵌套Table、隐藏空行、拼边框线的泥潭。有一次我试图用Table节点做每组订单带一个汇总行的布局做出来之后维护成本极高业务字段加一个列就要改动好几个行模板后来干脆推翻重做。1.2 Template节点与Loop节点的分工逻辑Template节点可以理解成一张白纸你在上面自由绘制单元格定义边框、长度、高度每个单元格里可以放文本、字段、图标等元素。它没有任何自动重复的能力画出来什么样打印出来就是什么样。Loop节点则是重复执行器它负责遍历内表行将当前行的值传给所包含的字段并控制重复输出的位置。TemplateLoop的组合逻辑是用Template把一行的骨架画出来然后在Template内部或者与Template并列放一个LoopLoop控制这一行骨架按照数据行数反复输出。每循环一次模板行就在纵向占一个新位置边框线、字段值跟着一起延伸。这样说可能有点抽象你可以理解为Template是印刷模具Loop是履带模具放在履带上履带每前进一格模具就压出一份内容。模具决定了每一份内容长什么样履带决定了压多少份。这个类比在理解复杂布局时帮助很大。1.3 一个你需要先弄懂的关键机制Loop的输出区Loop节点有一个重要属性叫输出区Output Area它在SMARTFORMS主窗口中与Template通过相对定位结合。很多新手第一步就栽在这在Template外面单独放一个Loop期望它沿Template的右边界向下循环结果发现Loop的内容跑到了Template的左边或者覆盖了Template的边框。原因在于Loop在SMARTFORMS中不是一个纯粹的布局对象它更像一个输出控制指令。Loop的每个循环周期间它会按照主窗口中内容的垂直排列位置输出。当Loop位于Template的某个单元格内时这个单元格就变成每次循环的驻留位置当Loop位于Template之外它就与Template并列存在互不干扰。想实现行循环但边框延续的效果正确的做法通常是先用Template画好一行的骨架然后把这个骨架的全部内容包括边框单元格放进一个主流程的循环块里或者把Template放在主窗口的主区域Loop放在该区域内部并紧贴Template的行位置。具体怎么操作我会在下一节用步骤拆解这里先记住核心结论Template负责长什么样Loop负责有多少行两者必须通过主窗口的输出顺序协作谁在前谁在后直接决定最终布局。2. 手把手搭第一个TemplateLoop表格这一节我们从零开始搭一个最基础的TemplateLoop表格。目标是打印一个物料清单表头是物料号、物料描述、数量、单位明细行由内表数据循环产生。2.1 前置准备明确数据源与内表结构我习惯先在ABAP程序里把数据准备好不要在SMARTFORMS里做复杂计算。对于物料清单这个例子我会定义以下内表结构TYPES: BEGIN OF ty_bom, matnr TYPE mara-matnr, maktx TYPE makt-maktx, menge TYPE bapimeoutquantity, meins TYPE mara-meins, END OF ty_bom. DATA: gt_bom TYPE STANDARD TABLE OF ty_bom.在调用SMARTFORM的Function Module之前把内表数据填充好。这样SMARTFORMS只负责展示逻辑放在程序里调试时也方便。SMARTFORM的全局定义Global Definitions里需要把内表定义到对应类型。先定义表单接口的参数参数名类型说明IV_DOCNO可选文档号GT_BOM传递表物料明细数据不要图省事直接传数据库表最好定义专用结构体避免SMARTFORM去查太底层的表。2.2 创建Template并绘制单元格骨架进入SMARTFORMS事务码通常为SMARTFORMS新建一个表单在主窗口MAIN下创建一个主区域Main Area然后插入Template节点。关键动作如下右键主窗口选择创建 - Template命名如LINE_BOM。双击Template进入行/列设置页面。对于一行5列的表头我通常会定义1行5列列宽度分别为4、8、3、3单位一般是厘米或字符。在Template内部每个单元格都预先分配一个样式Style。这一步不能省否则循环时边框线可能出现错位。表头与明细行如果样式相同也可以把表头做成模板的一部分如果表头需要固定在每页顶部就把表头放在Loop外面明细行部分放在Loop里面。需要注意Template的垂直高度明细行的高不要设太大我一般设为0.6cm到0.8cm因为一页可能打很多行省空间。2.3 在Template中插入Loop并绑定字段绘制好模板行后在Template的明细行区域插入Loop节点将光标定位到Template的明细行单元格中通常是第2行或者计划用于循环的行的位置。右键选择创建 - Loop命名为LOOP_BOM。双击Loop绑定数据源GT_BOM。在Loop的表字段设置里勾选要输出的字段MATNR - 物料号MAKTX - 物料描述MENGE - 数量MEINS - 单位将字段拖拽到Template对应的单元格中。注意是拖入Loop内不是放在Loop外面。如果你打开属性面板看到Loop的图标可以垂直折叠和水平折叠不要乱动。大部分情况下我们只需要垂直循环水平折叠是用在横排数据场景的。这个例子保持默认即可。2.4 控制循环行的增长与边框延续这时打印预览会发现每一行数据都输出了但行与行之间的边框线可能不连续或者横向边框线没有延伸。解决边框延续问题的核心在Template中不绘制水平线而是给单元格设置下边框样式。每行模板都带下边框循环时上一行的下边框自然就成了这一行的上边框视觉上连续。具体操作选中Template中明细行的所有单元格。在边框属性中选择下边框为实线上、左、右边框视情况设置。表头部分要有上下左右全边框明细行则建议用下边框左右边框这样可以避免相邻行出现双线。如果明细行内某个字段没有值观察是否会影响竖线。有个常见坑某个单元格里的字段为空时该单元格高度会被压缩导致一行看起来比其他行矮。解决办法是给单元格的最小高度Min Height设置固定值例如0.6cm防止空值导致布局塌陷。这样跑通后一个基础的单层循环表格就完成了。但真正的复杂表格远不止于此接下来我们做一个分组嵌套的案例。3. 实战案例带分组和动态列的物料清单表格很多业务表格远比表头明细复杂。我拿生产部门的物料需求打印单举个例子它要求按物料组分组输出。每组先输出一个物料组名称行下面再输出属于该组的物料清单每组结束后输出一个小计行。这个场景就是典型的块状重复用Table节点很难优雅实现但TemplateLoop嵌套可以。3.1 需求拆解与表格结构设计数据源有两个维度物料组和物料。程序里需要准备两层内表结构或者一张带有组别字段的扁平表。我建议用嵌套Loop处理分组逻辑更清晰。外层Loop遍历物料组内表内层Loop遍历该组下的物料明细。先看数据结构TYPES: BEGIN OF ty_group, matkl TYPE mara-matkl, maktx TYPE makt-maktx, END OF ty_group. TYPES: BEGIN OF ty_item. INCLUDE TYPE ty_bom. TYPES: matkl TYPE mara-matkl, amount TYPE bapie2amount, END OF ty_item. DATA: gt_group TYPE STANDARD TABLE OF ty_group, gt_items TYPE STANDARD TABLE OF ty_item.在外层内表里我还会加一个组内行数的计算字段用于内层Loop的循环次数控制。实际上SMARTFORM的Loop可以自己判断内表行数但如果你要对组内数据进行聚合建议预先算好。3.2 用嵌套Loop实现分组打印在主窗口的主区域中按下面的结构搭建创建外层Template只画两行一行放组名文本一行放明细表头比如物料号、名称、数量、金额。在外层Template的下方插入外层Loop循环对象为GT_GROUP。在外层Loop内部先放一个只读模板行组名行字段绑定GT_GROUP-MATKL和GT_GROUP-MAKTX。在内层放置一个Template或复用同一个行模板定义明细行模板高度约0.7cm。在内层Template中插入内层Loop循环对象为GT_ITEMS并且通过循环条件控制只输出GT_ITEMS-MATKL GT_GROUP-MATKL的数据。具体到SMARTFORMS操作内层Loop的General Attributes页面里有一个Where Condition条件表达式配置。写成GT_ITEMS-MATKL GT_GROUP-MATKL即可。注意两边字段类型必须一致否则系统会报错。系统按顺序处理外层Loop每进入一组先输出组名然后内层Loop遍历该组的所有物料行输出组内明细。全部物料行输出完后外层Loop进入下一组。这样分组打印就实现了不需要用复杂的排序技巧——前提是内表数据已经按物料组排序建议在调用SMARTFORM的ABAP程序里使用SORT GT_ITEMS BY MATKL完成。内层和外层的边框延续照旧按2.4节的办法处理。另外要特别注意外层模板中的明细表头不能被内层Loop包含否则表头会跟着每一组重复输出。正确做法是让表头位于外层Template中且是固定行。3.3 动态隐藏列利用Loop索引与条件表达式有时候业务要求根据数据动态隐藏某一列。比如只有部分物料有新旧状态标识如果整列都是空的就不要显示该列。SMARTFORMS里没有直接隐藏列属性但可以通过条件表达式控制列宽度或者用Column Moving/Suppressing技巧。我的做法是在Loop中设置显示条件假设状态列在Template中位于第5列。在需要进行逻辑判断的字段单元格里打开输出条件Output Condition。编写条件例如GT_ITEMS-STATUS 。当条件不满足时该单元格为空。但问题来了空单元格仍然占位列不会消失。如果要真正隐藏一列可以让该列宽度在循环首行之前通过变量动态设置。这里一个技巧是把列宽写成公式绑定到表字段的某个值比如列宽0表示不显示。但SMARTFORMS模板的列宽默认是固定的所以这个方法有局限性。更通用的是预先在程序中准备好显示列的结构把不需要的字段置空并在模板中用条件字体或颜色让它看起来像是空列视觉上等效于隐藏。如果是专业场景可以考虑用SAPscript兼容模式做完全动态列但SMARTFORMS原生交互方式并不擅长这个。所以遇到动态隐藏列需求我一般建议需求方改规格——只在模板里展示固定列空数据留白通过无边框线隐藏空格来达到视觉隐藏效果。3.4 结果验证打印预览中需要关注的点嵌套Loop跑通后最重要的一步是打印预览重点检查以下几点组名行与第一个明细行之间是否有意外的空隙第二组开始后组名是否仍然出现在正确位置有没有被上一组的边框线干扰每组明细行之间是否有粗线或断裂分页时一组数据可能在页面底部被截断下一组从下一页顶部才开始这种情况是否可接受我通常会在预览时缩放到100%逐页翻看。顺便在浏览器或SAP GUI的打印预览界面确认每页的行数是否符合纸张高度。如果发现某页只有一行明细就要考虑调整行高或分页策略。4. Loop调试经验动态断点设置的完整链路写SMARTFORMS时最痛苦的不是排版而是调试。尤其是Loop循环几十行、几百行数据的时候你只想看第37行的输出为什么错了结果按F8一下程序跑到底根本没法定位。这里分享一套我实际在用的动态断点排查链路。4.1 为什么普通断点不够用你在ABAP调试器里直接在子程序或Function Module设置断点每次循环都会中断几十行数据就要按几十次继续完全没法专注检查特定行。而且SMARTFORM生成的代码是动态程序断点位置和标准调用结构经常搅在一起普通断点维护成本很高。但实际业务问题往往是有规律的第1行正常第10行正常第11行以后字段错位。如果能在循环计数器等于11的时候精确停下来一切就简单了。4.2 在ABAP调试器中设置动态断点的具体路径ABAP调试器支持两种动态断点方式。第一种通过ABAP调试器主界面的断点选项卡选择创建断点时优先级选择仅当满足条件时输入条件语句。例如sy-tabix 11这里的sy-tabix是当前循环索引。但由于SMARTFORMS的Loop不一定直接映射到标准SY-TABIX有时需要借助上下文变量。这种情况下我一般会补充显式判断( sy-tabix 11 ) AND ( gt_items-matkl M01 )第二种在ABAP程序调用SMARTFORM的Function Module时在代码里临时写断点条件。这种方法更可控。你可以把传递的内表数据事先打一个调试标记IF line_exists( gt_items[ matkl M01 ] ). BREAK-POINT ID zfi_mm. ENDIF.然后在SMARTFORMS中使用这个标记或者干脆在ABAP调试器里针对某个内存变量设置监控。4.3 结合SY-TABIX和表字段条件精准中断要精准地进入第37行最好的方式是在ABAP调试器的断点选项中选中一个行断点然后在断点属性里勾选条件写sy-tabix 37注意如果SMARTFORMS的Loop实现不是直接用SY-TABIX你需要找到表单上下文中的实际循环变量。在调试时打开调试器的系统字段视图观察SY-TABIX是否随Loop变化。如果不变化就要在调试过程中拆解(st_loop_out)之类的内部结构变量。另一个实用的技巧是在SMARTFORMS调试时可以直接查看传递的内表数据在调试器里输入内表名比如GT_ITEMS然后在下标字段输入37将光标放到某行上用字段查看检查该行的值。这种方式的优势是你能在断点停止时同时看到当前循环变量和内表对应行的值是否一致。4.4 我实际排查过的一个字段错位Bug有一次做客户退货单打印显示第三列和第四列内容对调了但前两列正常。我在循环第33行设置了动态断点。断点触发后我检查了当前循环索引和模板字段变量发现第三列的字段源在程序中绑定的顺序与模板列顺序不一致——程序里返回的内表结构是MENGE在前MEINS在后但模板里把MEINS放在了第三列MENGE放在了第四列。这类问题在数据量大时直接肉眼看不出来动态断点让定位时间从半小时缩短到两分钟。你要养成一个习惯每在SMARTFORMS里拖动一个字段到模板都回程序里核对结构字段顺序别完全依赖拖拽可视化。5. 避坑指南TemplateLoop实战中的高频问题与排查方法从单层Loop到嵌套Loop再到动态内容我遇到的高频问题其实是有固定模式的。下面把这些模式整理出来每个都配上诊断思路供你排查时快速对照。5.1 反复出现循环内容未显示的诊断思路明明是正确绑定了内表也写了Loop但打印出来就是空白的。先检查四件事检查项操作数据源是否非空在程序调用SMARTFORM前用消息或调试器确认内表行数Loop是否放在Template外面Template外部区域无法感知模板行内容会以普通文本流输出Loop的条件表达式是否正确特别是嵌套Loop时条件字段名写错会导致无数据主区域高度是否足够主窗口区域设成0高度或隐藏区域时Loop无法输出我遇到最隐蔽的是第三种嵌套Loop中内层条件使用了外层字段但外层Loop还没有进入第一组所以内层条件永假。解决方式是先在预处理逻辑中给内表添加外层组字段索引或者把外层Loop的索引值赋给一个全局变量在条件中使用该全局变量。5.2 数据重复或漏行Loop起始位置与偏移量排查Loop默认从内表第一行开始循环到末尾。但SMARTFORMS允许你设置起始行和结束行比如Offset参数。如果你曾调整过这些参数数据重复的Bug多半就出在这里。我的建议是尽量把从第几行开始、到第几行结束这类逻辑放在ABAP程序中处理比如通过LOOP AT GT_ITEMS FROM n TO m生成一个临时切片内表再传给SMARTFORM。SMARTFORMS里只做全量循环不做分片循环这样可以避免SMARTFORMS的索引从0开始还是从1开始这类低级错误。如果你确实要在SMARTFORMS里设置起始行要记得初始值定为1不是0。这个错误常见到几乎每个开发人员都犯过。5.3 表格竖线断裂与分页异常的处理分页时经常会发现一个诡异的竖线断裂上一页最后一行有右边框下一页第一行左边框却消失。核心原因还是边框线的绘制方式。SMARTFORMS的边框依赖于单元格样式如果分页后新页没有对应的边框样式竖线就断了。解决方法是确保每一行模板的所有单元格都定义了完整的右、左、下边框并在样式里设置重复输出Repeat属性。如果是跨页可以在页面末尾加一个分页符让模板强制分页。另一种分页异常一组数据在页面底部被截断而后半组跑到下一页。这时需要为外层Loop设置防止分页拆分Keep Together属性。在Loop节点的属性窗口中勾选Keep group together。不过这个选项在SMARTFORMS中有时会失效建议搭配行模板下边框连续一起检查。如果Keep Together失效我通常会改用最笨但最稳妥的方式在数据预处理时根据每页最大行数把内表手工切分为多个子表每个子表对应一页或一个模板块。5.4 性能问题多层Loop嵌套时的优化建议嵌套Loop如果层数多SMARTFORMS生成时间是线性增加的。三层以上打印预览可能要等十几秒。性能优化首选方案减少SMARTFORMS内部循环层数在ABAP程序里把二层嵌套结构压平成一张扁平表。每个明细行都带上组名外层组信息通过AT NEW或ON CHANGE OF在程序里处理。如果你必须保留多层Loop那就优化条件表达式。内层Loop的Where条件字段不能是未索引的数据库表最好是内表字段且外层内表应该先排序这样内层循环的匹配效率高一些。还有一个小技巧不要在SMARTFORMS里写大量的字符串拼接尤其是拼接物料描述时。所有需要拼接的文本在ABAP程序里拼好传值给SMARTFORMS的字段。SMARTFORMS的文本节点处理字符串拼接的能力远不如ABAP代码用多了程序会变慢且难排查。6. 我在实战后的几个实用小结上面讲了完整的实现链路最后我再补充几个操作层面的实用心得都是我实际试过、被坑过之后才总结出来的。第一Template的命名规范非常重要。我在项目里会为每个模板定义清晰前缀TPL_HEADER、TPL_DETAIL_LINE、TPL_SUM_LINE。这样做的好处是Loop绑定模板时你能一眼从下拉列表里认出哪个是行模板。我之前用默认名TEMPLATE_001嵌套Loop时经常找错模板后来统一改成了带业务含义的命名再没犯过这个错。第二字体与样式要提前统一。单元格边框、字体大小、行高和字段数据类型都要有规范。比如数量字段要用NUMC或PIC转换日期字段要在程序里转成显示格式不要在SMARTFORMS里做转换。我在SMARTFORMS里写日期转换被坑过同一套代码在不同的SAP版本上表现不一样后来全部在程序里完成格式转换效果稳定。第三测试时多用不同数据量的用例。不只是测试一两条数据的显示效果还要测100行、分页临界值的数据。很多Line断裂和分页错乱都是大数据量时才暴露。我的习惯是在程序里准备三个测试用例单行数据、刚好一整页、超过一页半。三个用例都通过后再提交给业务做UAT。第四调整布局时最好一边改一边预览。SMARTFORMS没有实时画布改完模板参数必须点激活再到打印预览里看效果。多次来回操作后我养成了一个习惯先改参数再按一次激活然后用快捷键进入预览形成肌肉记忆效率会高一些。第五别忘了做版本管理意识。SMARTFORMS中的表单和字体类型一样都是传输请求里的对象。每次改动前记得把旧版本做一份备份上传避免改坏后无法回滚。特别是修改Loop条件和模板边框时影响范围是全局的。这套TemplateLoop的方法我在多个打印场景里用了很多次从销售订单到质检报告都适用。核心思路就一句话先用Template想清楚一行的物理样子再用Loop把这一行套上数据循环。遇到再复杂的表格只要把表格拆成一行一行的重复块每个块内部都有自己的TemplateLoop问题就能迎刃而解。你要是正在做一个让人抓狂的复杂表格不妨试试这个链路。
返回列表