본문 바로가기
ALV · 화면 · 리포트

[SAP ABAP] ALV 특정 셀만 편집 가능 — CELL 별 EDIT (LVC_T_STYL + stylefname)

by Song.sh 2026. 6. 4.

편집 가능한 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_stylMAXLEN 필드로 셀 단위 입력 길이를 제한할 수도 있습니다. 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_GRIDmc_style_enabled · mc_style_disabled · mc_style_button · mc_style_hotspot · mc_style_dropdown 상수와 SLIS 패키지의 LVC_T_STYL(테이블유형) · LVC_S_STYL(라인타입) · LVC_S_LAYOstylefname 필드 · register_edit_event · set_ready_for_input 메소드는 NetWeaver 스탠다드 정의(ECC 6.0 / S/4HANA on-premise 기준) 입니다. EKKO·EKPO 표준 필드를 사용했으며 실무 적용 시 운영 시스템의 실제 컬럼 구성에 맞춰 조정하시기 바랍니다.