做SAP报表做到一定阶段你会发现Grid表格有时候不够用。比如用户拿着一张物料清单过来问能不能按销售组织往下展开一层销售组织、一层分销渠道最后才是物料明细这种父子层级关系非常明确的数据硬塞进普通ALV GRID里要么靠缩进字段伪造成树形要么做一堆重复行既难看又难维护。我接到这个需求时第一反应是也是GRID排出来以后自己都觉得丑层级关系完全靠猜。后来换成ABAP OO的ALV_TREE三层结构一次成型用户看完直点头。如果你也在做类似的主从明细、分类汇总、BOM展开、组织架构这类报表ALV_TREE这个控件值得专门花半天时间研究。这篇文章我不打算泛泛讲概念直接围绕一个能跑的例程展开销售组织—分销渠道—物料的三级树从类设计、字段目录配置、节点构建、事件注册到懒加载和避坑全部掰开揉碎讲清楚。1. 为什么是ALV_TREE从普通列表到树形展示的必然选择1.1 先分清两种报表需求我做SAP开发的这些年发现报表需求其实可以分成两大类。第一类是“明细平铺型”比如查询某个月的采购订单每条凭证一行列多但彼此独立数据之间没有明显的父子关系这种用ALV GRID最合适。第二类是“层级主从型”比如组织架构、BOM展开、物料分类、销售区域下的客户清单数据天然有上下级关系上级是一个汇总概念下级才是具体业务对象。很多初学者遇到第二类需求第一反应就是去Grid里想办法把上级字段放在最前面然后按这个字段排序、加重复值隐藏甚至做单元格合并。表面上看行数没变少但用户根本看不出层级关系也没法自由地折叠和展开。ALV_TREE就是专门解决这个问题的它把数据组织成树结构每个节点可以有自己的子节点每个节点同时又能携带一行明细列。1.2 ALV Tree 与 ALV Grid 的本质差异为了让你对这个选择有直观印象我列个对比表对比维度ALV GridALV Tree数据结构二维表每行独立树形结构节点可包含子节点层级展示靠排序和缩进伪装原生支持展开/折叠数据加载基本一次性传入内表支持按需加载TREE_GET_NODES事件行定位基于行号基于node_key树节点唯一标识交互复杂度排序、过滤、编辑都很直接需要额外维护节点关系适合场景明细报表、汇总报表主从结构、父子层级清单这里最核心的区别在于Grid的数据是一个“平铺内表”整体丢给控件的而Tree是由你主动构建的一个节点树。树控件内部保存了节点的父子关系普通列比如物料号、描述、类型只是挂在节点旁边的一行明细数据。换句话说ALV_TREE本质上是“一棵树 一个明细表格”的结合体这个认知特别重要后面很多参数你就能看明白了。1.3 三种树控件怎么选ABAP里实现树形展示方案不止一个我见很多老项目还在用FMREUSE_ALV_TREE_LIST_DISPLAY这玩意儿早年能用但函数式编程的回调逻辑非常绕全局变量满天飞事件处理不清晰。现在新项目建议直接走OO.OO方案里主要有三个类CL_GUI_ALV_TREE_BASE抽象基类一般不直接用。CL_GUI_ALV_TREE完整版树控件功能最全支持复杂交互。CL_GUI_ALV_TREE_SIMPLE简化版继承了完整版的大部分能力方法名和参数更友好适合常规业务树。我的例程用CL_GUI_ALV_TREE_SIMPLE因为常规的主从层级报表用这个类完全够代码写起来更清爽。如果你要做非常定制化的树——比如多选树、带特殊图标的节点、和Splitter容器做复杂布局那可能就得用CL_GUI_ALV_TREE。实际上CL_GUI_ALV_TREE_SIMPLE是CL_GUI_ALV_TREE的子类绝大多数方法都有只是隐藏了一部分高级能力让你调起来更省心。1.4 什么时候不该用树虽然ALV_TREE很好用但它不是万能的。我见过有人把几十万行的流水账做成树每行挂一个父节点结果性能直接崩了。ALV_TREE适合层级关系明确、层级数量有限的数据。如果是纯平铺明细、数据量超大且没有天然层级或者需要做复杂的整表编辑、拖拽调整那Grid甚至List还是更合适的选择。另外一点如果用户最终要把数据导出成Excel做透视树导出后的格式往往不如Grid友好这点也要提前和后端沟通清楚。2. 用OO写ALV_TREE之前先把这几个类搞清楚2.1 从REUSE_ALV_TREE_LIST_DISPLAY到OO方案老一代ABAP开发者对REUSE_ALV_TREE_LIST_DISPLAY应该不陌生它和REUSE_ALV_GRID_DISPLAY是同一个“函数式ALV”家族。函数式ALV的问题在于所有回调都是FORM状态靠全局变量传递多个事件交织在一起时代码像一团乱麻。OO方式把控件封装成对象事件绑定到类的方法上数据、节点、事件都围绕对象管理可读性、可维护性明显提升。我自己在重构一个物料主档查询报表时从FM版改成OO版代码量少了差不多三分之一功能还多了树展开和懒加载。这个对比非常直观新代码没有理由再走FM老路。2.2 三个核心类与一个核心容器搞定ALV_TREE你真正要打交道的类就三个CL_GUI_CUSTOM_CONTAINER屏幕上的自定义容器相当于画布树控件要贴在这个容器上。CL_GUI_ALV_TREE_SIMPLE树控件本体所有节点操作和显示逻辑都在这里。LVC_T_FCAT字段目录不是类但它决定了树行里的明细列显示哪些字段和Grid的Fieldcat完全一致。创建顺序也很固定先创建容器再创建树控件接着设置字段目录然后调用树的展示方法最后开始构建节点。容器在Dynpro屏幕上需要预留一个自定义控件区域比如CUSTOM_CONTROL。如果你的程序是报表Report而不是屏幕程序那还需要把容器创建语句放在START-OF-SELECTION事件里否则容器对象还没准备好树控件创建会失败。2.3 节点模型node_key是理解ALV_TREE的钥匙我一直觉得搞懂ALV_TREE最关键的不是Fieldcat而是node_key。树控件内部是一个标准的树结构每个节点有一个全局唯一的键值类型是TV_NODEKEY。你往树上挂节点时必须指定它的父节点键值I_REL_NODE_KEY和挂接方式I_RELATIONSHIP控件通过这种方式维护节点之间的父子关系。节点键值不是系统自动生成的而是你调用ADD_NODE方法后由控件返回的。我习惯把返回的E_NEW_NODE_KEY维护在一个内部表里字段通常包括NODE_KEY、NODE_TYPE、NODE_TEXT等这样后面想定位某个节点、删除某个节点、给节点追加子节点时都能快速找回来。这里有个新手容易绕进去的点树里根本没有“行号”这个概念。你要找某个数据行对应的树节点唯一办法就是通过node_key。所以构建树的时候一定要建立“业务数据主键 ↔ node_key”的映射关系。2.4 事件驱动和GRID完全不同的交互方式ALV_TREE的事件机制和Grid也不太一样。Grid常用的事件比如双击、工具栏按钮点击、数据修改Tree也有但它多了一个自己特有的事件TREE_GET_NODES这是懒加载模式的入口。当你没有一次性把所有节点挂上而是在用户展开某个父节点时才触发这个事件你需要在事件处理里动态加载子节点。事件注册分两步第一步调用SET_REGISTERED_EVENTS告诉控件“我要监听哪些事件”第二步用SET_HANDLER把事件绑定到类的方法上。很多人漏了第一步导致事件完全不触发后面避坑部分我会详细说。3. 可直接复制运行的例程销售组织—分销渠道—物料三级树3.1 例程的业务场景和数据结构这个例程我选了一个非常典型的场景销售组织VKORG下面有多个分销渠道VTWEG每个分销渠道下销售多个物料MATNR。三层结构正好覆盖了“根节点—中间层节点—叶子节点”三种形态学会这个例程其他类似的层级报表你基本能举一反三。数据源方面我用了三张标准表MVKE物料销售数据包含VKORG、VTWEG、MATNR、MARA物料主数据取物料类型MTART、物料组MATKL、MAKT物料描述按登录语言取MAKTX。数据库里只要有这些表的基本数据就能跑起来。数据结构定义如下TYPES: BEGIN OF ty_data, vkorg TYPE vkorg, vtweg TYPE vtweg, matnr TYPE matnr, maktx TYPE maktx, mtart TYPE mtart, matkl TYPE matkl, END OF ty_data. TYPES: BEGIN OF ty_node_map, node_key TYPE tv_nodekey, node_type TYPE c, R根, O销售组织, C渠道, M物料 matnr TYPE matnr, END OF ty_node_map. DATA: go_container TYPE REF TO cl_gui_custom_container, go_tree TYPE REF TO cl_gui_alv_tree_simple, gt_data TYPE TABLE OF ty_data, gt_fieldcat TYPE lvc_t_fcat, gt_node_map TYPE TABLE OF ty_node_map. DATA: gv_initialized TYPE abap_bool.3.2 主程序和完整代码为了让你能直接复制测试我写了完整的类实现。这里说一下设计思路我定义了一个局部类LCL_TREE_HANDLER把数据读取、字段目录构建、节点构建、事件处理方法都封装在里面主程序只负责初始化对象和调用入口方法。这样比在Report里堆一堆全局FORM清晰得多。REPORT zalv_tree_demo. TYPES: BEGIN OF ty_data, vkorg TYPE vkorg, vtweg TYPE vtweg, matnr TYPE matnr, maktx TYPE maktx, mtart TYPE mtart, matkl TYPE matkl, END OF ty_data. TYPES: BEGIN OF ty_node_map, node_key TYPE tv_nodekey, node_type TYPE c, matnr TYPE matnr, END OF ty_node_map. DATA: go_container TYPE REF TO cl_gui_custom_container, go_tree TYPE REF TO cl_gui_alv_tree_simple, gt_data TYPE TABLE OF ty_data, gt_fieldcat TYPE lvc_t_fcat, gt_node_map TYPE TABLE OF ty_node_map. CLASS lcl_tree_handler DEFINITION. PUBLIC SECTION. METHODS: start. PRIVATE SECTION. METHODS: read_data, build_fieldcatalog, init_tree, build_tree, handle_double_click FOR EVENT node_double_click OF cl_gui_alv_tree_simple IMPORTING node_key. ENDCLASS. CLASS lcl_tree_handler IMPLEMENTATION. METHOD start. read_data( ). build_fieldcatalog( ). init_tree( ). build_tree( ). ENDMETHOD. METHOD read_data. DATA: lt_mvke TYPE TABLE OF mvke, lt_mara TYPE TABLE OF mara, lt_makt TYPE TABLE OF makt. SELECT vkorg vtweg matnr FROM mvke UP TO 200 ROWS INTO CORRESPONDING FIELDS OF TABLE lt_mvke. IF lt_mvke IS INITIAL. MESSAGE MVKE表中没有数据请先维护物料销售视图 TYPE I. RETURN. ENDIF. SELECT matnr mtart matkl FROM mara INTO CORRESPONDING FIELDS OF TABLE lt_mara FOR ALL ENTRIES IN lt_mvke WHERE matnr lt_mvke-matnr. SELECT matnr maktx FROM makt INTO CORRESPONDING FIELDS OF TABLE lt_makt FOR ALL ENTRIES IN lt_mvke WHERE matnr lt_mvke-matnr AND spras sy-langu. LOOP AT lt_mvke INTO DATA(ls_mvke). DATA(ls_data) CORRESPONDING #( ls_mvke ). READ TABLE lt_mara INTO DATA(ls_mara) WITH KEY matnr ls_mvke-matnr BINARY SEARCH. IF sy-subrc 0. ls_data-mtart ls_mara-mtart. ls_data-matkl ls_mara-matkl. ENDIF. READ TABLE lt_makt INTO DATA(ls_makt) WITH KEY matnr ls_mvke-matnr BINARY SEARCH. IF sy-subrc 0. ls_data-maktx ls_makt-maktx. ENDIF. APPEND ls_data TO gt_data. ENDLOOP. SORT gt_data BY vkorg vtweg matnr. ENDMETHOD. METHOD build_fieldcatalog. DATA: ls_fcat TYPE lvc_s_fcat. DEFINE add_fcat. clear ls_fcat. ls_fcat-fieldname 1. ls_fcat-coltext 2. ls_fcat-outputlen 3. append ls_fcat to gt_fieldcat. END-OF-DEFINITION. add_fcat MATNR 物料号 18. add_fcat MTART 物料类型 10. add_fcat MATKL 物料组 10. add_fcat MAKTX 物料描述 40. ENDMETHOD. METHOD init_tree. IF go_container IS NOT BOUND. CREATE OBJECT go_container EXPORTING container_name CUSTOM_CONTROL EXCEPTIONS cntl_error 1 cntl_system_error 2 create_error 3 lifetime_error 4 lifetime_dynpro_dynpro_link 5. IF sy-subrc 0. MESSAGE 容器创建失败 TYPE E. ENDIF. CREATE OBJECT go_tree EXPORTING parent go_container node_selection_mode cl_gui_alv_treenode_sel_mode_single item_selection X no_html_header X no_toolbar space EXCEPTIONS cntl_error 1 cntl_system_error 2 create_error 3 lifetime_error 4 illegal_node_selection_mode 5. IF sy-subrc 0. MESSAGE 树控件创建失败 TYPE E. ENDIF. ENDIF. DATA: ls_hierarchy TYPE treev_hhdr. ls_hierarchy-header_name TREE_COL. ls_hierarchy-header_text 层级结构. DATA: ls_layout TYPE lvc_s_layo. ls_layout-cwidth_opt X. CALL METHOD go_tree-set_table_for_first_display EXPORTING is_hierarchy_header ls_hierarchy it_fieldcatalog gt_fieldcat is_layout ls_layout EXCEPTIONS error 1. DATA: lt_events TYPE cntl_simple_events. DATA: ls_event LIKE LINE OF lt_events. ls_event-eventid cl_gui_alv_tree_simpleeventid_node_double_click. APPEND ls_event TO lt_events. CALL METHOD go_tree-set_registered_events EXPORTING it_events lt_events. SET HANDLER handle_double_click FOR go_tree. gv_initialized abap_true. ENDMETHOD. METHOD build_tree. DATA: lv_root_key TYPE tv_nodekey, lv_org_key TYPE tv_nodekey, lv_cha_key TYPE tv_nodekey, lv_node_txt TYPE lvc_value, ls_node_map LIKE LINE OF gt_node_map. CALL METHOD go_tree-add_node EXPORTING i_node_text 销售数据 IMPORTING e_new_node_key lv_root_key. ls_node_map-node_key lv_root_key. ls_node_map-node_type R. APPEND ls_node_map TO gt_node_map. DATA: lt_orgs TYPE SORTED TABLE OF ty_data WITH NON-UNIQUE KEY vkorg. lt_orgs CORRESPONDING #( gt_data ). DELETE ADJACENT DUPLICATES FROM lt_orgs COMPARING vkorg. SORT lt_orgs BY vkorg. LOOP AT lt_orgs INTO DATA(ls_org). lv_node_txt |销售组织 { ls_org-vkorg }|. CALL METHOD go_tree-add_node EXPORTING i_rel_node_key lv_root_key i_relationship cl_gui_alv_treerelat_last_child i_node_text lv_node_txt IMPORTING e_new_node_key lv_org_key. ls_node_map-node_key lv_org_key. ls_node_map-node_type O. APPEND ls_node_map TO gt_node_map. DATA: lt_chas TYPE SORTED TABLE OF ty_data WITH NON-UNIQUE KEY vkorg vtweg. lt_chas CORRESPONDING #( gt_data ). DELETE lt_chas WHERE vkorg ls_org-vkorg. DELETE ADJACENT DUPLICATES FROM lt_chas COMPARING vtweg. SORT lt_chas BY vtweg. LOOP AT lt_chas INTO DATA(ls_cha). lv_node_txt |渠道 { ls_cha-vtweg }|. CALL METHOD go_tree-add_node EXPORTING i_rel_node_key lv_org_key i_relationship cl_gui_alv_treerelat_last_child i_node_text lv_node_txt IMPORTING e_new_node_key lv_cha_key. ls_node_map-node_key lv_cha_key. ls_node_map-node_type C. APPEND ls_node_map TO gt_node_map. LOOP AT gt_data INTO DATA(ls_mat) WHERE vkorg ls_org-vkorg AND vtweg ls_cha-vtweg. lv_node_txt |{ ls_mat-matnr } { ls_mat-maktx }|. CALL METHOD go_tree-add_node EXPORTING i_rel_node_key lv_cha_key i_relationship cl_gui_alv_treerelat_last_child i_node_text lv_node_txt is_outtab_line ls_mat IMPORTING e_new_node_key ls_node_map-node_key. ls_node_map-node_type M. ls_node_map-matnr ls_mat-matnr. APPEND ls_node_map TO gt_node_map. ENDLOOP. ENDLOOP. ENDLOOP. CALL METHOD go_tree-expand_no_levels EXPORTING i_level 1. CALL METHOD go_tree-refresh_tree. ENDMETHOD. METHOD handle_double_click. READ TABLE gt_node_map INTO DATA(ls_node) WITH KEY node_key node_key. IF sy-subrc 0 AND ls_node-node_type M. SET PARAMETER ID MAT FIELD ls_node-matnr. CALL TRANSACTION MM03 AND SKIP FIRST SCREEN. ENDIF. ENDMETHOD. ENDCLASS. START-OF-SELECTION. NEW lcl_tree_handler( )-start( ).3.3 关键方法逐个拆解这段代码里我觉得最值得讲的是这五个点。数据读取READ_DATA用了FOR ALL ENTRIES关联三张表注意排序和二分查找的配合。MARA和MAKT是标准表不管有没有数据程序都不能崩所以加了一个空表判断。字段目录BUILD_FIELDCATALOG里我只定义了四个明细字段物料号、物料类型、物料组、物料描述。注意我并没有把VKORG和VTWEG这两个字段放进Fieldcat因为这两个字段已经通过树的层级结构表达出来了不需要在明细列里重复出现。这是一个非常关键的设计取舍树的层级列承担了字段展示职责对应的业务字段就可以从Fieldcat里拿掉不然表格会显得冗余。初始化树INIT_TREE里的创建顺序不能乱。先容器后树控件再设置层级头最后设置Fieldcat。层级头结构TREEV_HHDR里HEADER_NAME可以随便取它只是内部标识HEADER_TEXT才是用户看到的层级列标题。事件注册我没有注册所有事件只注册了EVENTID_NODE_DOUBLE_CLICK。这里特别提醒SET_REGISTERED_EVENTS非常重要如果不注册后面SET_HANDLER绑定的方法永远不会被调用。事件ID字符串很容易拼错比如NODE_DOUBLE_CLICK中间的下划线少一个编译不报错但功能就没了。节点构建BUILD_TREE是整个程序的核心。根节点不需要父节点直接ADD销售组织节点挂在根节点下分销渠道节点挂在销售组织节点下物料叶子挂在渠道节点下。每个ADD_NODE返回的node_key我都记录到GT_NODE_MAP里后面双击跳转时才能根据key反查物料号。3.4 事件注册和双击交互关于事件机制我再展开一下。ALV_TREE常用的事件其实不少我列个表事件触发时机典型用途NODE_DOUBLE_CLICK双击节点跳转事务、下钻报表BUTTON_CLICK点击单元格内按钮自定义操作TREE_GET_NODES展开懒加载父节点按需加载子节点USER_COMMAND点击工具栏按钮自定义工具栏功能LINK_CLICK点击超链接跳转网页或事务KEYPRESS键盘按键快捷键操作双击非叶子节点时ALV_TREE默认会执行展开或折叠操作然后才触发双击事件。也就是说即使你在双击事件里写了跳转MM03用户双击“销售组织”节点时系统先做展开再进你的事件方法然后你判断node_type M不满足就不会跳转行为是符合预期的。但如果你的双击事件里写了很多复杂逻辑建议再判断一下节点类型避免不必要的性能开销。3.5 进阶懒加载模式下如何分段取数例程里我是一次性把所有节点挂上去的对于几百条数据完全没问题。但如果你的第一层节点下面每个都有几千条记录一次性ADD_NODE把控件堵死了用户等半天才看到界面这时候就该用懒加载。懒加载的原理是初始只挂第一层节点不挂子节点。当用户点击展开某个父节点时控件触发TREE_GET_NODES事件你在事件处理程序里根据node_key判断当前展开的是哪一层、对应哪个业务对象然后动态去数据库查数据并ADD_NODE。实现步骤大概是在SET_REGISTERED_EVENTS里注册EVENTID_TREE_GET_NODES。定义处理该事件的方法形参必须有node_key。在方法里根据node_key查GT_NODE_MAP得到当前节点的类型和业务主键。判断类型如果是销售组织节点就查它的分销渠道如果是渠道节点就查它的物料。查到的数据用ADD_NODE挂在当前节点下。这个模式的坑在于在TREE_GET_NODES事件处理中必须真的调用ADD_NODE去挂子节点。如果你忘了挂或者挂失败这个父节点会一直处于“展开但无子节点”的状态而且不会再触发下一次加载用户看到的就是一个永远点不开的分支。我用S_TCODE调试时踩过这个坑后来养成了在懒加载分支结束后检查sy-subrc的习惯。另外懒加载时不要用IS_OUTTAB_LINE传整行大数据能传主键就传主键在后续需要时再补查明细。这样树的构建速度会快很多。4. 从例程到项目落地ALV_TREE实战避坑清单4.1 树显示出来了但空白的排查路径这是ALV_TREE最常见的“灵异事件”容器和树控件都创建成功了字段目录也SetTableForFirstDisplay了但画面上只有列标题一行节点都没有。遇到这种情况我建议按下面顺序排查确认ADD_NODE真的被调用了。很多复制过来的代码把BUILD_TREE方法漏调了或者被IF条件挡掉了。在ADD_NODE处打断点看看有没有进到方法体。确认GO_TREE对象没有失效。Report程序里如果树控件不是类的静态属性或者全局属性有可能在第一次PBO之后被垃圾回收。看一下GO_TREE在调试器里是否显示为Initial。确认字段目录里没有把层级列相关的字段设成TECH。有些写法会在Fieldcat里加一个“NODE_KEY”字段并设TECH X这可能会导致层级列不渲染。我这里例程里没有把NODE_KEY放进Fieldcat层级列由IS_HIERARCHY_HEADER统一管理反而少了个坑。确认容器大小不为零。在Dynpro屏幕布局里如果自定义控件区域高度或宽度被压成0树可能呈现为空白。这个肉眼不容易看出来可以在运行时把容器对象的大小打出来检查。4.2 事件不触发原因十有八九在这里我在论坛上看到很多人问“ALV_TREE双击怎么没反应”一问代码只写了SET_HANDLER没写SET_REGISTERED_EVENTS。ABAP控件的事件机制和Java Swing类似你不仅要绑定监听器还要告诉控件“你允许触发哪些事件”。这个机制对Grid其实也存在但Grid某些版本默认全注册了Tree不一样它默认一个事件都不注册所以最容易踩坑。另一种情况是事件确实注册了但SET_HANDLER绑定的对象已经不存在了。ABAP里SET HANDLER绑定的是对象实例方法如果你在局部类里把树控件创建在临时对象里方法结束后对象被释放事件自然进不来。解决办法是把树控件和事件处理器都定义成全局可访问的对象或类属性。4.3 刷新树节点时的状态保持问题业务场景里很少只查一次数据用户经常会刷新树。最简单的刷新方法是REFRESH_TREE它只会重绘当前已有的节点数据节点结构不会变。如果用户切换了查询条件树的层级结构完全变了那就得用DELETE_ALL_NODES清空所有节点然后重新走BUILD_TREE逻辑。这里有个用户体验问题重建树之后用户之前展开的节点全部收回了滚动条位置也回到顶部。如果要保持状态需要在重建前遍历记录当前展开的节点路径重建后再用EXPAND_NODE把这些节点重新展开。我在实际项目里是这么处理的把执行展开的记录存在一个内部表里重建完成后循环EXPAND_NODE同时用SET_CURRENT_NODE把光标定位到用户之前选中的节点上。这套逻辑代码量不大但体验提升非常明显。4.4 大数据量下的性能优化节点数量过万之后ALV_TREE的构建和渲染会明显变慢。优化手段按效果排序第一懒加载。这是最彻底的方案数据分到各个层级用户看到哪层才加载哪层初始渲染只挂顶层节点速度流畅得多。第二批量添加节点。ADD_NODE一次加一个节点性能开销大。如果数据确实要一次性展开可以考虑ADD_NODES批量添加它会先创建节点再统一触发刷新效率高不少。但ADD_NODES的参数比较讲究建议先过一遍SAP的文档。第三精简字段目录和布局。CWIDTH_OPT开启自动列宽时控件会反复计算列宽大数据量下代价很高。可以关掉自动列宽手动设置每列合理宽度。第四避免在树控件里做求和。Grid的DO_SUM在树里计算逻辑复杂性能也不好。真需要汇总建议用独立的汇总行存在父节点上而不是依赖控件底层算。4.5 我在这类功能上的一些个人习惯看完了整套例程最后分享几个实际项目里的习惯这些属于不写在标准文档里但非常受用的东西。节点映射表一定单独管理。我见过很多例程随便搞几个全局变量记node_key项目小还好项目大了维护起来想骂人。规范做法是把NODE_KEY、NODE_TYPE、业务主键组成一个映射内表所有节点操作都从这个表里查。这个表同时承担了事件参数转换成业务数据的职责你写的代码会干净很多。能传主键就不要传整行。IS_OUTTAB_LINE虽然能传入完整行数据但节点多了以后内存和性能都不乐观。传入主键需要时再去查表对用户体验影响不大对系统资源友好很多。留意定制的深层需求。用户说要“目录树”往往不只是想要一个树形展示还希望点击节点能联动其他区域、导入导出带层级结构、或者把选中节点做批量操作。如果你只做个静态树交付后面大概率要返工。在动手写代码前花二十分钟问清楚用户“展开之后你要干什么”比什么都值。拿这套例程去改自己项目的时候我建议你也不要只抄ADD_NODE先把node_key的管理思路想清楚。树控件本身不复杂复杂的是你准备怎么用它、怎么跟业务数据挂钩、怎么在用户折腾数据的时候保持稳定。把上面这些点逐个过一遍ALV_TREE这关也就过得差不多了。
阅读完成 · 觉得有帮助?