편집 가능한 ALV 에서 사용자가 셀 값을 바꿨을 때 그 변화를 가로채서 검증하거나 다른 셀을 자동으로 채울 때 쓰는 이벤트가 DATA_CHANGED 입니다. "가격을 입력하면 통화가 자동으로 USD 로 채워지게" 같은 연동 입력이나 "음수 값은 안 됨" 같은 입력 검증을 ALV 안에서 처리할 수 있습니다.
핵심은 두 가지입니다. (1) register_edit_event 로 변경 인식 시점을 등록 해야 이벤트가 발생합니다(Enter / 셀 수정). (2) 이벤트 핸들러에서 CL_ALV_CHANGED_DATA_PROTOCOL 객체로 변경 셀 분석·다른 셀 수정·오류 메시지 등록 을 합니다.
이 글에서는 편집 ALV 구성 → 이벤트 시점 등록 → 변경 셀 분석 → 셀 자동 수정 → 오류 메시지 표시까지 정리합니다. 데이터는 SAP 스탠다드 학습 환경 테이블 SFLIGHT 의 CARRID·PRICE·CONNID 예시.
핵심 — DATA_CHANGED 이벤트의 짝
| 단계 | 설정 | 의미 |
|---|---|---|
| 1. 편집 가능 | set_ready_for_input(1) + fcat edit = abap_true |
사용자가 셀을 수정할 수 있게 |
| 2. 변경 시점 등록 | register_edit_event(mc_evt_enter / mc_evt_modified) |
언제 DATA_CHANGED 가 발생할지 |
| 3. 핸들러 등록 | SET HANDLER ... data_changed FOR go_grid |
변경 발생 시 받을 핸들러 |
| 4. 처리 | er_data_changed(CL_ALV_CHANGED_DATA_PROTOCOL) |
변경 셀 분석·수정·오류 등록 |
흐름을 그림으로 정리하면:
편집 ALV (set_ready_for_input + edit) + register_edit_event
↓
사용자가 셀 수정 (Enter / 포커스 이동)
↓
DATA_CHANGED 이벤트 → er_data_changed
↓
mt_mod_cells (변경된 셀 목록) 분석
↓
get_cell_value : 같은 행 다른 컬럼 값 조회
modify_cell : 다른 셀 값 자동 수정 (연동 입력)
add_protocol_entry : 오류 메시지 + 셀 빨간 표시 (검증 실패)
핵심 객체는 CL_ALV_CHANGED_DATA_PROTOCOL. 이 객체의 메소드 세 개(get_cell_value·modify_cell·add_protocol_entry) 가 검증·연동·오류 표시의 도구입니다.
1단계 — 이벤트 발생 시점: mc_evt_enter vs mc_evt_modified
DATA_CHANGED 는 그냥 발생하지 않습니다. register_edit_event 로 "언제" 발생시킬지 등록해야 합니다.
| 상수 | 발생 시점 |
|---|---|
cl_gui_alv_grid=>mc_evt_enter |
사용자가 셀에 값을 넣고 Enter 를 누른 순간 |
cl_gui_alv_grid=>mc_evt_modified |
셀이 수정되고 다른 셀로 포커스가 이동할 때 (Enter 안 쳐도) |
두 개 다 등록해 두면 Enter 와 포커스 이동 양쪽에서 모두 이벤트가 발생합니다. 보통은 둘 다 등록합니다.
FORM set_event.
event_receiver = NEW #( ).
SET HANDLER event_receiver->handle_data_changed FOR go_grid.
* 편집 가능 모드
go_grid->set_ready_for_input( i_ready_for_input = 1 ).
* 변경 인식 시점 등록 (둘 다)
go_grid->register_edit_event( i_event_id = cl_gui_alv_grid=>mc_evt_enter ).
go_grid->register_edit_event( i_event_id = cl_gui_alv_grid=>mc_evt_modified ).
ENDFORM.
register_edit_event 는 반드시 set_table_for_first_display 호출 전 에 해야 적용됩니다. ALV 가 화면에 그려진 뒤 등록하면 무시됩니다.
2단계 — 이벤트 핸들러 클래스
핸들러 시그니처는 단순합니다. er_data_changed 하나로 모든 변경 정보를 받습니다.
CLASS lcl_event DEFINITION.
PUBLIC SECTION.
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.
PERFORM data_changed USING er_data_changed.
ENDMETHOD.
ENDCLASS.
DATA event_receiver TYPE REF TO lcl_event.
er_data_changed 는 CL_ALV_CHANGED_DATA_PROTOCOL 타입으로, 변경 정보를 담은 두 가지 테이블 속성을 가집니다.
| 속성 | 의미 |
|---|---|
mt_mod_cells |
사용자가 수정한 모든 셀 목록 (검증 전) |
mt_good_cells |
그 중 SAP 의 1차 검증을 통과한 셀 목록 |
보통은 mt_mod_cells 를 분석해 추가 검증·연동을 합니다.
3단계 — 변경 분석: get_cell_value · modify_cell
핸들러 안에서 변경된 셀을 LOOP 으로 돌면서, 같은 행의 다른 컬럼 값을 조회하거나 다른 컬럼을 자동으로 수정합니다.
FORM data_changed USING pr_data_changed TYPE REF TO cl_alv_changed_data_protocol.
DATA lv_price TYPE p_price.
* 변경된 모든 셀 LOOP
LOOP AT pr_data_changed->mt_mod_cells INTO DATA(ls_mod_cell).
CASE ls_mod_cell-fieldname.
* CARRID 가 바뀌면 → PRICE 와 CONNID 도 자동 채움
WHEN 'CARRID'.
* 같은 행의 PRICE 값 조회
pr_data_changed->get_cell_value(
EXPORTING
i_row_id = ls_mod_cell-row_id
i_fieldname = 'CARRID'
IMPORTING
e_value = DATA(lv_carrid) ).
* 항공사가 'AA' 이면 가격·항공편 자동 입력
IF lv_carrid = 'AA'.
lv_price = 1234.
pr_data_changed->modify_cell(
EXPORTING
i_row_id = ls_mod_cell-row_id
i_fieldname = 'PRICE'
i_value = lv_price ).
pr_data_changed->modify_cell(
EXPORTING
i_row_id = ls_mod_cell-row_id
i_fieldname = 'CONNID'
i_value = '0018' ).
ENDIF.
ENDCASE.
ENDLOOP.
ENDFORM.
get_cell_value 는 변경된 행의 다른 컬럼 값 도 함께 읽을 수 있어 연동 검증에 유용하고, modify_cell 로 사용자가 직접 입력하지 않은 셀도 자동으로 채울 수 있습니다.
| 메소드 | 용도 |
|---|---|
get_cell_value |
특정 (row_id, fieldname) 셀의 현재(변경 후) 값을 읽음 |
modify_cell |
특정 (row_id, fieldname) 셀 값을 새 값으로 변경 |
4단계 — 오류 메시지: add_protocol_entry
값이 유효하지 않을 때 셀에 빨간 오류 표시 와 메시지를 띄우려면 add_protocol_entry 를 씁니다. ALV 가 자체적으로 오류 셀을 마크하고 사용자에게 알립니다.
* 가격이 음수면 오류
pr_data_changed->add_protocol_entry(
EXPORTING
i_msgid = 'ZXX_MSG' " 메시지 클래스 (커스텀)
i_msgty = 'E' " 메시지 타입 (E=오류)
i_msgno = '000' " 메시지 번호
i_msgv1 = '가격은 양수여야 합니다' " 메시지 내용
i_fieldname = ls_mod_cell-fieldname " 오류가 발생한 컬럼
i_row_id = ls_mod_cell-row_id ). " 오류가 발생한 행
i_fieldname 과 i_row_id 로 위치를 알려주면 그 셀이 빨간색으로 표시되고, 사용자가 메시지를 확인하기 전까지는 다음 단계로 못 넘어갑니다. 표준 검증이 충분하지 않을 때 비즈니스 규칙 검증을 ALV 안에서 해결할 수 있는 강력한 도구입니다.
5단계 — check_changed_data: SAVE 직전 변경 인식 강제
사용자가 셀을 바꾸고 Enter 도 안 누른 채로 다른 동작(SAVE 버튼 클릭 등) 으로 넘어가면 DATA_CHANGED 이벤트가 발생하지 않을 수 있습니다. 이 때 check_changed_data 를 호출하면 ALV 가 미반영 변경을 강제로 인식해 DATA_CHANGED 를 발생시킵니다.
MODULE user_command_0100 INPUT.
CASE ok_code.
WHEN 'SAVE'.
* SAVE 직전 — 미반영 변경을 강제로 인식
CALL METHOD go_grid->check_changed_data.
* 이제 안전하게 저장 처리
PERFORM save_data.
...
ENDCASE.
ENDMODULE.
저장 직전 이 한 줄을 빠뜨리면, 사용자가 마지막 셀의 Enter 를 안 친 경우 변경이 누락되어 저장됩니다. 편집 ALV 의 SAVE 액션에는 거의 필수.
자주 빠뜨리는 함정
register_edit_event 위치 — display 전에
register_edit_event 는 반드시 set_table_for_first_display 호출 전에 해야 합니다. 화면이 그려진 후에 호출하면 무시되어 이벤트가 발생하지 않습니다.
편집 가능 모드 누락
set_ready_for_input(1) 과 fcat 의 edit = abap_true 가 없으면 셀이 편집 불가라 DATA_CHANGED 이벤트 자체가 발생할 일이 없습니다.
check_changed_data 누락
SAVE 직전 호출 안 하면 마지막 셀의 변경이 누락됩니다. 편집 ALV 의 모든 액션(저장·전송·다음 단계 등) 직전에 호출하는 게 표준 패턴입니다.
mt_mod_cells vs mt_good_cells
검증 통과한 셀만 보려면 mt_good_cells, 사용자가 시도한 모든 변경을 보려면 mt_mod_cells. 커스텀 검증은 mt_mod_cells 를 LOOP 하는 게 일반적입니다.
add_protocol_entry 의 fieldname·row_id 불일치
i_fieldname·i_row_id 를 빠뜨리면 메시지는 떠도 어느 셀이 문제인지 표시가 안 됩니다. 사용자가 빨간 셀을 못 찾으니 둘 다 채우는 게 사용성에 직결.
메시지 클래스 미존재
i_msgid 에 지정한 메시지 클래스(SE91 에 등록된 것) 가 없으면 런타임 오류. 메시지 번호도 같은 클래스 안에 존재해야 합니다. 간단히는 i_msgv1 에 메시지 내용을 직접 박아도 됩니다.
전체 코드 — 복사용 통합본
아래는 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_DATA_CHANGED (메인 프로그램)
*&---------------------------------------------------------------------*
REPORT zrxx_alv_data_changed.
INCLUDE zrxx_alv_data_changed_t. " TOP - 전역 선언
INCLUDE zrxx_alv_data_changed_c. " CLASS - 로컬 클래스
INCLUDE zrxx_alv_data_changed_scr. " SCR - 셀렉션 스크린
INCLUDE zrxx_alv_data_changed_o. " PBO - OUTPUT 모듈
INCLUDE zrxx_alv_data_changed_i. " PAI - INPUT 모듈
INCLUDE zrxx_alv_data_changed_f. " FORM - 서브루틴
START-OF-SELECTION.
SELECT * UP TO 20 ROWS
INTO CORRESPONDING FIELDS OF TABLE gt_data
FROM sflight.
CALL SCREEN 100.
*&---------------------------------------------------------------------*
*& INCLUDE ZRXX_ALV_DATA_CHANGED_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.
DATA: gs_data TYPE sflight,
gt_data TYPE TABLE OF sflight.
DATA event_receiver TYPE REF TO lcl_event. " C INCLUDE 의 클래스 참조
*&---------------------------------------------------------------------*
*& INCLUDE ZRXX_ALV_DATA_CHANGED_C (CLASS - 로컬 클래스)
*&---------------------------------------------------------------------*
CLASS lcl_event DEFINITION.
PUBLIC SECTION.
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.
PERFORM data_changed USING er_data_changed.
ENDMETHOD.
ENDCLASS.
*&---------------------------------------------------------------------*
*& INCLUDE ZRXX_ALV_DATA_CHANGED_SCR (SCR - 셀렉션 스크린)
*&---------------------------------------------------------------------*
* 이 글은 셀렉션 스크린(SELECT-OPTIONS/PARAMETERS) 을 쓰지 않습니다.
* Screen 100 은 SE51 에서 정의 (Docking 사용, Custom Control 없음).
* 셀렉션 조건이 필요하면 여기에 SELECT-OPTIONS 를 선언.
*&---------------------------------------------------------------------*
*& INCLUDE ZRXX_ALV_DATA_CHANGED_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. " ★ display 전에 등록
gs_layo-zebra = abap_true.
gs_layo-cwidth_opt = abap_true.
go_grid->set_table_for_first_display(
EXPORTING
is_layout = gs_layo
CHANGING
it_outtab = gt_data
it_fieldcatalog = gt_fcat ).
ENDIF.
ENDMODULE.
*&---------------------------------------------------------------------*
*& INCLUDE ZRXX_ALV_DATA_CHANGED_I (PAI - INPUT 모듈)
*&---------------------------------------------------------------------*
MODULE user_command_0100 INPUT.
CASE ok_code.
WHEN 'SAVE'.
* ★ 마지막 셀 변경 인식 강제
CALL METHOD go_grid->check_changed_data.
* 이후 저장 로직 ...
WHEN 'BACK' OR 'EXIT' OR 'CANC'.
LEAVE TO SCREEN 0.
ENDCASE.
ENDMODULE.
*&---------------------------------------------------------------------*
*& INCLUDE ZRXX_ALV_DATA_CHANGED_F (FORM - 서브루틴)
*&---------------------------------------------------------------------*
* Field Catalog — 편집 가능 컬럼 지정
FORM set_fcat.
CALL FUNCTION 'LVC_FIELDCATALOG_MERGE'
EXPORTING
i_structure_name = 'SFLIGHT'
CHANGING
ct_fieldcat = gt_fcat.
LOOP AT gt_fcat INTO DATA(ls_fcat).
CASE ls_fcat-fieldname.
WHEN 'CARRID' OR 'PRICE' OR 'CONNID'.
ls_fcat-edit = abap_true. " ★ 편집 가능
ENDCASE.
MODIFY gt_fcat FROM ls_fcat.
ENDLOOP.
ENDFORM.
* ★ 이벤트 등록 + 편집 모드 + 시점 등록
FORM set_event.
event_receiver = NEW #( ).
SET HANDLER event_receiver->handle_data_changed FOR go_grid.
go_grid->set_ready_for_input( i_ready_for_input = 1 ).
go_grid->register_edit_event(
i_event_id = cl_gui_alv_grid=>mc_evt_enter ).
go_grid->register_edit_event(
i_event_id = cl_gui_alv_grid=>mc_evt_modified ).
ENDFORM.
* ★ DATA_CHANGED 핸들러 — 변경 분석 + 자동 채움 + 오류 메시지
FORM data_changed USING pr_data_changed
TYPE REF TO cl_alv_changed_data_protocol.
DATA lv_price TYPE p_price.
LOOP AT pr_data_changed->mt_mod_cells INTO DATA(ls_mod_cell).
CASE ls_mod_cell-fieldname.
* CARRID 가 'AA' 면 PRICE / CONNID 자동 입력
WHEN 'CARRID'.
pr_data_changed->get_cell_value(
EXPORTING
i_row_id = ls_mod_cell-row_id
i_fieldname = 'CARRID'
IMPORTING
e_value = DATA(lv_carrid) ).
IF lv_carrid = 'AA'.
lv_price = 1234.
pr_data_changed->modify_cell(
EXPORTING
i_row_id = ls_mod_cell-row_id
i_fieldname = 'PRICE'
i_value = lv_price ).
pr_data_changed->modify_cell(
EXPORTING
i_row_id = ls_mod_cell-row_id
i_fieldname = 'CONNID'
i_value = '0018' ).
ENDIF.
* PRICE 가 음수면 오류 메시지
WHEN 'PRICE'.
pr_data_changed->get_cell_value(
EXPORTING
i_row_id = ls_mod_cell-row_id
i_fieldname = 'PRICE'
IMPORTING
e_value = lv_price ).
IF lv_price < 0.
pr_data_changed->add_protocol_entry(
EXPORTING
i_msgid = 'ZXX_MSG'
i_msgty = 'E'
i_msgno = '000'
i_msgv1 = '가격은 양수여야 합니다'
i_fieldname = ls_mod_cell-fieldname
i_row_id = ls_mod_cell-row_id ).
ENDIF.
ENDCASE.
ENDLOOP.
ENDFORM.
요약
| 단계 | 설정/메소드 | 역할 |
|---|---|---|
| 1 | set_ready_for_input + fcat edit |
편집 가능 ALV |
| 2 | register_edit_event(mc_evt_enter / mc_evt_modified) |
변경 인식 시점 등록 |
| 3 | SET HANDLER data_changed |
핸들러 연결 |
| 4 | get_cell_value · modify_cell · add_protocol_entry |
변경 분석 · 자동 채움 · 오류 표시 |
| 5 | check_changed_data |
SAVE 직전 미반영 변경 강제 인식 |
DATA_CHANGED 의 본질은 "편집 ALV 의 셀 변화를 가로채서 같은 행의 다른 셀까지 연쇄 처리". register_edit_event 로 시점을 정하고, CL_ALV_CHANGED_DATA_PROTOCOL 의 세 메소드(get_cell_value·modify_cell·add_protocol_entry) 만 잘 다루면 됩니다. 저장 직전 check_changed_data 호출만 잊지 않으면 마지막 셀 변경 누락 없이 안전하게 처리할 수 있습니다.
Disclaimer — 이 포스트는 실무 정리 노트를 바탕으로 AI 보조로 정리되었습니다.
CL_GUI_ALV_GRID 의 DATA_CHANGED 이벤트(er_data_changed) · register_edit_event · check_changed_data 메소드와 CL_ALV_CHANGED_DATA_PROTOCOL 의 mt_mod_cells·mt_good_cells·get_cell_value·modify_cell·add_protocol_entry 는 NetWeaver 스탠다드 정의(ECC 6.0 / S/4HANA on-premise 기준) 입니다. 적용 환경 버전에 따라 일부 차이가 있을 수 있으니 실제 적용 시 SE24 의 클래스 정의를 확인하시기 바랍니다. SFLIGHT 는 SAP 스탠다드 학습 환경(IDES) 테이블입니다. 메시지 클래스 'ZXX_MSG' 는 SE91 에 미리 등록된 가상 예시값이니, 실무 적용 시 운영 메시지 클래스로 교체하시기 바랍니다.