편집 가능한 ALV 에서 컬럼 전체가 아니라 특정 행의 특정 셀만 편집 가능/불가로 만들고 싶을 때가 있습니다. "이미 발주된 행은 잠그고 신규 행만 입력 받기" 같은 조건부 잠금이 대표 케이스. 컬럼 단위 fcat-edit = abap_true 만으로는 부족하고, 셀(cell) 단위 스타일 을 박아 줘야 합니다.
핵심은 두 가지입니다. (1) 인터널 테이블에 LVC_T_STYL 타입의 컬럼 한 개 를 추가하고, (2) layout-stylefname 에 그 컬럼명을 등록. 그 다음 행마다 LOOP 돌면서 그 컬럼에 (필드명, 스타일 상수) 한 줄씩 채워 넣으면 ALV 가 자동으로 셀별로 편집/잠금을 표시합니다.
이 글에서는 셀 스타일 컬럼 선언 → fill 폼 작성 → layout 등록 → 편집 이벤트 등록까지 정리합니다. 데이터는 SAP 스탠다드 학습 환경 EKKO(구매오더 헤더) + EKPO(라인) 표준 필드 기준.
핵심 — LVC_T_STYL 셀 스타일 테이블
ALV 의 셀 스타일 제어는 인터널 테이블의 한 행 안에 또 다른 테이블(스타일 목록) 을 끼워 넣는 구조입니다. 각 행마다 그 안의 스타일 목록을 채워 어떤 셀이 잠겨 있고 어떤 셀이 열려 있는지 표현합니다.
| 구성 요소 | 타입/값 | 역할 |
|---|---|---|
| ITAB 컬럼 | cellstyl TYPE lvc_t_styl |
행 안에 셀별 스타일 목록을 담는 컬럼 |
| 스타일 라인 | lvc_s_styl (FIELDNAME + STYLE) |
"이 셀(필드명) 의 스타일은 이것" 한 줄 |
| Layout 등록 | gs_layout-stylefname = 'CELLSTYL' |
ALV 에게 "어느 컬럼이 스타일 컬럼인지" 알려주기 |
| 편집 활성 | set_ready_for_input(1) + register_edit_event |
사용자가 셀을 수정·Enter 인식 |
흐름을 그림으로 정리하면:
ITAB 한 행 (gs_data)
├─ ebeln = '4500000001'
├─ matnr = 'TEST-MAT-001'
├─ menge = 100
└─ cellstyl ← LVC_T_STYL (이 행의 셀별 스타일 목록)
├─ (FIELDNAME='EBELN', STYLE=mc_style_disabled) ← 잠금
├─ (FIELDNAME='MATNR', STYLE=mc_style_disabled) ← 잠금
└─ (FIELDNAME='MENGE', STYLE=mc_style_enabled) ← 편집 가능
Layout: gs_layout-stylefname = 'CELLSTYL'
→ ALV 가 각 행의 CELLSTYL 컬럼을 읽어 셀별로 잠금/편집 표시
핵심 상수는 cl_gui_alv_grid=>mc_style_* 시리즈:
| 상수 | 의미 |
|---|---|
mc_style_enabled |
편집 가능 (입력란 활성) |
mc_style_disabled |
편집 불가 (회색 잠금) |
mc_style_button |
셀을 버튼처럼 표시 |
mc_style_hotspot |
셀을 핫스팟(클릭 가능 링크) 으로 |
mc_style_dropdown |
드롭다운 셀 |
1단계 — ITAB 에 셀 스타일 컬럼 선언
ALV 에 띄울 인터널 테이블 끝에 cellstyl TYPE lvc_t_styl 컬럼 한 개를 추가합니다. 처리 분기에 쓸 flag 컬럼도 같이 만들어두면 편합니다(예: 사용자가 체크한 행만 lt_celltab 채우기).
DATA : BEGIN OF gs_data,
ebeln LIKE ekko-ebeln, " 구매오더번호
ebelp LIKE ekpo-ebelp, " 라인번호
bsart LIKE ekko-bsart, " 문서유형
lifnr LIKE ekko-lifnr, " 공급업체
matnr LIKE ekpo-matnr, " 자재
menge LIKE ekpo-menge, " 수량
meins LIKE ekpo-meins, " 단위
netpr LIKE ekpo-netpr, " 단가
werks LIKE ekpo-werks, " 플랜트
cellstyl TYPE lvc_t_styl, " ★ 셀 스타일 목록
flag(1) TYPE c, " 분기용 (체크 행)
END OF gs_data,
gt_data LIKE TABLE OF gs_data.
LVC_T_STYL 은 SAP 스탠다드 SLIS 패키지의 테이블 유형이고, 그 라인 타입은 LVC_S_STYL(FIELDNAME · STYLE · STYLE2 · MAXLEN · SUB) 입니다. 행마다 이 안에 (필드명, 스타일) 한 줄씩 쌓이는 구조.
2단계 — fill_celltab_edit: 행별 셀 스타일 채우기
field catalog 를 LOOP 돌면서 조건에 따라 셀 스타일을 박아 줍니다. 아래 예는 "발주번호(EBELN) 가 있는 라인(= 기존 발주) 은 모든 셀을 잠금" 패턴.
FORM fill_celltab_edit CHANGING p_lt_celltab TYPE lvc_t_styl.
DATA : ls_celltab TYPE lvc_s_styl.
LOOP AT gt_fcat INTO gs_fcat.
ls_celltab-fieldname = gs_fcat-fieldname.
* ★ 발주번호가 이미 있는 행(= 기존 발주) → 모든 셀 잠금
IF gs_data-ebeln IS NOT INITIAL.
ls_celltab-style = cl_gui_alv_grid=>mc_style_disabled.
INSERT ls_celltab INTO TABLE p_lt_celltab.
CONTINUE.
ENDIF.
* 신규 행은 stylefname 컬럼에 라인을 안 넣으면 fcat-edit 따라감
* (특정 컬럼만 선택적으로 열려면 mc_style_enabled 로 박기)
ENDLOOP.
ENDFORM.
규칙은 단순합니다. p_lt_celltab 에 라인이 들어간 (필드명) 셀은 그 스타일이 적용되고, 안 들어간 셀은 field catalog 의 기본(fcat-edit) 을 따릅니다. 그래서 "한 행 전체 잠금" 은 fcat LOOP 으로 모든 컬럼에 mc_style_disabled 박는 게 정석.
3단계 — cell_edit: 각 행에 lt_celltab 끼워 넣기
플래그가 켜진 행(flag = 'X') 만 LOOP 돌면서 lt_celltab 을 새로 만들고 ITAB 행의 cellstyl 컬럼에 채워 넣습니다.
FORM cell_edit.
DATA : lt_celltab TYPE lvc_t_styl,
l_index TYPE i.
LOOP AT gt_data INTO gs_data WHERE flag = 'X'.
l_index = sy-tabix.
REFRESH lt_celltab.
PERFORM fill_celltab_edit CHANGING lt_celltab.
CLEAR gs_data-cellstyl.
INSERT LINES OF lt_celltab INTO TABLE gs_data-cellstyl.
MODIFY gt_data FROM gs_data INDEX l_index.
ENDLOOP.
ENDFORM.
핵심은 MODIFY gt_data FROM gs_data INDEX l_index. 행 안의 cellstyl 컬럼은 인터널 테이블이라 LOOP 변수 안에서만 바꾸면 본 테이블에 반영이 안 됩니다. MODIFY ... INDEX 로 명시적으로 다시 써줘야 ALV refresh 시점에 새 스타일이 보입니다.
4단계 — Layout 에 stylefname 등록
ALV 에게 "ITAB 의 어떤 컬럼이 셀 스타일 컬럼인지" 알려줘야 합니다. gs_layout-stylefname 에 컬럼명(대문자 문자열) 을 박습니다.
FORM create_layout.
CLEAR gs_layout.
gs_layout-cwidth_opt = 'X'.
gs_layout-zebra = 'X'.
gs_layout-stylefname = 'CELLSTYL'. " ★ ITAB 의 cellstyl 컬럼 등록
ENDFORM.
stylefname 의 값은 ITAB 컬럼명의 대문자 문자열 리터럴. ABAP 변수명이 아니라 ALV 가 동적으로 컬럼을 찾기 때문에 따옴표로 감싼 문자열이어야 합니다. 이 한 줄을 빠뜨리면 cellstyl 컬럼에 아무리 채워 넣어도 ALV 가 무시합니다.
5단계 — 편집 모드 + 변경 인식 등록
셀이 회색이 아니라 실제로 입력 받으려면 set_ready_for_input(1) 과 변경 인식 이벤트(register_edit_event) 가 필요합니다.
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 ).
이 호출은 set_table_for_first_display 이후 에 해야 안전합니다. 그래야 사용자가 Enter 칠 때 셀 값이 ITAB 으로 반영됩니다. display 호출 전에 박으면 등록이 무시되는 경우가 있습니다.
자주 빠뜨리는 함정
stylefname 컬럼명 — 대문자 문자열 리터럴
gs_layout-stylefname = 'cellstyl' 처럼 소문자로 박으면 ALV 가 컬럼을 못 찾습니다. ABAP 의 DDIC 룩업은 내부적으로 대문자로 처리되므로 'CELLSTYL' 로.
MODIFY ... INDEX 누락
LOOP 안에서 gs_data-cellstyl 에 INSERT 만 하고 MODIFY gt_data FROM gs_data INDEX l_index 를 빠뜨리면 본 테이블 반영 안 됨. cellstyl 은 라인 내부의 인터널 테이블이라 LOOP 변수 안에서만 변경됩니다.
REFRESH lt_celltab 위치
LOOP 마다 lt_celltab 을 새로 만들지 않고 누적시키면 다음 행에 이전 행의 스타일이 같이 들어갑니다. REFRESH lt_celltab 을 LOOP 시작에 두는 게 안전.
fcat-edit 와의 우선순위
field catalog 의 edit = abap_true 로 컬럼 전체를 열어 두고, cellstyl 로 특정 셀만 mc_style_disabled 박는 게 일반적입니다. 반대로 컬럼 전체 잠그고 특정 셀만 mc_style_enabled 박는 패턴도 됩니다. cellstyl 라인이 들어간 셀은 cellstyl 이 이김.
set_ready_for_input 누락
fcat-edit + cellstyl 까지 다 했는데 셀이 안 열리면 거의 set_ready_for_input(1) 누락. 이건 그리드 전체의 편집 모드 스위치라 한 번 안 켜면 다 무용지물.
데이터 갱신 후 refresh_table_display 누락
cellstyl 만 바꾸고 ALV 가 새로 그려지지 않으면 화면은 그대로. go_grid->refresh_table_display( ) 호출이 필요합니다. 단순한 데이터 변경이라면 is_stable-row = 'X' is_stable-col = 'X' 옵션을 같이 넘기면 스크롤 위치가 유지됩니다.
MAXLEN — 입력 길이 제한
lvc_s_styl 의 MAXLEN 필드로 셀 단위 입력 길이를 제한할 수도 있습니다. DDIC 의 컬럼 길이보다 짧게 받고 싶을 때 사용. 안 채우면 0 = 컬럼 기본 길이.
전체 코드 — 복사용 통합본
화면(CALL SCREEN) 이 있는 모듈풀 구조라 SAP 스탠다드 INCLUDE 6분할(T / C / SCR / O / I / F) 로 구성합니다. SE51 에서 Screen 100 을 빈 화면으로 만들고 Custom Container CCONT 한 개 + Flow Logic 에 status_0100 · create_alv (PBO) · user_command_0100 (PAI).
*&---------------------------------------------------------------------*
*& Report ZRXX_ALV_CELL_EDIT (메인)
*&---------------------------------------------------------------------*
REPORT zrxx_alv_cell_edit.
INCLUDE zrxx_alv_cell_edit_t. " TOP - 전역 선언
INCLUDE zrxx_alv_cell_edit_c. " CLASS - 로컬 클래스 (이 글에서는 사용 안 함)
INCLUDE zrxx_alv_cell_edit_scr. " SCR - 셀렉션 스크린 (사용 안 함)
INCLUDE zrxx_alv_cell_edit_o. " PBO - OUTPUT 모듈
INCLUDE zrxx_alv_cell_edit_i. " PAI - INPUT 모듈
INCLUDE zrxx_alv_cell_edit_f. " FORM - 서브루틴
START-OF-SELECTION.
* 예시 데이터 — 기존 발주 1건 + 신규 입력용 빈 행 2개
SELECT ebeln ebelp bsart lifnr
FROM ekko UP TO 1 ROWS
INTO CORRESPONDING FIELDS OF TABLE gt_data.
APPEND INITIAL LINE TO gt_data.
APPEND INITIAL LINE TO gt_data.
CALL SCREEN 100.
*&---------------------------------------------------------------------*
*& INCLUDE ZRXX_ALV_CELL_EDIT_T (TOP)
*&---------------------------------------------------------------------*
DATA : go_container TYPE REF TO cl_gui_custom_container,
go_grid TYPE REF TO cl_gui_alv_grid.
DATA : gs_layout TYPE lvc_s_layo,
gt_fcat TYPE lvc_t_fcat,
gs_fcat TYPE lvc_s_fcat.
DATA : BEGIN OF gs_data,
ebeln LIKE ekko-ebeln,
ebelp LIKE ekpo-ebelp,
bsart LIKE ekko-bsart,
lifnr LIKE ekko-lifnr,
matnr LIKE ekpo-matnr,
menge LIKE ekpo-menge,
meins LIKE ekpo-meins,
netpr LIKE ekpo-netpr,
werks LIKE ekpo-werks,
cellstyl TYPE lvc_t_styl, " ★ 셀 스타일 컬럼
flag(1) TYPE c, " 처리 분기 (체크 행)
END OF gs_data,
gt_data LIKE TABLE OF gs_data.
DATA : ok_code TYPE sy-ucomm.
*&---------------------------------------------------------------------*
*& INCLUDE ZRXX_ALV_CELL_EDIT_C (CLASS)
*&---------------------------------------------------------------------*
* (이 글에서는 사용 안 함 — 변경 핸들러가 필요해지면 lcl_event 정의)
*&---------------------------------------------------------------------*
*& INCLUDE ZRXX_ALV_CELL_EDIT_SCR (SCR)
*&---------------------------------------------------------------------*
* (이 글에서는 셀렉션 스크린 사용 안 함)
* Screen 100 은 SE51 에서 정의 — Custom Container 이름 'CCONT'
*&---------------------------------------------------------------------*
*& INCLUDE ZRXX_ALV_CELL_EDIT_O (PBO)
*&---------------------------------------------------------------------*
MODULE status_0100 OUTPUT.
SET PF-STATUS 'S100'.
ENDMODULE.
MODULE create_alv OUTPUT.
IF go_container IS INITIAL.
go_container = NEW #( container_name = 'CCONT' ).
go_grid = NEW #( i_parent = go_container ).
PERFORM create_fcat.
PERFORM create_layout.
* ★ 초기 cellstyl 채우기 (전체 행 대상으로 한 번)
PERFORM cell_edit_all.
go_grid->set_table_for_first_display(
EXPORTING
is_layout = gs_layout
CHANGING
it_outtab = gt_data
it_fieldcatalog = gt_fcat ).
* ★ display 이후에 편집 모드 + 이벤트 등록
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 ).
ENDIF.
ENDMODULE.
*&---------------------------------------------------------------------*
*& INCLUDE ZRXX_ALV_CELL_EDIT_I (PAI)
*&---------------------------------------------------------------------*
MODULE user_command_0100 INPUT.
CASE ok_code.
WHEN 'REFR'.
* 체크된 행만 다시 잠그기 + 화면 갱신
PERFORM cell_edit.
go_grid->refresh_table_display( ).
WHEN 'BACK' OR 'EXIT' OR 'CANC'.
LEAVE TO SCREEN 0.
ENDCASE.
ENDMODULE.
*&---------------------------------------------------------------------*
*& INCLUDE ZRXX_ALV_CELL_EDIT_F (FORM)
*&---------------------------------------------------------------------*
* Field Catalog — EKPO 구조 기반 + 전체 컬럼 편집 가능
FORM create_fcat.
CALL FUNCTION 'LVC_FIELDCATALOG_MERGE'
EXPORTING
i_structure_name = 'EKPO'
CHANGING
ct_fieldcat = gt_fcat.
LOOP AT gt_fcat INTO gs_fcat.
gs_fcat-edit = abap_true. " 컬럼 전체는 편집 가능
MODIFY gt_fcat FROM gs_fcat.
ENDLOOP.
ENDFORM.
* Layout — stylefname 등록
FORM create_layout.
CLEAR gs_layout.
gs_layout-cwidth_opt = 'X'.
gs_layout-zebra = 'X'.
gs_layout-stylefname = 'CELLSTYL'. " ★ ITAB 의 cellstyl 컬럼명
ENDFORM.
* 초기 1회 — 모든 행 대상으로 cellstyl 채우기
FORM cell_edit_all.
DATA : lt_celltab TYPE lvc_t_styl,
l_index TYPE i.
LOOP AT gt_data INTO gs_data.
l_index = sy-tabix.
REFRESH lt_celltab.
PERFORM fill_celltab_edit CHANGING lt_celltab.
CLEAR gs_data-cellstyl.
INSERT LINES OF lt_celltab INTO TABLE gs_data-cellstyl.
MODIFY gt_data FROM gs_data INDEX l_index.
ENDLOOP.
ENDFORM.
* 사용자 액션 — flag = 'X' 행만 다시 채우기
FORM cell_edit.
DATA : lt_celltab TYPE lvc_t_styl,
l_index TYPE i.
LOOP AT gt_data INTO gs_data WHERE flag = 'X'.
l_index = sy-tabix.
REFRESH lt_celltab.
PERFORM fill_celltab_edit CHANGING lt_celltab.
CLEAR gs_data-cellstyl.
INSERT LINES OF lt_celltab INTO TABLE gs_data-cellstyl.
MODIFY gt_data FROM gs_data INDEX l_index.
ENDLOOP.
ENDFORM.
* 행별 셀 스타일 규칙 — 기존 발주(EBELN 있음) 는 전 셀 잠금
FORM fill_celltab_edit CHANGING p_lt_celltab TYPE lvc_t_styl.
DATA : ls_celltab TYPE lvc_s_styl.
LOOP AT gt_fcat INTO gs_fcat.
ls_celltab-fieldname = gs_fcat-fieldname.
IF gs_data-ebeln IS NOT INITIAL.
ls_celltab-style = cl_gui_alv_grid=>mc_style_disabled.
INSERT ls_celltab INTO TABLE p_lt_celltab.
CONTINUE.
ENDIF.
* (선택) 특정 셀만 강제 편집: mc_style_enabled 박기
* IF gs_fcat-fieldname = 'MENGE'.
* ls_celltab-style = cl_gui_alv_grid=>mc_style_enabled.
* INSERT ls_celltab INTO TABLE p_lt_celltab.
* ENDIF.
ENDLOOP.
ENDFORM.
요약
| 단계 | 위치 | 핵심 |
|---|---|---|
| 1 | ITAB | cellstyl TYPE lvc_t_styl 컬럼 추가 |
| 2 | FORM fill_celltab | 조건 분기로 ls_celltab-style = mc_style_disabled / enabled |
| 3 | FORM cell_edit | 행마다 INSERT LINES OF lt_celltab INTO TABLE gs_data-cellstyl + MODIFY gt_data ... INDEX |
| 4 | Layout | gs_layout-stylefname = 'CELLSTYL' (대문자) |
| 5 | 편집 모드 | set_ready_for_input(1) + register_edit_event(mc_evt_enter / mc_evt_modified) — display 이후 |
셀별 편집 제어의 본질은 "ITAB 의 한 행 안에 셀 스타일 목록(인터널 테이블) 을 끼워 넣고, layout 으로 ALV 에게 그 컬럼명을 알려주기". cellstyl·stylefname·MODIFY ... INDEX 세 가지만 챙기면 됩니다. "기존 데이터는 잠그고 신규 라인만 입력 받기" 같은 마스터/디테일 화면의 흔한 요구를 깔끔하게 처리할 수 있습니다.
Disclaimer — 이 포스트는 실무 정리 노트를 바탕으로 AI 보조로 정리되었습니다.
CL_GUI_ALV_GRID 의 mc_style_enabled · mc_style_disabled · mc_style_button · mc_style_hotspot · mc_style_dropdown 상수와 SLIS 패키지의 LVC_T_STYL(테이블유형) · LVC_S_STYL(라인타입) · LVC_S_LAYO 의 stylefname 필드 · register_edit_event · set_ready_for_input 메소드는 NetWeaver 스탠다드 정의(ECC 6.0 / S/4HANA on-premise 기준) 입니다. EKKO·EKPO 표준 필드를 사용했으며 실무 적용 시 운영 시스템의 실제 컬럼 구성에 맞춰 조정하시기 바랍니다.