☰
ABAP OO ALV TREE树形ALV实战:层级数据展示与节点事件处理
2026/10/4 5:33:11 网站建设 项目流程

刚接触 ABAP OO 的同学,十有八九绕不过 ALV TREE 这个组件。做报表做到后面你会发现,普通 ALV GRID 只能平铺数据,一旦遇到 BOM 展开、组织结构、成本中心层级、资产分类这类带父子关系的数据,GRID 就有点力不从心了。这时候 ABAP OO 里的 ALV TREE(树形 ALV)才是正统方案,而且它几乎没有函数封装,官方提供的方式就是面向对象,这意味着谁早点把 OO 写法吃透,谁在处理层级数据时就多一张底牌。

这篇例程,我从头到尾用一个可运行的 ABAP 程序讲清楚:ALV TREE 怎么建容器、怎么挂节点、怎么响应双击、怎么展开和获取选中节点。我尽量把参数、事件、常见坑都写明白,代码可以直接复制到 SE38 里跑,适合已经会基础 ABAP、但对 OO 和树控件还不熟的开发者。

1. 场景拆解与方案选型

1.1 树形 ALV 和普通 ALV Grid 怎么选

很多第一次接触 ALV TREE 的人会问:GRID 难道不能显示层级吗?能,但本质不一样。GRID 是把数据库查询结果按行列平铺展示,你要表达父子关系,通常只能通过缩进字段、加前缀符号等方式“假装”成树,数据本身还是一张平表,用户没法展开折叠,也没法按节点维度操作。ALV TREE 则是把数据组织成真正的树形结构,每个节点有唯一键、有父节点引用、有文件夹和叶子属性,点击节点前的展开符号才能看到下一层,交互体验完全不一样。

我给个简单判断标准:

  • 数据天然有父子层级,比如 BOM 多级展开、组织架构、权限树、科目表,优先 ALV TREE。
  • 数据是列表型,只需要汇总、排序、筛选,用 ALV GRID 就够。
  • 既要层级,又要格子内做编辑、统计,ALV TREE 能做部分编辑,但复杂操作还是得 GRID。

另外要注意,ALV TREE 和 ALV GRID 在类归属上不是兄弟关系。GRID 有函数封装REUSE_ALV_GRID_DISPLAY,但 ALV TREE 没有对应的REUSE_ALV_TREE_DISPLAY函数。你只能通过类CL_GUI_ALV_TREE来用,这也是为什么这个例程必须走 OO 路线的原因之一。

1.2 为什么建议直接用 OO 方式写树

老项目里很多人还是习惯写函数式 ALV,因为代码短、模板多、复制粘贴就走。但树的官方控件类从设计上就是面向对象的,如果强行绕开 OO,你会在后续做事件处理、节点动态增删时发现无处下手。

OO 方式写树有这几个实际好处:

第一,事件处理干净。树的双击、右键、拖拽、选中变化都是通过事件处理器分发,不需要像函数式 ALV 那样在一个全局 USER_COMMAND 里接收各种隐式参数,再靠 IF 判断。第二个好处是可维护性强,每个事件对应一个明确的方法,读代码的人不需要猜。

第三,OO 方式便于和业务封装结合。你可以写一个ZCL_BOM_TREE类,把构建节点、展开等逻辑封装进去,其他程序直接复用。这在函数式时代很难做到。

所以我的结论是:如果你还在犹豫学不学 OO 写法,ALV TREE 是一个很好的切入口,它强迫你理解对象创建、事件注册、方法调用,但难度又比 SAPUI5 那些前端技术低得多。

1.3 例程整体设计思路

这篇例程我选了一个电脑组装 BOM 的三层结构做演示数据:一台整机(根节点)下面挂着主板和电源两个组件,主板下面又挂 CPU 这个原材料。节点数量不多,但能完整展示“根节点—中间节点—叶子节点”三种形态。

程序整体就一块屏幕,屏幕上放一个自定义控件容器CUSTOM_CONTAINER,屏幕顶部放两个按钮:一个“展开全部”,一个“获取选中节点”。进入屏幕后,通过 PBO 创建设备,把模拟 BOM 数据逐个添加成树节点。用户双击某个物料节点时,弹出消息框显示物料号和描述,这是最常见的业务需求。选择多个节点后点击获取按钮,则会把选中的节点物料号打印出来。

这套设计覆盖了 ALV TREE 开发的核心闭环:容器实例化 -> 树实例化 -> 构建节点 -> 事件响应 -> 选中读取,往后做正式项目基本就是在这个骨架上扩展。

2. ALV TREE 核心对象与常用方法速览

2.1 容器与树控件的创建顺序

写 ALV TREE 的第一步是先创建容器,再创建树。顺序反了会直接运行时报错。容器是树的“画布”,没有画布,树不知道往哪儿画。

容器类用CL_GUI_CUSTOM_CONTAINER,创建时必须传入屏幕上的自定义控件名称。我这里屏幕控件名就叫CUSTOM_CONTAINER,两边的名字必须完全一致,否则会报控件找不到。

树类用CL_GUI_ALV_TREE,创建时把容器对象传进去。创建完之后,必须先调用SET_TABLE_FOR_DISPLAY做一次初始化,之后才能用ADD_NODE逐个加节点。这一步很多新手会漏,以为像 GRID 一样传内表就能显示,结果发现树创建成功了,但是页面是空白的。

标准流程是:

  1. 创建容器对象。
  2. 创建 ALV TREE 对象,关联容器。
  3. 调用SET_TABLE_FOR_DISPLAY,传入布局、表头等信息。
  4. 用ADD_NODE添加根节点和子节点。
  5. 注册事件处理器。
  6. 需要时调用EXPAND_ALL展开树。

2.2 节点关键属性:NODE_KEY、RELATKEY、RELATSHIP

树的核心是节点。ALV TREE 里每个节点都有一个唯一键,类型是TM_NODEKEY,这个键由系统在ADD_NODE时生成,也可以自己维护。节点的位置不是靠物理排序,而是靠父子关系和顺序关系。

ADD_NODE最常用的三个参数:

  • RELAT_NODE_KEY:相对节点,指“这个新节点相对于哪个节点”。
  • RELATSHIP:相对关系,决定了新节点是相对节点的第一个节点、最后一个节点,还是子节点。
  • ISFOLDER:是否文件夹节点,等于ABAP_TRUE时节点以文件夹图标展示,即使下面暂时没有子节点也能展开。

RELATSHIP是一个容易踩坑的地方。它不是'1''2'这种魔法数字,而应该用接口常量。比如:

  • IF_CUSTOMER_RELATSHIP=>RELAT_IS_FIRST:作为相对节点的第一个兄弟节点。
  • IF_CUSTOMER_RELATSHIP=>RELAT_IS_LAST:作为相对节点的最后一个兄弟节点。
  • IF_CUSTOMER_RELATSHIP=>RELAT_IS_CHILD:作为相对节点的子节点。

建根节点时不传RELAT_NODE_KEY,只传RELATSHIP = IF_CUSTOMER_RELATSHIP=>RELAT_IS_FIRST,系统会自动把它作为第一棵树的根。建子节点时,RELAT_NODE_KEY传父节点的键,RELATSHIP传RELAT_IS_CHILD。这一个动作理解透,整棵树的构建就通了。

2.3 节点操作方法:ADD_NODE、EXPAND_ALL、REFRESH_TREE_DISPLAY

ADD_NODE是构建树的主力,但我实际经验是,大型树不建议反复调用ADD_NODE构建几千个节点,性能会很差。更优的做法是构造完整的节点表,一次性用SET_TABLE_FOR_DISPLAY传入。但例程为了讲清逻辑,我优先用ADD_NODE,直观且适合初学者。

展开树有两个方法:EXPAND_ALL和EXPAND_NODES。EXPAND_ALL直接把所有节点全部展开,适合节点数不多、需要看全貌的场景。EXPAND_NODES按指定节点展开,可以传MAXLEVEL控制展开层数。

树显示后如果修改了节点属性或新增了节点,需要调用REFRESH_TREE_DISPLAY刷新界面。这个方法和 GRID 的REFRESH_TABLE_DISPLAY要区分开,不要记混。刷新之后,节点的选中状态可能会受影响,涉及选中逻辑的地方要重新读取。

3. 完整例程:三层物料 BOM 树的实现

3.1 主程序与屏幕搭建

先在 SE38 新建一个可执行程序,名字随便起,比如ZALV_TREE_DEMO。程序类型选可执行程序,然后画屏幕 0100。

屏幕布局很简单,我给了三个元素:

  • CUSTOM_CONTAINER:自定义控件,放在屏幕中央占据大部分区域,这是树的容器。
  • 按钮EXPAND_BTN:显示文本“展开全部”。
  • 按钮GETSEL_BTN:显示文本“获取选中节点”。

注意自定义控件区域的建议宽度和高度要设置够大,不然树显示出来只有窄窄一条。屏幕的PBO和PAI都要写对应逻辑。

程序顶层结构如下:

REPORT zalv_tree_demo. DATA: go_cont TYPE REF TO cl_gui_custom_container, go_tree TYPE REF TO cl_gui_alv_tree. DATA: gv_init TYPE abap_bool. TYPES: BEGIN OF ty_bom, node_key TYPE tm_nodekey, matnr TYPE matnr, maktx TYPE maktx, menge TYPE char12, meins TYPE meins, parent TYPE char20, " 父节点物料号,空表示根 END OF ty_bom. DATA: gt_bom TYPE TABLE OF ty_bom, gs_bom TYPE ty_bom. DATA: gt_nodes TYPE TABLE OF tm_nodekey. DATA: gs_layout TYPE lvc_s_layo, gs_head TYPE lvc_s_head.

这里我多定义一个GT_NODES内表,用来维护物料号和树节点键之间的对应关系。因为业务数据表GT_BOM里的NODE_KEY在节点还没创建前是不知道的,需要等ADD_NODE返回后才能回填,只有回填后,双击节点时才能通过节点键查到物料信息。

3.2 准备模拟 BOM 数据

为了演示,我直接在INITIALIZATION事件里把 BOM 数据填进内表,不用查数据库,这样任何人都能直接跑。

数据设计成三个层级:整机 NODE-ROOT,下面挂 NODE-MB 主板和 NODE-PS 电源,主板下面挂 NODE-CPU CPU。代码如下:

INITIALIZATION. PERFORM fill_bom_data. FORM fill_bom_data. REFRESH gt_bom. gs_bom-matnr = 'A100'. gs_bom-maktx = '高性能台式整机'. gs_bom-menge = '1'. gs_bom-meins = 'PC'. gs_bom-parent = ''. APPEND gs_bom TO gt_bom. gs_bom-matnr = 'C200'. gs_bom-maktx = '主板'. gs_bom-menge = '1'. gs_bom-meins = 'PCS'. gs_bom-parent = 'A100'. APPEND gs_bom TO gt_bom. gs_bom-matnr = 'P300'. gs_bom-maktx = '电源'. gs_bom-menge = '1'. gs_bom-meins = 'PCS'. gs_bom-parent = 'A100'. APPEND gs_bom TO gt_bom. gs_bom-matnr = 'U400'. gs_bom-maktx = 'CPU处理器'. gs_bom-menge = '1'. gs_bom-meins = 'PCS'. gs_bom-parent = 'C200'. APPEND gs_bom TO gt_bom. ENDFORM.

这里parent字段存的是父物料号,而不是树节点键。构建树时我会根据父物料号找到已经创建的父节点键,再调用ADD_NODE。这个设计比硬编码节点键更贴近真实业务场景,因为真实业务数据里物料和物料之间的父子关系本来就是靠物料号关联的。

3.3 屏幕 PBO:创建控件并构建树

进入屏幕 0100 时,PBO 里判断树控件是否已经创建。因为 PBO 每次进入屏幕都会执行,如果每次都重新创建,树会不断重建,体验很糟糕。我用GV_INIT这个变量标记,第一次进入时创建,之后直接跳过。

MODULE status_0100 OUTPUT. SET PF-STATUS 'STATUS_0100'. SET TITLEBAR 'TITLE_0100'. IF gv_init IS INITIAL. PERFORM init_tree. gv_init = abap_true. ENDIF. ENDMODULE. FORM init_tree. CREATE OBJECT go_cont EXPORTING container_name = 'CUSTOM_CONTAINER'. CREATE OBJECT go_tree EXPORTING parent = go_cont node_selection_mode = cl_gui_alv_tree=>node_sel_mode_multi. " 布局设置:多选 gs_layout-sel_mode = 'M'. " 树标题 gs_head-typ = 'H'. gs_head-info = '物料 BOM 树例程'. CALL METHOD go_tree->set_table_for_display EXPORTING i_header = gs_head i_layout = gs_layout EXCEPTIONS cntl_error = 1 OTHERS = 2. IF sy-subrc <> 0. MESSAGE 'ALV TREE 初始化失败' TYPE 'E'. ENDIF. PERFORM build_tree. PERFORM register_events. ENDFORM.

NODE_SELECTION_MODE参数我传了NODE_SEL_MODE_MULTI,这个常量允许用户通过 CTRL 或 SHIFT 多选节点。同时我在布局里也把SEL_MODE设为'M',双层保险。

构建树的方法如下:

FORM build_tree. DATA: lv_root TYPE tm_nodekey, lv_node TYPE tm_nodekey, lv_parent TYPE tm_nodekey, ls_bom TYPE ty_bom, lv_text TYPE lvc_value. SORT gt_bom BY parent matnr. LOOP AT gt_bom INTO ls_bom. lv_text = |{ ls_bom-matnr } - { ls_bom-maktx } (数量:{ ls_bom-menge } { ls_bom-meins })|. IF ls_bom-parent IS INITIAL. " 根节点 CALL METHOD go_tree->add_node EXPORTING relat_node_key = space relatship = if_customer_relatship=>relat_is_first node_text = lv_text isfolder = abap_true IMPORTING tree_node_key = lv_root. gs_bom-node_key = lv_root. MODIFY gt_bom FROM gs_bom TRANSPORTING node_key WHERE matnr = ls_bom-matnr. ENDIF. ENDLOOP. LOOP AT gt_bom INTO ls_bom WHERE parent IS NOT INITIAL. " 找父节点的树节点键 READ TABLE gt_bom INTO gs_bom WITH KEY matnr = ls_bom-parent. IF sy-subrc = 0. lv_parent = gs_bom-node_key. CALL METHOD go_tree->add_node EXPORTING relat_node_key = lv_parent relatship = if_customer_relatship=>relat_is_child node_text = lv_text isfolder = abap_true IMPORTING tree_node_key = lv_node. gs_bom-node_key = lv_node. MODIFY gt_bom FROM gs_bom TRANSPORTING node_key WHERE matnr = ls_bom-matnr. ENDIF. ENDLOOP. ENDFORM.

这段逻辑我分了两遍循环:第一遍建根节点,第二遍建子节点。之所以要分开,是因为建子节点时必须保证父节点的NODE_KEY已经回填到GT_BOM表里。如果合在一次循环里,万一父节点排在子节点后面,READ TABLE就查不到,子节点就建不出来了。

真实项目里 BOM 数据往往几十上百行,我会用递归或者按层级排序的方式处理,但例程为了好懂,用两遍循环就够了。我写的SORT gt_bom BY parent matnr是一个额外的保证,让空的parent字段排最前面,这样根节点一定会先处理。其实第二遍已经用WHERE parent IS NOT INITIAL过滤,排序对第一次循环没影响,但对第二遍处理顺序有辅助作用。

3.4 事件注册:双击节点显示物料信息

树控件的事件处理是 OO 方式最让人舒服的地方。我定义了一个事件处理器类,类里声明对应的事件处理方法,然后在程序里用SET HANDLER注册。

事件处理器的完整写法:

CLASS lcl_tree_handler DEFINITION. PUBLIC SECTION. METHODS: on_node_double_click FOR EVENT node_double_click OF cl_gui_alv_tree IMPORTING node_key. ENDCLASS. CLASS lcl_tree_handler IMPLEMENTATION. METHOD on_node_double_click. DATA: ls_bom TYPE ty_bom. READ TABLE gt_bom INTO ls_bom WITH KEY node_key = node_key. IF sy-subrc = 0. MESSAGE i000(0) WITH |物料 { ls_bom-matnr }:{ ls_bom-maktx }|. ELSE. MESSAGE i000(0) WITH '未找到对应物料数据'. ENDIF. ENDMETHOD. ENDCLASS.

事件参数node_key的类型是TM_NODEKEY,这个名字不能改,必须和事件定义保持一致。注册代码:

DATA: go_handler TYPE REF TO lcl_tree_handler. FORM register_events. CREATE OBJECT go_handler. SET HANDLER go_handler->on_node_double_click FOR go_tree. ENDFORM.

一个容易忽略的点:事件处理器对象必须保存到全局变量里。如果你在REGISTER_EVENTS里用一个局部变量接收处理器,方法结束局部变量被释放,事件可能就失效了。我之前踩过这个坑,表现是双击没有任何反应,查了半天才发现处理器对象被垃圾回收了。

3.5 按钮响应:展开全部与获取选中节点

屏幕上有两个按钮对应的功能码,PAI里处理:

MODULE user_command_0100 INPUT. DATA: lv_ucomm TYPE sy-ucomm. lv_ucomm = sy-ucomm. CASE lv_ucomm. WHEN 'EXPAND_BTN'. CALL METHOD go_tree->expand_all. WHEN 'GETSEL_BTN'. PERFORM get_selected_nodes. ENDCASE. ENDMODULE. FORM get_selected_nodes. DATA: lt_sel TYPE TABLE OF tm_nodekey, lv_sel TYPE tm_nodekey, ls_bom TYPE ty_bom, lv_msg TYPE string. REFRESH lt_sel. CALL METHOD go_tree->get_selected_nodes IMPORTING node_key_table = lt_sel. IF lines( lt_sel ) = 0. MESSAGE i000(0) WITH '请先选择节点'. RETURN. ENDIF. LOOP AT lt_sel INTO lv_sel. READ TABLE gt_bom INTO ls_bom WITH KEY node_key = lv_sel. IF sy-subrc = 0. CONCATENATE lv_msg ls_bom-matnr ls_bom-maktx INTO lv_msg SEPARATED BY cl_abap_char_utilities=>cr_lf. ENDIF. ENDLOOP. MESSAGE i000(0) WITH lv_msg. ENDFORM.

GET_SELECTED_NODES方法返回的是节点键内表,我需要通过节点键反向查GT_BOM得到物料号。注意这里的node_key_table是导出参数,不是导入参数,我在网上见过一些人写反,结果一直报参数错误。

3.6 双击事件的完整联动效果

完整跑起来后,界面效果是这样的:树根节点显示“A100 - 高性能台式整机(数量:1 PC)”,展开后有“C200 - 主板”和“P300 - 电源”两个子节点,再展开主板,能看到“U400 - CPU处理器”。

双击任何一个节点,消息框会显示对应物料号和描述。如果用户按住 CTRL 选择主板和 CPU 两个节点,再点“获取选中节点”,消息框会把两行物料信息都列出来。

这套联动就是 ALV TREE 最常见的标准玩法,后续你要加上增删改节点、右键菜单、拖拽排序,都是在这些基础上扩展。

4. 进阶功能:让树控件更好用

4.1 图标控制与展开样式

默认树节点只有文件夹和文档两种图标。真实业务里,用户往往希望通过图标一眼看出节点类型,比如物料、设备、人员。

ADD_NODE在创建节点时可以指定图片。SAP 标准能用的图标在ICON表里,开发时可以利用函数ICON_CREATE生成图标名。比如物料节点想用ICON_OBJECTS,设备节点想用ICON_SYSTEM_OBJECTS,可以把生成的图标名传给ADD_NODE的IMAGE参数。

示例写法:

CALL METHOD go_tree->add_node EXPORTING relat_node_key = lv_parent relatship = if_customer_relatship=>relat_is_child node_text = lv_text isfolder = abap_true image = lv_icon IMPORTING tree_node_key = lv_node.

注意IMAGE参数设的是节点未展开时显示的图标,如果你希望展开时换一个图标,可以用EXPANDED_IMAGE参数。一个常见细节:只有ISFOLDER = ABAP_TRUE的节点,展开前和展开后的默认文件夹图标才有区别,叶子节点设置EXPANDED_IMAGE通常没有实际意义。

4.2 右键菜单和工具栏集成

树控件默认支持右键,但菜单内容需要你自己实现。需要注册两个事件:

  • NODE_CONTEXT_MENU_REQUEST:右键点击节点时触发,在这里添加菜单项。
  • NODE_CONTEXT_MENU_SELECT:用户点击菜单项时触发,在这里处理业务逻辑。

事件方法大致思路:

METHOD on_node_context_menu_request. DATA: lt_menu TYPE TABLE OF int4. " 增加一个“查看物料详情”菜单项 CALL METHOD sender->add_menu_entry EXPORTING i_menu_entry = 'DETAIL' i_entry_text = '查看物料详情' i_icon = icon_detail. ENDMETHOD. METHOD on_node_context_menu_select. IF p_fcode = 'DETAIL'. " 根据节点键查物料并弹详情 ENDIF. ENDMETHOD.

这个功能我强烈建议做,因为用户使用树时,右键操作比双击更自然。SAP 标准事务里几乎所有树都带右键,不带右键的树会显得像个半成品。

4.3 动态增删节点

树构建完之后,业务上经常需要在某个节点下新增子节点,或者删除某个节点。

新增子节点还是用ADD_NODE,只需要找到父节点的键,然后以RELAT_IS_CHILD的关系挂上去。

删除节点用DELETE_NODE方法:

CALL METHOD go_tree->delete_node EXPORTING node_key = lv_del_key EXCEPTIONS OTHERS = 1.

DELETE_NODE会连带删除所有子节点,这一点和文件系统删除文件夹的机制一样。如果不想物理删除,只想隐藏,可以把节点的HIDDEN设置成ABAP_TRUE,属性设置后调用刷新方法即可。

4.4 拖拽调整节点顺序

ALV TREE 原生支持拖拽,但配置比较繁琐。先要设置DRAGDROP对象,然后实现ON_DRAG、ON_DROP等事件。树内拖拽调整顺序我最常用的做法:

  1. 创建CL_DRAGDROP对象。
  2. 创建CL_DRAGDROP_TREE事件处理器。
  3. 在ON_DRAG里记录拖拽源节点键。
  4. 在ON_DROP里获取目标节点键,调用MOVE_NODE或重新ADD_NODE调整层级。

拖拽模式对性能不太友好,且大量节点时容易出现拖拽后节点错乱,所以我一般建议节点少于两三百时才开放拖拽,数据量大就别做了,容易把用户搞蒙。

5. 常见问题与排查思路

5.1 树创建出来永远是空白

这是我被问到最多的问题。表现是容器有了,树对象也创建成功了,但页面上什么都没有。

排查顺序我建议按下面来:

  • 检查是否调用了SET_TABLE_FOR_DISPLAY。没有调用这步,树不会真正初始化显示,ADD_NODE也不会有效果。
  • 检查容器控件名称是否拼写正确。CL_GUI_CUSTOM_CONTAINER的CONTAINER_NAME和屏幕上的自定义控件名必须完全一致,包括大小写。
  • 检查ADD_NODE时是否传了根节点。如果所有节点都用RELAT_IS_CHILD关系挂在空节点下,树是建立不起来的。

还有一个隐藏问题:如果屏幕上的自定义控件放在 TabStrip 里,必须等 TabStrip 激活后才能创建容器,否则树加载时机不对,也会显示空白。

5.2 节点双击事件不触发

可能原因有两种:

第一种是事件方法声明时事件拼错。FOR EVENT node_double_click OF cl_gui_alv_tree,注意是NODE_DOUBLE_CLICK,不要写成DOUBLE_CLICK。第二种是事件处理器对象被回收。

我遇到过一种很隐蔽的情况:同一个树对象在 PBO 里被重建了。比如GV_INIT标志被重置,第二次进屏幕时重新创建了GO_TREE,但事件处理器还挂在旧对象上,双击自然没反应。解决方法是保证树的创建和事件注册只执行一次,如果树对象重建,必须重新SET HANDLER。

5.3 节点图标显示成问号

节点图标显示为问号,通常是IMAGE参数传的图标名无效。

CONSTANTS里直接写ICON_DETAIL可能没生效,因为你看到的ICON_*常量,底层是包含图标和功能码的字符串,必须通过ICON_CREATE或者直接赋常量才能用。如果引用了自定义图标池,需要确认图标对象已经上传到系统里,并选择了正确的图标集。

建议写法:

DATA: lv_icon TYPE icon_d. CALL FUNCTION 'ICON_CREATE' EXPORTING name = 'ICON_DETAIL' IMPORTING result = lv_icon.

5.4 节点几千个时页面卡死

ALV TREE 在节点数量超过几千个时,性能下降得很快,这是控件的天然特性。我的建议是:

第一,不要一次性全部ADD_NODE,优先只加载当前需要展示的层级,懒加载。第二,使用SET_TABLE_FOR_DISPLAY一次传入完整的节点表,比循环调ADD_NODE性能好得多。第三,将展开方式设置为按需展开,不要调用EXPAND_ALL展开所有层。

我把几类常见问题整理成速查表,方便现场排查:

现象可能原因排查方向
页面空白未调用 SET_TABLE_FOR_DISPLAY检查初始化流程
页面空白容器控件名不匹配核对自定义控件名称
双击无响应事件处理器对象被释放确保 GO_HANDLER 是全局变量
双击无响应树对象被重建加 GV_INIT 标志保证只创建一次
图标问号ICON 名无效用 ICON_CREATE 生成图标
节点层级错乱父节点键未回填确认 ADD_NODE 的导出参数已保存
大数据卡顿循环调用 ADD_NODE 过多改用节点表一次传入

5.5 一个容易被忽略的刷新问题

当你动态增删节点或者修改节点属性后,界面不会自动更新,需要手动触发刷新。ALV TREE 的刷新方法是REFRESH_TREE_DISPLAY,但它有个特点:刷新后树的展开状态可能会被重置。

所以如果你的树有几十上百个节点,用户正在展开某几个节点查看,你此时调用刷新,用户的展开状态就丢了,体验非常差。我的做法是刷新前先调用GET_EXPANDED_NODES保存展开的节点键,刷新后重新EXPAND_NODES。这一步需要写一点代码,但用户感知完全不同,属于必须做的优化。

回到例程本身,我这套 BOM 树代码最初是在一个物料主数据维护程序里写的。当时老板提的需求是能看多级 BOM,并且双击物料可以直接跳转 MM03。我第一版用 GRID 硬缩进,做了两天,用户试用完直摇头,说子物料一行一行平铺看不清层级。后来我改成 ALV TREE,半天重写完,双击事件一接,用户连声说对味。

这里分享一个实际心得:树控件的节点文本别只拼物料号。真实用户看树,希望在节点层级里直接看到“这个东西是什么”,所以我在文本里把“物料号 - 描述 - 数量”都拼上,业务人员不用点进去就知道节点内容。虽然这会牺牲一点界面简洁性,但对生产效率的提升非常明显。

另外,如果你要在正式项目里用这套代码,记得把硬编码的GT_BOM数据源替换成自己的业务表查询,逻辑完全不用改。后续想加右键菜单,注册NODE_CONTEXT_MENU_REQUEST事件,往里加菜单项就行。树控件的学习曲线其实不陡,关键是理解节点的层级关系和 OO 事件模型,理解了以后,你会发现它在处理层级数据时,比 GRID 顺手太多。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询