ALV 의 USER_COMMAND 이벤트는 내가 직접 추가한 커스텀 버튼(예: 행추가, 행삭제) 의 클릭을 처리합니다. 그런데 정렬·필터·합계 같은 SAP 스탠다드 툴바 버튼의 동작 전후에 로직을 끼워넣고 싶을 때는 USER_COMMAND 로는 안 됩니다. 스탠다드 기능은 SAP 가 내부에서 처리하기 때문입니다.
이때 쓰는 것이 BEFORE_USER_COMMAND 와 AFTER_USER_COMMAND 이벤트입니다. 이름 그대로 스탠다드 기능이 실행되기 직전(before) 과 실행된 직후(after) 에 끼어들어, 사전 준비나 후처리를 할 수 있게 해줍니다. 대표적인 활용이 "사용자가 필터를 건 뒤, 화면에서 숨겨진 행의 데이터를 정리" 하는 후처리입니다.
이 글에서는 세 이벤트(USER_COMMAND / BEFORE / AFTER) 의 역할 차이 → 이벤트 등록 → 스탠다드 기능 전후 처리 → 필터 후 숨겨진 행 처리 실전 예시까지 정리합니다. 데이터는 SAP 스탠다드 학습 환경 테이블 SFLIGHT 예시.
핵심 — 세 이벤트의 역할 차이
| 이벤트 | 대상 버튼 | 발생 시점 |
|---|---|---|
USER_COMMAND |
커스텀 버튼 (내가 추가) | 커스텀 버튼 클릭 시 |
BEFORE_USER_COMMAND |
스탠다드 버튼 (정렬·필터·합계 등) | 스탠다드 기능 실행 직전 |
AFTER_USER_COMMAND |
스탠다드 버튼 (정렬·필터·합계 등) | 스탠다드 기능 실행 직후 |
같은 버튼 클릭에 대해 SAP 가 이벤트를 던지는 순서는 다음과 같습니다.
사용자가 스탠다드 툴바 버튼(예: 필터) 클릭
↓
1) BEFORE_USER_COMMAND ← 스탠다드 처리 전 (사전 준비)
↓
2) SAP 스탠다드 기능 실행 (필터 적용)
↓
3) AFTER_USER_COMMAND ← 스탠다드 처리 후 (후처리)
핵심 포인트는 스탠다드 버튼에 로직을 추가하고 싶을 때 이 두 이벤트를 쓴다 는 것입니다. 스탠다드 function code 는 & 로 시작합니다 (예: &FILTER, &SORT_ASC, &SUM).
1단계 — 이벤트 핸들러 클래스 + 등록
세 이벤트의 시그니처를 비교하면 인자가 다릅니다.
CLASS lcl_receiver DEFINITION.
PUBLIC SECTION.
* 스탠다드 기능 실행 직전
METHODS handle_before_uc
FOR EVENT before_user_command OF cl_gui_alv_grid
IMPORTING e_ucomm.
* 스탠다드 기능 실행 직후
METHODS handle_after_uc
FOR EVENT after_user_command OF cl_gui_alv_grid
IMPORTING e_ucomm e_saved e_not_processed.
ENDCLASS.
| 이벤트 | 인자 |
|---|---|
before_user_command |
e_ucomm (실행될 스탠다드 기능 코드) |
after_user_command |
e_ucomm · e_saved(레이아웃 저장 여부) · e_not_processed(스탠다드 미처리 여부) |
등록은 SET HANDLER 로 합니다.
FORM set_event.
g_event = NEW #( ).
SET HANDLER g_event->handle_before_uc FOR go_grid.
SET HANDLER g_event->handle_after_uc FOR go_grid.
ENDFORM.
2단계 — BEFORE_USER_COMMAND: 스탠다드 기능 직전 처리
스탠다드 기능이 실행되기 전에 끼어듭니다. e_ucomm 으로 어떤 스탠다드 기능인지 판단해, 사전 검증·상태 저장·로그 같은 준비 작업을 합니다.
FORM handle_before_uc USING p_ucomm TYPE sy-ucomm.
CASE p_ucomm.
WHEN '&INFO'. " 정보 버튼 — 예: 클릭 로그 남기기
* 사전 처리 로직
WHEN '&FILTER'. " 필터 버튼 — 예: 현재 화면 상태 백업
* 필터 적용 전 현재 데이터 스냅샷 보관 등
ENDCASE.
ENDFORM.
스탠다드 기능 실행 자체는 SAP 가 진행하고, BEFORE_USER_COMMAND 는 그 직전 "준비 시점" 을 제공합니다. 스탠다드 처리를 막거나 바꾸는 게 아니라, 그 앞에 한 단계 끼어드는 것입니다.
3단계 — AFTER_USER_COMMAND: 스탠다드 기능 직후 처리
스탠다드 기능이 끝난 뒤 끼어듭니다. 정렬·필터가 적용된 결과 상태를 기준으로 후처리할 수 있습니다.
FORM handle_after_uc USING p_ucomm TYPE sy-ucomm
p_saved TYPE char01
p_not_processed TYPE char01.
CASE p_ucomm.
WHEN '&FILTER'.
* 필터가 적용된 뒤의 후처리 (아래 4단계 참고)
PERFORM after_filter.
ENDCASE.
ENDFORM.
e_saved 는 레이아웃이 저장됐는지, e_not_processed 는 스탠다드 처리가 안 됐는지(예: 다른 핸들러가 가로챔) 여부를 알려줍니다. 보통 e_ucomm 으로 어떤 기능 직후인지 분기하는 게 핵심입니다.
4단계 — 실전: 필터 후 숨겨진 행 처리
가장 흔한 활용입니다. 사용자가 스탠다드 필터를 걸면 조건에 안 맞는 행이 화면에서 사라지는데, 데이터 자체(내부 테이블)에는 그대로 남아 있습니다. 이때 화면에서 숨겨진 행이 무엇인지 get_filtered_entries 로 알아내, 그 행들의 체크 상태나 표시 값을 정리할 수 있습니다.
FORM after_filter.
DATA lt_filtered TYPE lvc_t_fidx. " 필터로 숨겨진 행 인덱스 목록
* 필터로 화면에서 사라진 행들의 인덱스를 가져옴
go_grid->get_filtered_entries(
IMPORTING et_filtered_entries = lt_filtered ).
* 숨겨진 행의 체크 상태 정리 (예: 체크박스 해제)
LOOP AT lt_filtered INTO DATA(lv_index).
READ TABLE gt_data INTO DATA(ls_data) INDEX lv_index.
IF sy-subrc = 0.
CLEAR ls_data-checkbox. " 숨겨진 행은 선택 해제
MODIFY gt_data FROM ls_data INDEX lv_index.
ENDIF.
ENDLOOP.
go_grid->refresh_table_display(
EXPORTING is_stable = VALUE #( col = abap_true row = abap_true ) ).
ENDFORM.
get_filtered_entries 가 돌려주는 lvc_t_fidx 는 필터로 숨겨진 행의 인덱스 목록입니다. 이 인덱스로 내부 테이블에 접근해, 화면에 안 보이는 행이 선택된 채로 남아 잘못 처리되는 일을 막습니다. "전체 선택 후 필터" 같은 시나리오에서 숨겨진 행까지 처리되는 버그를 예방하는 핵심 패턴입니다.
자주 빠뜨리는 함정
스탠다드 버튼인데 USER_COMMAND 로 잡으려 함
정렬·필터·합계 같은 스탠다드 버튼은 USER_COMMAND 이벤트로 안 잡힙니다. USER_COMMAND 는 커스텀 버튼 전용이고, 스탠다드 버튼은 BEFORE/AFTER_USER_COMMAND 로 잡아야 합니다.
before 에서 스탠다드 처리를 막으려 함
BEFORE_USER_COMMAND 는 e_ucomm 만 줄 뿐 스탠다드 처리를 막는 인자가 없습니다. "끼어들기" 용도이지 "차단" 용도가 아닙니다. 특정 기능을 막으려면 애초에 it_toolbar_excluding 로 그 버튼을 제외하는 게 맞습니다.
필터 후 get_filtered_entries 시점 오해
get_filtered_entries 는 필터가 적용된 후 에 의미가 있으므로 AFTER_USER_COMMAND 에서 호출해야 합니다. BEFORE 에서 부르면 아직 필터 전이라 숨겨진 행이 없습니다.
e_ucomm 분기 누락
AFTER_USER_COMMAND 는 모든 스탠다드 기능 실행 후 발생합니다. e_ucomm 으로 분기하지 않으면 정렬·합계 등 무관한 기능에도 후처리가 돌아 성능 낭비나 오작동이 생깁니다.
스탠다드 function code 를 모름
필터는 &FILTER, 정렬은 &SORT_ASC/&SORT_DSC 등 스탠다드 코드가 정해져 있습니다. 디버거에서 e_ucomm 값을 한 번 찍어 확인한 뒤 분기 조건을 정하는 게 안전합니다.
전체 코드 — 복사용 통합본
아래는 SAP 스탠다드 모듈풀 구조에 맞춰 메인 + 6개 INCLUDE(T / C / SCR / O / I / F) 로 나눈 통합본입니다. SE38 에서 메인 프로그램과 각 INCLUDE 를 만들고, Screen 100 의 Layout 은 빈 상태로 두고 Flow Logic 에 status_0100 · set_alv (PBO) · user_command_0100 (PAI) 를 연결합니다. INCLUDE 는 T → C → SCR → O → I → F 순서로 로드됩니다.
*&---------------------------------------------------------------------*
*& Report ZRXX_ALV_BEFORE_AFTER_UC (메인 프로그램)
*&---------------------------------------------------------------------*
REPORT zrxx_alv_before_after_uc.
INCLUDE zrxx_alv_before_after_uc_t. " TOP - 전역 선언
INCLUDE zrxx_alv_before_after_uc_c. " CLASS - 로컬 클래스
INCLUDE zrxx_alv_before_after_uc_scr. " SCR - 셀렉션 스크린
INCLUDE zrxx_alv_before_after_uc_o. " PBO - OUTPUT 모듈
INCLUDE zrxx_alv_before_after_uc_i. " PAI - INPUT 모듈
INCLUDE zrxx_alv_before_after_uc_f. " FORM - 서브루틴
START-OF-SELECTION.
SELECT * UP TO 30 ROWS
INTO CORRESPONDING FIELDS OF TABLE gt_data
FROM sflight.
CALL SCREEN 100.
*&---------------------------------------------------------------------*
*& INCLUDE ZRXX_ALV_BEFORE_AFTER_UC_T (TOP - 전역 선언)
*&---------------------------------------------------------------------*
DATA: go_docking TYPE REF TO cl_gui_docking_container,
go_grid TYPE REF TO cl_gui_alv_grid.
DATA: ok_code TYPE sy-ucomm.
DATA: gs_layo TYPE lvc_s_layo,
gt_fcat TYPE lvc_t_fcat.
* SFLIGHT + 체크박스 컬럼
DATA: BEGIN OF gs_data.
INCLUDE STRUCTURE sflight.
DATA: checkbox TYPE c LENGTH 1,
END OF gs_data,
gt_data LIKE TABLE OF gs_data.
DATA g_event TYPE REF TO lcl_receiver. " C INCLUDE 의 클래스 참조
*&---------------------------------------------------------------------*
*& INCLUDE ZRXX_ALV_BEFORE_AFTER_UC_C (CLASS - 로컬 클래스)
*&---------------------------------------------------------------------*
CLASS lcl_receiver DEFINITION.
PUBLIC SECTION.
METHODS handle_before_uc
FOR EVENT before_user_command OF cl_gui_alv_grid
IMPORTING e_ucomm.
METHODS handle_after_uc
FOR EVENT after_user_command OF cl_gui_alv_grid
IMPORTING e_ucomm e_saved e_not_processed.
ENDCLASS.
CLASS lcl_receiver IMPLEMENTATION.
METHOD handle_before_uc.
PERFORM handle_before_uc USING e_ucomm.
ENDMETHOD.
METHOD handle_after_uc.
PERFORM handle_after_uc USING e_ucomm e_saved e_not_processed.
ENDMETHOD.
ENDCLASS.
*&---------------------------------------------------------------------*
*& INCLUDE ZRXX_ALV_BEFORE_AFTER_UC_SCR (SCR - 셀렉션 스크린)
*&---------------------------------------------------------------------*
* 이 글은 셀렉션 스크린(SELECT-OPTIONS/PARAMETERS) 을 쓰지 않습니다.
* Screen 100 은 SE51 에서 정의 (Docking 사용, Custom Control 없음).
* 셀렉션 조건이 필요하면 여기에 SELECT-OPTIONS 를 선언.
*&---------------------------------------------------------------------*
*& INCLUDE ZRXX_ALV_BEFORE_AFTER_UC_O (PBO - OUTPUT 모듈)
*&---------------------------------------------------------------------*
MODULE status_0100 OUTPUT.
SET PF-STATUS 'S100'.
ENDMODULE.
MODULE set_alv OUTPUT.
IF go_docking IS INITIAL.
go_docking = NEW #( dynnr = sy-dynnr extension = 3000 ).
go_grid = NEW #( i_parent = go_docking ).
PERFORM set_fcat.
PERFORM set_event.
gs_layo-zebra = abap_true.
gs_layo-cwidth_opt = abap_true.
go_grid->set_table_for_first_display(
EXPORTING
i_structure_name = 'SFLIGHT'
is_layout = gs_layo
CHANGING
it_outtab = gt_data
it_fieldcatalog = gt_fcat ).
ENDIF.
ENDMODULE.
*&---------------------------------------------------------------------*
*& INCLUDE ZRXX_ALV_BEFORE_AFTER_UC_I (PAI - INPUT 모듈)
*&---------------------------------------------------------------------*
MODULE user_command_0100 INPUT.
CASE ok_code.
WHEN 'BACK' OR 'EXIT' OR 'CANC'.
LEAVE TO SCREEN 0.
ENDCASE.
ENDMODULE.
*&---------------------------------------------------------------------*
*& INCLUDE ZRXX_ALV_BEFORE_AFTER_UC_F (FORM - 서브루틴)
*&---------------------------------------------------------------------*
* Field Catalog — 체크박스 컬럼 편집 가능
FORM set_fcat.
DATA ls_fcat TYPE lvc_s_fcat.
gs_data = VALUE #( ).
CALL FUNCTION 'LVC_FIELDCATALOG_MERGE'
EXPORTING
i_structure_name = 'SFLIGHT'
CHANGING
ct_fieldcat = gt_fcat.
* 체크박스 컬럼 추가
ls_fcat-fieldname = 'CHECKBOX'.
ls_fcat-checkbox = abap_true.
ls_fcat-edit = abap_true.
ls_fcat-coltext = '선택'.
APPEND ls_fcat TO gt_fcat.
ENDFORM.
* 이벤트 등록
FORM set_event.
g_event = NEW #( ).
SET HANDLER g_event->handle_before_uc FOR go_grid.
SET HANDLER g_event->handle_after_uc FOR go_grid.
ENDFORM.
* ★ BEFORE_USER_COMMAND — 스탠다드 기능 직전
FORM handle_before_uc USING p_ucomm TYPE sy-ucomm.
CASE p_ucomm.
WHEN '&FILTER'.
* 필터 적용 전 사전 처리 (예: 현재 상태 백업)
ENDCASE.
ENDFORM.
* ★ AFTER_USER_COMMAND — 스탠다드 기능 직후
FORM handle_after_uc USING p_ucomm TYPE sy-ucomm
p_saved TYPE char01
p_not_processed TYPE char01.
CASE p_ucomm.
WHEN '&FILTER'.
PERFORM after_filter.
ENDCASE.
ENDFORM.
* 필터 후 숨겨진 행 처리
FORM after_filter.
DATA lt_filtered TYPE lvc_t_fidx.
go_grid->get_filtered_entries(
IMPORTING et_filtered_entries = lt_filtered ).
LOOP AT lt_filtered INTO DATA(lv_index).
READ TABLE gt_data INTO DATA(ls_data) INDEX lv_index.
IF sy-subrc = 0.
CLEAR ls_data-checkbox.
MODIFY gt_data FROM ls_data INDEX lv_index.
ENDIF.
ENDLOOP.
go_grid->refresh_table_display(
EXPORTING is_stable = VALUE #( col = abap_true row = abap_true ) ).
ENDFORM.
요약
| 이벤트 | 용도 | 핵심 |
|---|---|---|
USER_COMMAND |
커스텀 버튼 처리 | 내가 추가한 버튼의 function 분기 |
BEFORE_USER_COMMAND |
스탠다드 기능 직전 hook | 사전 준비 (e_ucomm 만) |
AFTER_USER_COMMAND |
스탠다드 기능 직후 hook | 후처리 (get_filtered_entries 등) |
BEFORE/AFTER_USER_COMMAND 의 본질은 "SAP 스탠다드 툴바 버튼의 동작 전후에 내 로직을 끼워넣는 hook". 커스텀 버튼은 USER_COMMAND, 스탠다드 버튼은 BEFORE/AFTER 로 잡는다는 구분만 명확하면 됩니다. 대표 활용인 "필터 후 get_filtered_entries 로 숨겨진 행 정리" 는 전체 선택·일괄 처리 화면에서 화면에 안 보이는 행이 잘못 처리되는 버그를 막는 실전 패턴입니다.
Disclaimer — 이 포스트는 실무 정리 노트를 바탕으로 AI 보조로 정리되었습니다.
CL_GUI_ALV_GRID 의 BEFORE_USER_COMMAND(e_ucomm) · AFTER_USER_COMMAND(e_ucomm·e_saved·e_not_processed) 이벤트와 get_filtered_entries 메소드(lvc_t_fidx) 는 NetWeaver 스탠다드 정의(ECC 6.0 / S/4HANA on-premise 기준) 입니다. 적용 환경 버전에 따라 일부 차이가 있을 수 있으니 실제 적용 시 SE24 의 클래스 정의를 확인하시기 바랍니다. 스탠다드 function code(&FILTER 등) 도 버전에 따라 다를 수 있으니 디버거로 확인 후 사용하시기 바랍니다. SFLIGHT 는 SAP 스탠다드 학습 환경(IDES) 테이블입니다.