SAP ABAP树状ALV开发实战:从CL_SALV_TREE原理到性能优化
2026/8/7 6:13:34 网站建设 项目流程

1. 从列表到树形:为什么我们需要树状ALV?

在SAP ABAP开发中,ALV(ABAP List Viewer)几乎是每个开发者都绕不开的报表输出工具。标准的ALV表格功能强大,能排序、筛选、汇总,应付大多数业务场景绰绰有余。但当你面对那些天然具有层级关系的数据时,比如组织结构图、物料BOM(Bill of Material)、会计科目表,或者任何需要父子结构展示的场景,传统的平铺列表就显得力不从心了。你不得不通过缩进、颜色或者额外的描述字段来“模拟”层级,不仅代码复杂,用户体验也大打折扣。

这时,树状ALV(Tree ALV)的价值就凸显出来了。它不是一个独立的新控件,而是对标准ALV功能的扩展和深化。想象一下,你点开一个部门节点,其下的所有员工列表像树枝一样展开;或者展开一个产品物料,其所有的子组件和原材料一目了然地呈现在下方。这种交互方式直观、高效,完全符合数据的本质结构。

我接手过不少从复杂平铺报表重构为树状展示的项目,最深的体会是:树状ALV的核心价值不在于“炫技”,而在于“降本增效”。对于开发而言,它让数据模型的展示逻辑变得异常清晰;对于最终用户,尤其是业务分析、财务或供应链管理人员,他们能像操作Windows资源管理器一样浏览数据,极大地降低了理解和使用门槛,减少了因数据层级混乱导致的误操作。接下来,我就结合一个完整的、可运行的实例源码,带你从零开始,拆解树状ALV的实现逻辑、核心类库的用法,以及那些官方文档里不会写的“坑”和调试技巧。

2. 环境准备与核心类库解析

在动手编码前,我们必须先理清SAP为我们提供的“武器库”。树状ALV的实现主要依赖于SAP提供的一系列标准类(Class),这些类封装了复杂的GUI和数据处理逻辑。

2.1 关键类(CL_GUI)家族

树状ALV的实现核心是CL_GUI_ALV_GRIDCL_GUI_COLUMN_TREE这两个类族的协同工作。但请注意,在现代SAP开发中(特别是NetWeaver 7.0以后),更推荐使用功能更强大、集成度更高的CL_SALV_TREE类。为了展示从基础到进阶的理解过程,我们先从相对底层的组合方式讲起,这有助于理解其原理。

  1. CL_GUI_ALV_GRID: 这是标准ALV表格的承载类。在树状结构中,它并不直接显示树,而是用于显示某个树节点被双击或展开后,其下级的详细数据列表。你可以把它想象成树形结构末端的“叶子”或“详情面板”。
  2. CL_GUI_COLUMN_TREE: 这是构建树形结构主干的核心类。它负责创建和管理树的节点(Node)、定义节点的层级关系、处理节点的展开/折叠事件。这个类生成的是一棵“光秃秃”的树,只有节点键值和文本。
  3. CL_GUI_CFW: 调度器类。当界面上同时存在多个GUI控件(比如一个树控件和一个ALV网格控件)时,需要用它来统一管理和调度这些控件的事件流,确保用户操作能被正确响应。

然而,直接使用CL_GUI_COLUMN_TREECL_GUI_ALV_GRID进行拼接,代码量会比较大,需要手动处理大量的控件布局和事件联动。因此,SAP提供了更高级的封装:

  1. CL_SALV_TREE这是当前实现树状ALV的首选和推荐类。它属于SALV(SAP List Viewer)框架,内部已经完美整合了树形结构和表格展示。你只需要定义好数据结构和层级关系,CL_SALV_TREE会自动渲染出带有层次感的表格,支持与标准ALV几乎相同的所有功能(排序、筛选、工具栏等),开发效率极高。我们本次附带的源码也将主要基于此类实现。

2.2 数据模型设计:内表结构与节点键

无论用哪种类,清晰的数据结构是基石。树状数据通常需要在内表中明确体现父子关系。

假设我们要展示一个简单的部门-员工树。通常会设计两个内表,或者一个内表包含所有必要字段:

TYPES: BEGIN OF ty_s_node, node_key TYPE tm_nodekey, " 节点的唯一标识键,至关重要 relatkey TYPE tm_nodekey, " 父节点的键值,顶层节点此值为空 relatship TYPE tm_nodeship, " 与父节点的关系,如‘CN’(子节点) isfolder TYPE tm_boolean, " 标记是否为文件夹(可展开) text TYPE lvc_value, " 节点显示文本 dept_id TYPE string, " 业务数据:部门ID emp_id TYPE string, " 业务数据:员工ID emp_name TYPE string, " 业务数据:员工姓名 ... " 其他需要展示的业务字段 END OF ty_s_node. DATA: gt_tree_data TYPE TABLE OF ty_s_node.

关键字段解析:

  • node_key: 每个节点的身份证号,必须全局唯一。通常可以用业务键(如部门号+员工号)拼接,或者简单使用一个自增的序号。
  • relatkeyrelatship: 这对字段定义了树的拓扑结构。relatkey指向父节点的node_key,根节点的relatkey为空。relatship定义了关系类型,最常用的是'CN'(Child Node)。
  • isfolder: 布尔值。‘X’表示该节点是一个文件夹(有子节点,可展开),‘ ’表示是叶子节点。这个字段直接影响树控件是否在该节点显示展开图标。
  • 业务字段: 如dept_id,emp_name等,这些是最终要展示在ALV表格列里的内容。

踩坑提示1:node_key的唯一性陷阱我曾在一个项目里,用“年份+序号”生成node_key,自认为很安全。结果当用户同时打开两个不同年份的报表时,因为程序用的是共享内存区,导致node_key冲突,树形结构完全错乱。教训是:node_key最好使用全局唯一的GUID(SY-UUID),或者至少包含会话相关的标识符(如SY-SYSIDSY-MANDT),避免跨会话污染。

3. 基于CL_SALV_TREE的完整实现流程

下面,我们以CL_SALV_TREE为例,分步拆解一个树状ALV的创建过程。我会在关键步骤穿插代码片段和详细解释。

3.1 步骤一:数据准备与填充

首先,我们需要从数据库或逻辑层获取数据,并按照树形结构填充到定义好的内表中。

METHOD build_tree_data. DATA: lv_parent_key TYPE tm_nodekey. DATA: lv_index TYPE n LENGTH 6. CLEAR: gt_tree_data. " 1. 假设从数据库读取了部门数据(LT_DEPT)和员工数据(LT_EMP) " LT_DEPT 包含字段:DEPT_ID, DEPT_NAME " LT_EMP 包含字段:DEPT_ID, EMP_ID, EMP_NAME " 2. 创建根节点(可选,提供一个总览) lv_index = '000001'. gt_tree_data = VALUE #( BASE gt_tree_data ( node_key = lv_index relatkey = '' " 根节点,父键为空 relatship = cl_gui_column_tree=>relat_last_child isfolder = abap_true " 根节点是可展开的文件夹 text = '组织架构总览' dept_id = '' emp_id = '' emp_name = '' ) ). lv_parent_key = lv_index. " 记录根节点键,作为后续部门的父键 " 3. 循环部门,作为第一级子节点 LOOP AT lt_dept ASSIGNING FIELD-SYMBOL(<fs_dept>). lv_index = lv_index + 1. gt_tree_data = VALUE #( BASE gt_tree_data ( node_key = lv_index relatkey = lv_parent_key " 父键是根节点 relatship = cl_gui_column_tree=>relat_last_child isfolder = abap_true " 部门节点也是文件夹,因为下面有员工 text = <fs_dept>-dept_name dept_id = <fs_dept>-dept_id emp_id = '' emp_name = '' ) ). " 记录当前部门节点的键,用于挂载其员工 DATA(lv_dept_node_key) = lv_index. " 4. 循环该部门下的员工,作为第二级子节点(叶子节点) LOOP AT lt_emp ASSIGNING FIELD-SYMBOL(<fs_emp>) WHERE dept_id = <fs_dept>-dept_id. lv_index = lv_index + 1. gt_tree_data = VALUE #( BASE gt_tree_data ( node_key = lv_index relatkey = lv_dept_node_key " 父键是所属部门节点 relatship = cl_gui_column_tree=>relat_last_child isfolder = abap_false " 员工是叶子节点,不可展开 text = <fs_emp>-emp_name dept_id = <fs_dept>-dept_id emp_id = <fs_emp>-emp_id emp_name = <fs_emp>-emp_name ) ). ENDLOOP. ENDLOOP. ENDMETHOD.

关键点解析:

  • relat_last_child是一个常量,表示将新节点添加为指定父节点的最后一个子节点。这是最常用的关系。
  • 通过动态计算和保存lv_dept_node_key,我们精确地建立了“部门-员工”的父子链接。
  • isfolder字段根据节点是否有子节点来设置,这决定了树控件是否显示展开图标。

3.2 步骤二:创建并配置SALV树对象

数据准备好后,就可以创建CL_SALV_TREE对象并绑定数据了。

METHOD create_tree. DATA: lo_tree TYPE REF TO cl_salv_tree. DATA: lo_settings TYPE REF TO cl_salv_tree_settings. DATA: lx_msg TYPE REF TO cx_salv_msg. TRY. " 1. 使用工厂方法创建树对象,并传入数据 cl_salv_tree=>factory( EXPORTING r_container = go_container " 屏幕上的容器对象,如CL_GUI_CUSTOM_CONTAINER container_name = 'CC_TREE' " 或使用容器名称 IMPORTING r_salv_tree = lo_tree CHANGING t_table = gt_tree_data ). " 2. 获取并设置树的显示属性 lo_settings = lo_tree->get_tree_settings( ). lo_settings->set_hierarchy_header( '组织架构' ). " 设置层次列标题 lo_settings->set_hierarchy_size( 30 ). " 设置层次列宽度 lo_settings->set_hierarchy_icon( abap_true ). " 显示文件夹/叶子图标 " 3. 获取列模型并配置各列 DATA(lo_columns) = lo_tree->get_columns( ). lo_columns->set_optimize( abap_true ). " 自动优化列宽 " 设置特定列的属性 TRY. DATA(lo_column) = lo_columns->get_column( 'DEPT_ID' ). lo_column->set_visible( abap_false ). " 隐藏部门ID列,因为已在层次中体现 CATCH cx_salv_not_found. " 列不存在,忽略 ENDTRY. TRY. lo_column = lo_columns->get_column( 'EMP_NAME' ). lo_column->set_long_text( '员工姓名' ). lo_column->set_medium_text( '姓名' ). lo_column->set_short_text( '名' ). CATCH cx_salv_not_found. ENDTRY. " 4. 设置排序(可选) DATA(lo_sorts) = lo_tree->get_sorts( ). lo_sorts->add_sort( columnname = 'DEPT_ID' sequence = if_salv_c_sort=>sort_up ). " 5. 显示树 lo_tree->display( ). " 将对象引用保存到全局变量,以便后续使用 go_tree = lo_tree. CATCH cx_salv_msg INTO lx_msg. " 异常处理:显示错误信息 MESSAGE lx_msg->get_text( ) TYPE 'E'. ENDTRY. ENDMETHOD.

踩坑提示2:容器(Container)的生命周期管理CL_SALV_TREE=>FACTORY方法需要一个容器。在SAP的Screen Painter(屏幕绘制器)中,你需要在屏幕上放置一个Custom Control,并为其指定一个名称(如CC_TREE)。在PBO(Process Before Output)事件中,你需要实例化一个CL_GUI_CUSTOM_CONTAINER对象,并将其与这个控件名称绑定。关键点在于:这个容器对象必须在整个屏幕会话期间保持有效。我曾犯过一个错误,在每次调用create_tree方法时都新建一个容器对象,导致旧的树控件没有被正确释放,内存泄漏且界面混乱。正确的做法是在程序生命周期内只创建一次容器对象。

3.3 步骤三:处理树节点的交互事件

静态的树意义有限,我们需要响应用户的点击、展开等操作。CL_SALV_TREE通过事件(Event)来处理交互。

METHOD register_events. DATA: lo_events TYPE REF TO cl_salv_events_tree. IF go_tree IS INITIAL. RETURN. ENDIF. lo_events = go_tree->get_event( ). " 1. 注册节点双击事件 SET HANDLER handle_node_double_click FOR lo_events. " 2. 注册节点右键点击事件(用于弹出菜单) SET HANDLER handle_node_context_menu FOR lo_events. " 3. 注册节点展开/折叠事件 SET HANDLER handle_expand_no_children FOR lo_events. ENDMETHOD. " 事件处理器方法示例:处理节点双击 METHOD handle_node_double_click. " 参数 `NODE_KEY` 包含了被双击节点的键值 DATA: ls_node_data TYPE ty_s_node. " 根据节点键,从数据内表 `gt_tree_data` 中读取对应的行数据 READ TABLE gt_tree_data INTO ls_node_data WITH KEY node_key = node_key. IF sy-subrc = 0. " 判断节点类型,执行不同操作 IF ls_node_data-isfolder = abap_true. " 如果是文件夹节点,可以执行一些逻辑,如加载其子节点的详细信息 MESSAGE |您双击了文件夹: { ls_node_data-text }| TYPE 'I'. ELSE. " 如果是叶子节点(如员工),可以弹出详情对话框或跳转到其他事务 MESSAGE |您双击了员工: { ls_node_data-emp_name },工号: { ls_node_data-emp_id }| TYPE 'I'. " 这里可以调用 CALL TRANSACTION, CALL SCREEN 等 ENDIF. ENDIF. ENDMETHOD.

事件处理逻辑的核心在于通过事件参数(如node_key)回溯到原始数据内表,获取完整的业务信息,从而执行相应的业务逻辑。这实现了树形界面与后台数据的无缝联动。

4. 高级功能与性能优化实战

一个基础的树状ALV实现后,我们往往会面临更复杂的需求和性能挑战。

4.1 动态加载与虚拟模式

当数据量极大时(例如数十万个节点),一次性加载所有数据到前端会严重拖慢程序响应速度。此时需要使用动态加载(On-Demand Loading)虚拟模式(Virtual Mode)

  • 动态加载: 初始只加载顶层节点。当用户点击展开一个文件夹节点时,系统触发一个事件(如handle_expand_no_children),在这个事件的处理方法中,再去动态查询该节点下的子数据,并添加到树中。

    METHOD handle_expand_no_children. DATA: lt_child_data TYPE TABLE OF ty_s_node. " 根据展开的节点键 `node_key`,去数据库查询其子数据 SELECT ... INTO CORRESPONDING FIELDS OF TABLE lt_child_data FROM database_table WHERE parent_field = get_parent_id_from_key( node_key ). " 将查询到的子数据,通过树的 ADD_NODE 或相关方法动态添加到当前节点下 go_tree->add_nodes( table_structure_name = 'TY_S_NODE' table_line = lt_child_data ). ENDMETHOD.

    这种方式将数据加载压力分散到用户操作中,适合深度和广度都很大的树。

  • 虚拟模式CL_SALV_TREE本身对虚拟模式的支持不如CL_GUI_ALV_GRID直接。一种实践方案是,结合动态加载,只维护一个当前可见路径上的数据子集。当用户展开或滚动时,动态替换或追加数据。这需要更精细的缓存管理和数据状态控制。

性能优化心得:NODE_KEY的设计直接影响性能在动态加载场景下,NODE_KEY最好能直接或间接映射到数据库查询条件。例如,如果节点代表一个物料,NODE_KEY可以直接是物料号;如果节点是某个层级,NODE_KEY可以设计为‘LEVEL1-LEVEL2’的格式。这样在handle_expand_no_children事件中,可以直接解析NODE_KEY来构造SQL的WHERE条件,避免再去内表中做二次查找,能极大提升响应速度。

4.2 自定义节点图标与样式

为了让树更直观,我们经常需要为不同类型的节点设置不同的图标。

METHOD set_custom_icons. DATA: lo_nodes TYPE REF TO cl_salv_nodes. DATA: lo_node TYPE REF TO cl_salv_node. lo_nodes = go_tree->get_nodes( ). LOOP AT gt_tree_data ASSIGNING FIELD-SYMBOL(<fs_data>). TRY. lo_node = lo_nodes->get_node( <fs_data>-node_key ). IF <fs_data>-isfolder = abap_true. " 设置文件夹图标:区分不同部门类型 CASE <fs_data>-dept_type. " 假设有一个部门类型字段 WHEN 'SALES'. lo_node->set_icon( icon_sales_order ). WHEN 'HR'. lo_node->set_icon( icon_hr ). WHEN OTHERS. lo_node->set_icon( icon_folder_closed ). ENDCASE. ELSE. " 设置叶子节点图标:区分员工状态 CASE <fs_data>-emp_status. " 假设有一个员工状态字段 WHEN 'ACTIVE'. lo_node->set_icon( icon_led_green ). WHEN 'INACTIVE'. lo_node->set_icon( icon_led_red ). WHEN OTHERS. lo_node->set_icon( icon_user ). ENDCASE. ENDIF. " 还可以设置文本颜色、背景色等 IF <fs_data>-emp_id IS INITIAL. " 假设工号为空是虚拟节点 lo_node->set_text_color( col_negative ). " 红色文本 ENDIF. CATCH cx_salv_not_found. " 节点未找到,可能尚未加载,忽略 ENDTRY. ENDLOOP. ENDMETHOD.

通过set_icon方法,可以引用SAP系统内置的大量图标常量(以ICON_开头),这能让你的树形报表在用户体验上提升一个档次。

4.3 集成工具栏与批量操作

CL_SALV_TREE自带了一个功能丰富的工具栏。我们可以轻松地添加自定义按钮,并实现针对选中节点的批量操作。

METHOD add_custom_functions. DATA: lo_functions TYPE REF TO cl_salv_functions_tree. lo_functions = go_tree->get_functions( ). lo_functions->set_all( abap_true ). " 显示所有标准功能 " 添加一个自定义按钮:批量发送邮件 lo_functions->add_function( name = 'SEND_EMAIL' icon = icon_mail text = '发送邮件' tooltip = '向选中员工发送邮件' position = if_salv_c_function_position=>right_of_salv ). ENDMETHOD. " 然后需要处理自定义功能的事件 METHOD handle_user_command. CASE e_salv_function. WHEN 'SEND_EMAIL'. perform_send_email_to_selected( ). WHEN OTHERS. " 处理其他标准或自定义功能 ENDCASE. ENDMETHOD. METHOD perform_send_email_to_selected. DATA: lt_selected_nodes TYPE salv_t_node_ref. DATA: lr_selected_node TYPE REF TO cl_salv_node. " 获取所有选中的节点 lt_selected_nodes = go_tree->get_selected_nodes( ). IF lt_selected_nodes IS INITIAL. MESSAGE '请先选择至少一个员工节点' TYPE 'W'. RETURN. ENDIF. LOOP AT lt_selected_nodes INTO lr_selected_node. DATA(lv_node_key) = lr_selected_node->get_key( ). " 根据 node_key 找到业务数据,收集邮箱等信息 READ TABLE gt_tree_data ... " 省略查找逻辑 " 执行发送邮件逻辑... ENDLOOP. ENDMETHOD.

这里的关键是get_selected_nodes方法,它返回了用户当前选中的所有节点对象的引用,使得批量处理变得非常直接。

5. 常见问题排查与调试技巧

即使按照步骤操作,在实际开发中你还是会遇到各种“诡异”的问题。下面分享几个我踩过的坑和解决方法。

5.1 树形结构不显示或显示错乱

这是最常见的问题,通常由以下几个原因导致:

  1. 数据内表gt_tree_dataNODE_KEY不唯一: 这是最致命的错误。必须确保每个节点的node_key在整个内表中是唯一的。建议在填充数据后,使用SORT gt_tree_data BY node_key.DELETE ADJACENT DUPLICATES FROM gt_tree_data COMPARING node_key.进行检查和清理。
  2. 父子关系(RELATKEY)指向了不存在的NODE_KEY: 即子节点的relatkey在内表中找不到对应的父节点node_key。这会导致该子节点无法挂载到树上。在填充数据时,务必确保父节点先于子节点被插入内表,并且键值引用正确。可以通过写一个简单的校验程序来检查所有relatkey是否都存在于node_key集合中。
  3. ISFOLDER标志设置错误: 如果一个节点实际有子节点,但isfolder被设为abap_false,则它不会显示展开图标,用户也无法看到其子节点。反之,如果一个节点是叶子节点却被设为abap_true,用户点击展开图标后可能没有任何反应(或触发handle_expand_no_children事件)。务必根据数据实际情况正确设置此字段。

调试建议: 在调用lo_tree->display( )之前,将你的gt_tree_data内表输出到调试器或通过CL_DEMO_OUTPUT=>DISPLAY_DATA显示出来。逐行检查node_key,relatkey,isfolder这三个核心字段的值,确保其逻辑自洽。

5.2 事件不触发或触发异常

  1. 事件处理器未正确注册: 确保SET HANDLER语句在树对象创建(cl_salv_tree=>factory之后执行,并且指向的实例方法所属的对象是存在的(即go_tree不为空)。
  2. 容器或屏幕流逻辑问题: 在复杂的屏幕中,如果容器在PBO/PVA(Process After Input)事件中被意外重建,会导致树对象及其事件绑定丢失。确保你的屏幕流逻辑(PBO/PVA)是稳定的,树和容器的创建只在必要时(如第一次加载屏幕)进行。
  3. HANDLE_EXPAND_NO_CHILDREN的误用: 这个事件在用户点击一个isfolder = abap_true但当前没有子节点数据的节点时触发。如果你已经预加载了所有子节点数据,这个事件就不会触发。如果你需要处理所有的展开动作(无论是否有子节点),可能需要结合其他方法,或者监听标准工具栏的展开/折叠事件。

5.3 性能瓶颈分析与优化

当树节点很多(>5000)时,可能会感到卡顿。除了前述的动态加载,还可以:

  1. 减少层次列宽度: 通过lo_settings->set_hierarchy_size( )设置一个合理的宽度,避免过宽影响渲染。
  2. 精简输出字段: 只将必要字段放入gt_tree_data。特别是避免将很长的字符串(如备注、长文本)放入,除非它们必须显示。
  3. 延迟图标和样式设置: 在set_custom_icons方法中,如果节点数量巨大,循环设置每个节点的图标会成为性能瓶颈。可以考虑只对可见区域或特定类型的节点设置图标,或者使用更简单的图标方案。
  4. 使用CL_SALV_TREE而非CL_GUI_COLUMN_TREE+CL_GUI_ALV_GRID: 前者是更高层次的封装,SAP对其做了更多性能优化。在大多数场景下,它的性能表现优于手动拼接的方案。

6. 附:完整可运行源码框架

由于篇幅限制,这里提供一个高度浓缩但结构完整、可直接在SE38中创建程序并填充运行的代码框架。你需要根据你的实际数据表替换其中的数据获取逻辑。

REPORT zdemo_tree_alv. *&---------------------------------------------------------------------* *& 数据类型定义 *&---------------------------------------------------------------------* TYPES: BEGIN OF ty_s_node, node_key TYPE tm_nodekey, relatkey TYPE tm_nodekey, relatship TYPE tm_nodeship, isfolder TYPE tm_boolean, text TYPE lvc_value, dept_id TYPE string, emp_id TYPE string, emp_name TYPE string, END OF ty_s_node. DATA: gt_tree_data TYPE TABLE OF ty_s_node. DATA: go_container TYPE REF TO cl_gui_custom_container. DATA: go_tree TYPE REF TO cl_salv_tree. *&---------------------------------------------------------------------* *& 屏幕定义 (Screen 100) *&---------------------------------------------------------------------* SELECTION-SCREEN BEGIN OF SCREEN 100 AS SUBSCREEN. SELECTION-SCREEN BEGIN OF BLOCK b1 WITH FRAME TITLE TEXT-t01. SELECTION-SCREEN END OF BLOCK b1. SELECTION-SCREEN END OF SCREEN 100. *&---------------------------------------------------------------------* *& START-OF-SELECTION *&---------------------------------------------------------------------* START-OF-SELECTION. CALL SCREEN 100. *&---------------------------------------------------------------------* *& MODULE STATUS_0100 OUTPUT *&---------------------------------------------------------------------* MODULE status_0100 OUTPUT. SET PF-STATUS 'STATUS_100'. SET TITLEBAR 'TITLE_100'. IF go_container IS INITIAL. " 创建容器 CREATE OBJECT go_container EXPORTING container_name = 'CC_TREE'. " 构建数据 perform_build_data( ). " 创建并显示树 perform_create_tree( ). " 注册事件 perform_register_events( ). ENDIF. ENDMODULE. *&---------------------------------------------------------------------* *& MODULE USER_COMMAND_0100 INPUT *&---------------------------------------------------------------------* MODULE user_command_0100 INPUT. CASE sy-ucomm. WHEN 'BACK' OR 'EXIT' OR 'CANCEL'. LEAVE PROGRAM. ENDCASE. ENDMODULE. *&---------------------------------------------------------------------* *& FORM perform_build_data *&---------------------------------------------------------------------* FORM perform_build_data. " 这里模拟数据,实际应从数据库读取 DATA: lv_key TYPE i VALUE 1. " 根节点 gt_tree_data = VALUE #( BASE gt_tree_data ( node_key = |{ lv_key }| relatkey = '' relatship = cl_gui_column_tree=>relat_last_child isfolder = abap_true text = '公司组织' dept_id = '' emp_id = '' emp_name = '' ) ). DATA(lv_root_key) = |{ lv_key }|. lv_key = lv_key + 1. " 部门节点 gt_tree_data = VALUE #( BASE gt_tree_data ( node_key = |{ lv_key }| relatkey = lv_root_key relatship = cl_gui_column_tree=>relat_last_child isfolder = abap_true text = '销售部' dept_id = 'DEPT_SALES' emp_id = '' emp_name = '' ) ). DATA(lv_sales_key) = |{ lv_key }|. lv_key = lv_key + 1. " 员工节点 (叶子) gt_tree_data = VALUE #( BASE gt_tree_data ( node_key = |{ lv_key }| relatkey = lv_sales_key relatship = cl_gui_column_tree=>relat_last_child isfolder = abap_false text = '张三' dept_id = 'DEPT_SALES' emp_id = 'EMP001' emp_name = '张三' ) ). lv_key = lv_key + 1. gt_tree_data = VALUE #( BASE gt_tree_data ( node_key = |{ lv_key }| relatkey = lv_sales_key relatship = cl_gui_column_tree=>relat_last_child isfolder = abap_false text = '李四' dept_id = 'DEPT_SALES' emp_id = 'EMP002' emp_name = '李四' ) ). ENDFORM. *&---------------------------------------------------------------------* *& FORM perform_create_tree *&---------------------------------------------------------------------* FORM perform_create_tree. TRY. cl_salv_tree=>factory( EXPORTING r_container = go_container IMPORTING r_salv_tree = go_tree CHANGING t_table = gt_tree_data ). DATA(lo_settings) = go_tree->get_tree_settings( ). lo_settings->set_hierarchy_header( '部门/员工' ). lo_settings->set_hierarchy_size( 40 ). DATA(lo_columns) = go_tree->get_columns( ). lo_columns->set_optimize( abap_true ). go_tree->display( ). CATCH cx_salv_msg INTO DATA(lx_msg). MESSAGE lx_msg->get_text( ) TYPE 'E'. ENDTRY. ENDFORM. *&---------------------------------------------------------------------* *& FORM perform_register_events *&---------------------------------------------------------------------* FORM perform_register_events. DATA: lo_events TYPE REF TO cl_salv_events_tree. lo_events = go_tree->get_event( ). SET HANDLER handle_double_click FOR lo_events. ENDFORM. *&---------------------------------------------------------------------* *& CLASS lcl_event_handler DEFINITION *&---------------------------------------------------------------------* CLASS lcl_event_handler DEFINITION. PUBLIC SECTION. CLASS-METHODS: handle_double_click FOR EVENT double_click OF cl_salv_events_tree IMPORTING node_key. ENDCLASS. *&---------------------------------------------------------------------* *& CLASS lcl_event_handler IMPLEMENTATION *&---------------------------------------------------------------------* CLASS lcl_event_handler IMPLEMENTATION. METHOD handle_double_click. DATA: ls_data TYPE ty_s_node. READ TABLE gt_tree_data INTO ls_data WITH KEY node_key = node_key. IF sy-subrc = 0. MESSAGE |双击了: { ls_data-text }| TYPE 'I'. ENDIF. ENDMETHOD. ENDCLASS.

将上述代码复制到SAP SE38事务码中,创建一个程序,并在屏幕绘制器(SE51)中为屏幕100添加一个名为CC_TREECustom Control,运行后你就能看到一个最基本的、可交互的树状ALV。从这个框架出发,你可以逐步添加上文所述的高级功能,构建出满足复杂业务需求的树形报表。记住,理解数据模型和事件流是掌握树状ALV的关键,多实践几次,这些概念就会变得非常清晰。

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

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

立即咨询