ARTICLE DETAIL

资讯详情

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

SAP ABAP ALV单元格下拉框配置实战:从FM到OO ALV

SAP ABAP ALV单元格下拉框配置实战:从FM到OO ALV 做SAP ABAP开发谁还没被ALV报表虐过几回。报表要能编辑单元格要加下拉这在业务顾问嘴里就是一句话“加个下拉框就行”但真落到ABAP代码里涉及的细节比想象中多得多。本文就围绕ALV单元格下拉框配置这个话题把F4弹窗、内嵌下拉、行级动态选项、回车校验这些常见玩法一次讲透并附上可以直接复制到SE38的完整代码。不管你是在做SD交货单维护、MM主数据扩展、还是自开发的审批界面这个需求几乎天天能用上。我会先讲原理再给快速跑通的Demo最后把几年实战里踩过的坑全部列出来。老项目还在用REUSE_ALV_GRID_DISPLAY的也有对应的改造思路建议先收藏。1. 先搞明白ALV里的“下拉框”到底有几种1.1 别把“弹窗选择”和“内嵌下拉”混为一谈很多初学者一听说“ALV下拉框”第一反应就是调用F4IF_INT_TABLE_VALUE_REQUEST弹一个选择列表。这种方式在技术上叫“F4值请求”它的交互形态是弹出一个独立的小窗口用户选中后把值回填到当前单元格本质是搜索帮助的轻量版。另一种才是真正意义上的“单元格下拉框”用户点击单元格后单元格右侧会出现一个向下的小箭头点箭头直接拉出列表值就嵌在表格里面不需要弹出新窗口。在OO ALVCL_GUI_ALV_GRID里这是通过字段目录的DRDN_HNDL/DRDN_FIELD实现的也就是大家经常在技术群里问的“下拉按钮怎么配置”。这两种方案的代码路径完全不同。FM方式的REUSE_ALV_GRID_DISPLAY要注册F4事件回调OO方式要维护下拉值表和句柄。搞混了很容易出现“有下拉箭头但是弹不出列表”或者“能弹窗但是单元格不显示可编辑状态”这种怪问题。1.2 单元格下拉的触发机制为什么内表要多个辅助字段内嵌下拉的核心逻辑是这样的ALV在渲染每个可编辑单元格之前会去检查这个单元格是否被标记为“带下拉功能”。如果字段目录里指定了DRDN_HNDL整列都会用同一个句柄对应的值列表如果指定了DRDN_FIELDALV就会去读当前数据行里那个辅助字段的值再根据这个值到下拉值表里面找对应的列表。这就是为什么很多例子里内表会多出一个DRDN_HNDL字段它是给ALV“看”的不是给业务用户看的。实际项目里经常遇到“这一列大多数行可以下拉少数行必须手工输入”的需求靠DRDN_HNDL整列统一设置是搞不定的必须用DRDN_FIELD配合行内句柄字段。1.3 到底该用哪种方案一张表说清楚需求场景推荐方案理由快速验证、报表临时加个选择列FM F4事件代码量最小不依赖Screen生产环境、用户习惯点击单元格下拉OO ALV DRDN_HNDL交互体验最接近Web下拉同一列不同行要显示不同枚举OO ALV DRDN_FIELD行内句柄天然支持动态区分老程序改造不想动原有ALV架构继续用FM F4事件改动最小风险最低2. 5分钟快速实战FM方式先跑通一个可编辑下拉选择2.1 为什么先用FM版本做验证如果你只是想看看“ALV可编辑单元格下拉选择”到底长什么样我强烈建议先用REUSE_ALV_GRID_DISPLAY写一个最小Demo。原因很简单REUSE FM不需要创建Screen不需要Docking Container不需要事件接收类一个START-OF-SELECTION就能直接出ALV列表。对刚上手ABAP的人来说这是最快拿到反馈的路径。下面的代码可以直接粘到SE38里跑前提是系统里有SFLIGHT航班表ECC和S4 HANA标准教学数据一般都有。如果没有把SELECT语句换成任意一张有几条数据的表即可。2.2 FM版完整代码REPORT z_alv_dropdown_fm. DATA: BEGIN OF gt_data OCCURS 0, carrid TYPE sflight-carrid, connid TYPE sflight-connid, status TYPE char20, END OF gt_data. DATA: gt_fcat TYPE slis_t_fieldcat_alv, gs_fcat TYPE slis_fieldcat_alv, gt_events TYPE slis_t_event, gs_event TYPE slis_alv_event, gt_exit TYPE slis_t_exit, gs_exit TYPE slis_event_exit. INITIALIZATION. PERFORM fill_data. PERFORM fill_fcat. PERFORM fill_events. START-OF-SELECTION. CALL FUNCTION REUSE_ALV_GRID_DISPLAY EXPORTING i_callback_program sy-repid it_fieldcat gt_fcat it_events gt_events it_event_exit gt_exit TABLES t_outtab gt_data. IF sy-subrc 0. MESSAGE ALV展示失败 TYPE S DISPLAY LIKE E. ENDIF. FORM fill_data. SELECT carrid connid FROM sflight INTO CORRESPONDING FIELDS OF TABLE gt_data UP TO 20 ROWS. LOOP AT gt_data ASSIGNING FIELD-SYMBOL(fs). fs-status OPEN. ENDLOOP. ENDFORM. FORM fill_fcat. CLEAR gs_fcat. gs_fcat-fieldname CARRID. gs_fcat-ref_table SFLIGHT. APPEND gs_fcat TO gt_fcat. CLEAR gs_fcat. gs_fcat-fieldname CONNID. gs_fcat-ref_table SFLIGHT. APPEND gs_fcat TO gt_fcat. CLEAR gs_fcat. gs_fcat-fieldname STATUS. gs_fcat-ref_table GT_DATA. gs_fcat-edit X. 必须可编辑F4事件才会触发 APPEND gs_fcat TO gt_fcat. ENDFORM. FORM fill_events. CLEAR gs_event. gs_event-name CALLER_EXIT. APPEND gs_event TO gt_events. CLEAR gs_exit. gs_exit-f4 X. gs_exit-user_command X. APPEND gs_exit TO gt_exit. ENDFORM. FORM caller_exit USING ucomm TYPE syucomm. 这里留给按钮处理后面保存校验会用到 ENDFORM. FORM f4 USING fieldname TYPE slis_fieldname selfield TYPE slis_selfield. DATA: lt_return TYPE TABLE OF char20, ls_return LIKE LINE OF lt_return. IF fieldname STATUS. ls_return OPEN. APPEND ls_return TO lt_return. ls_return IN_PROG. APPEND ls_return TO lt_return. ls_return CLOSED. APPEND ls_return TO lt_return. CALL FUNCTION F4IF_INT_TABLE_VALUE_REQUEST EXPORTING retfield STATUS value_org S TABLES value_tab lt_return EXCEPTIONS parameter_error 1 no_values_found 2 OTHERS 3. IF sy-subrc 0. selfield-refresh X. ENDIF. ENDIF. ENDFORM.2.3 跑通之后你会看到什么运行后STATUS列是可编辑的光标点进去按F4或者用回车确认键就会弹出OPEN/IN_PROG/CLOSED三个值的选择列表。选完值后单元格里就会变成你选择的文本。这个版本胜在极短适合用来看清整套F4回调的骨架。但它有个天生的局限用户必须主动按F4或触发值请求表格本身不会出现那个明显的小箭头。业务用户如果习惯Excel那种“点单元格右边就有箭头”的体验这个方案会被吐槽“没有下拉框”。2.4 我建议的生产判断如果这个ALV只是内部顾问自己用的工具FM方案完全够用维护成本极低。如果是给最终用户批量录入数据用的建议直接跳到下一章用OO ALV实现真正的内嵌下拉用户体验完全不一样一次投入后面可以持续复用。3. OO ALV真正内嵌下拉同一列不同行选项还能不一样3.1 明确DRDN_HNDL和DRDN_FIELD的分工OO ALV的字段目录LVC_S_FCAT里有两个长得特别像的字段DRDN_HNDL和DRDN_FIELD。这两个是整套下拉配置的关键但作用截然不同。DRDN_HNDL是列级别的句柄你直接给字段目录的DRDN_HNDL赋个整数比如1那么这一列所有单元格都会共用句柄1对应的下拉列表。DRDN_FIELD则是指向数据内表里的某个字段这个字段里存的是每行自己的句柄值ALV渲染某一行的下拉时会先读这行的句柄值再去下拉值表里匹配对应列表。所以“行级动态下拉”这句话翻译成代码动作就是两步第一步数据内表里准备一个整数类型的句柄字段第二步字段目录的DRDN_FIELD指向这个字段名。3.2 下拉值表的结构LVC_DROP下拉值表的行类型是LVC_DROP一共两个关键字段HANDLE和VALUE。HANDLE是整数用来分组VALUE是显示在列表里的具体值。同一个HANDLE下面挂多个VALUE就组成一组下拉选项。我见过有人误把TABLE行结构当成一个VALUE字段的简单表结果下拉列表永远是空的。记住一定要用LVC_T_DROP这种带句柄的结构。注册的时候调用GRID的SET_DROP_DOWN_TABLE方法把整个值表传进去ALV才能把“句柄值列表”的映射关系建立起来。3.3 核心代码演示TYPES: BEGIN OF ty_data, carrid TYPE sflight-carrid, connid TYPE sflight-connid, status TYPE char20, drdn_hndl TYPE int4, 行级下拉句柄只服务ALV渲染 END OF ty_data. DATA: gt_data TYPE TABLE OF ty_data, gs_data TYPE ty_data, gt_fcat TYPE lvc_t_fcat, gs_fcat TYPE lvc_s_fcat, gs_layout TYPE lvc_s_layo, gt_drop TYPE lvc_t_drop, gs_drop TYPE lvc_drop, go_dock TYPE REF TO cl_gui_docking_container, go_grid TYPE REF TO cl_gui_alv_grid. * 1) 构建下拉值表 CLEAR gs_drop. gs_drop-handle 1. gs_drop-value OPEN. APPEND gs_drop TO gt_drop. gs_drop-value IN_PROG. APPEND gs_drop TO gt_drop. gs_drop-value CLOSED. APPEND gs_drop TO gt_drop. CLEAR gs_drop. gs_drop-handle 2. gs_drop-value DRAFT. APPEND gs_drop TO gt_drop. gs_drop-value FINAL. APPEND gs_drop TO gt_drop. * 2) 填充数据时按行设置句柄 LOOP AT gt_data ASSIGNING FIELD-SYMBOL(fs). IF fs-status DRAFT. fs-drdn_hndl 2. ELSE. fs-drdn_hndl 1. ENDIF. ENDLOOP. * 3) 字段目录关键配置 CLEAR gs_fcat. gs_fcat-fieldname STATUS. gs_fcat-ref_table SFLIGHT. gs_fcat-edit X. gs_fcat-drdn_field DRDN_HNDL. 行级动态句柄 APPEND gs_fcat TO gt_fcat. * 4) 注册下拉表 展示 CREATE OBJECT go_dock EXPORTING repid sy-repid dynnr sy-dynnr extension 3000. CREATE OBJECT go_grid EXPORTING i_parent go_dock. CALL METHOD go_grid-set_drop_down_table EXPORTING it_drop_down gt_drop. CALL METHOD go_grid-set_table_for_first_display EXPORTING is_layout gs_layout CHANGING it_outtab gt_data it_fieldcatalog gt_fcat.3.4 为什么我强烈推荐DRDN_FIELD模式实际项目里我几乎不用DRDN_HNDL整列固定因为业务约束永远比想象的复杂。交货单状态有些行允许改成“已发货”有些行只允许改成“部分发货”物料主数据有些行允许改成“删除标记”有些行不允许。这些规则用行级句柄字段控制非常自然在填充数据时根据业务逻辑把句柄算好ALV的渲染逻辑一行都不用改。DRDN_FIELD模式还有一个隐性好处当你需要在界面上动态改变某些行的可下拉范围时只要修改内表对应行的句柄值然后刷新GRID下拉列表立刻变化。不需要重建字段目录也不需要重传下拉值表这对交互频繁的维护界面特别友好。4. 用户改完怎么校验回车事件与数据保存4.1 可编辑ALV的另一半注册编辑事件下拉框配置好只是第一步用户选完值之后程序必须能感知“单元格被改过”才能做校验和保存。OO ALV的DATA_CHANGED事件就是干这个的。很多人把事件注册漏了写完下拉发现“改完单元格没反应”其实不是下拉的问题是事件压根没挂上。需要调用两个方法REGISTER_EDIT_EVENT事件ID分别是CL_GUI_ALV_GRIDMC_EVT_ENTER回车和CL_GUI_ALV_GRIDMC_EVT_MODIFIED单元格内容变更然后通过SET HANDLER把本地类里的事件处理方法挂到GRID实例上。CLASS lcl_event DEFINITION. PUBLIC SECTION. CLASS-METHODS handle_data_changed FOR EVENT data_changed OF cl_gui_alv_grid IMPORTING er_data_changed. ENDCLASS. CLASS lcl_event IMPLEMENTATION. METHOD handle_data_changed. IF g_refresh abap_true. RETURN. ENDIF. LOOP AT er_data_changed-mt_mod_cells INTO DATA(ls_mod). IF ls_mod-fieldname STATUS. IF ls_mod-value FINAL AND gs_data-carrid LH. MESSAGE LH航班不允许终态 TYPE S DISPLAY LIKE E. er_data_changed-refresh_data( ). g_refresh abap_true. go_grid-refresh_table_display( ). g_refresh abap_false. RETURN. ENDIF. ENDIF. ENDLOOP. ENDMETHOD. ENDCLASS.4.2 校验失败如何回滚两个坑必须避开第一个坑是直接MESSAGE TYPE E。在DATA_CHANGED事件里弹错误消息用户点完确定单元格值可能已经写进内表了界面却显示还是旧值数据处于一种“半改半不改”的状态。更稳的做法是用事件参数里的REFRESH_DATA方法强制让GRID重新读取内表数据把界面恢复成内表当前的实际值再给一个黄色加感叹号的提示。第二个坑是刷新死循环。调用REFRESH_TABLE_DISPLAY会导致GRID重新渲染而重新渲染又有可能再次触发DATA_CHANGED如果事件方法里没有任何保护会看到界面疯狂闪烁甚至崩溃。所以我习惯在类里定义一个G_REFRESH标志刷新前置为真事件方法一进来先判断是刷新触发的就什么都不做。4.3 保存按钮里的二次校验事件里的校验只是“过程校验”真正的保存动作一定要在按钮处理逻辑里再校验一次。因为用户可能改了值之后根本没触发回车或者触发了但还是绕过了一些边界条件。保存时重新检查一遍GT_DATA不合格的直接回滚错误提示不更新数据库。FORM save_data. LOOP AT gt_data INTO gs_data. IF gs_data-status NOT IN (OPEN, IN_PROG, CLOSED). MESSAGE 存在非法状态值请检查后再保存 TYPE E. RETURN. ENDIF. ENDLOOP. 校验通过后执行UPDATE/MODIFY数据库操作 MODIFY sflight FROM TABLE gt_data. IF sy-subrc 0. COMMIT WORK. MESSAGE 保存成功 TYPE S. ELSE. ROLLBACK WORK. MESSAGE 保存失败 TYPE E. ENDIF. ENDFORM.4.4 为什么我建议事件里“少做业务判断”DATA_CHANGED事件里最忌讳写大段业务逻辑它每改一个单元格都可能触发频率很高。之前接过一个项目开发在事件里查了一堆主数据结果用户每改一格就卡一两秒体验极差。我后来的习惯是事件里只做轻量校验比如枚举合法性、必填项涉及数据库查询、跨表校验的逻辑全部放到保存按钮里统一做。这样界面响应快逻辑也更好维护。5. 老项目FM版改造思路F4事件也能实现下拉5.1 两代方案的核心差异回到FM版的REUSE_ALV_GRID_DISPLAY前面给了快速Demo但它和OO ALV的架构差异很大。FM版没有明确的“下拉值表句柄”概念下拉效果基本靠F4事件回调加F4IF_INT_TABLE_VALUE_REQUEST实现。它的字段目录SLIS_S_FCAT里其实也有一个DROP_DOWN_FIELD字段可以把单元格渲染成带下拉箭头的样式但这个字段要求数据内表里额外放一个SLIS_TABTYPE类型的辅助表字段非常绕而且对数据类型卡得严。我在老项目里见过几次这种写法维护起来头大如果你只是改别人写好的程序不建议动这个机制容易牵一发动全身。新写代码优先考虑OO ALV。5.2 FM转OO的迁移建议老程序改造最怕的是功能没变界面却变了个样业务不接受。我的建议是第一把FM方式的字段目录SLIS_T_FIELDCAT_ALV换成LVC_T_FCAT不是一个字段一个字段翻译而是直接用新查询逻辑生成第二原来的F4回调FORM f4全部改成事件接收类里的DATA_CHANGED和自定义USER_COMMAND第三数据内表增加DRDN_HNDL辅助字段让下拉逻辑彻底脱离F4弹窗。5.3 什么时候还在用FMFM版也不是一无是处。没有屏幕、不需要容器、代码量少做快速测试和临时分析工具非常合适。如果报表只是展示加简单交互或者你要在几千行代码的老程序里快速加一个下拉枚举FM版是最安全的改动。等需求明确要长期维护再考虑花半天时间迁移到OO。6. 高频问题排查速查表与实战避坑6.1 10个高频问题速查现象根本原因解决方案单元格不能编辑EDIT没设X字段目录或布局里设置EDIT X下拉箭头出来了但列表为空下拉值表没注册调用SET_DROP_DOWN_TABLE列表为空且值表已注册句柄对不上检查数据行的DRDN_HNDL和值表HANDLE整列都下拉但只想部分行可下拉使用了DRDN_HNDL改用DRDN_FIELD配合行内句柄下拉选完值单元格不显示新值缺REFRESHF4事件返回后设置SELFIELD-REFRESH X改完值回车没反应事件没注册REGISTER_EDIT_EVENT SET HANDLER校验失败后界面闪烁/死循环刷新无保护标志用G_REFRESH标志拦截递归触发FM版F4事件不触发EVENT_EXIT里F4没设X在IT_EVENT_EXIT中F4 X想下拉显示中文库里存代码值表直接传了字段使用VALUE/TEXT分离的返回表保存时取不到最新值从GRID取而不是内表取统一从GT_DATA内表读取6.2 我最想提醒的一个坑下拉值表的VALUE字段是字符型长度有限制。早年间我做过一个供应商状态下拉直接把供应商描述塞进去了结果超长截断用户选中后单元格里出现一串乱码。从那以后我养成了习惯下拉列表里永远只放短代码长描述放到F4IF_INT_TABLE_VALUE_REQUEST的TEXT列里展示。用户看到的是描述写进内表的是代码界面展示和数据库存储彻底分离这才是一套干净的设计。另外有个细节DRDN_HNDL字段在填充数据时如果某行句柄初始为0或空ALV会认为这行不需要下拉。这个特性可以巧妙利用需要下拉的行给句柄1不需要下拉的行留空这样同一列也能自由控制哪些行可下拉比单纯依赖字段目录更灵活。6.3 实测下来最稳的开发顺序先做死数据Demo把下拉值表硬编码跑通后换真实枚举来源最后才接事件校验。别一上来就同时搞下拉加保存校验加数据库更新出了问题你根本分不清是哪一环。模块化地去验证每一步都确认无误再往下走这是ALV开发里最省时间的经验。我个人在实际项目里的体会是下拉框这东西技术上不难难的是把业务规则理清。到底是整列统一还是行级差异是纯枚举还是需要落到数据库用户选完之后要不要限制不能改回去这些问题问清楚了写代码就是几十分钟的事。最怕的是代码写了一版业务又改需求说“不是这种下拉是那种下拉”来回折腾的时间比编码本身多得多。所以收到需求先确认交互形态再动手。
返回列表