Web GUI ALV Refresh 커서 초기화 이슈 - modify_cell과 커서 복원 정리

Web GUI ALV Refresh 커서 초기화 이슈 배너

증상

편집 가능(Editable) ALV에서 다음 순서로 조작할 때 발생합니다.

STEP 1셀 편집값 입력 후 Enter
STEP 2Handler / PBOrefresh_table_display
STEP 3커서 초기화1행 1열로 이동

편집하던 자리에 커서가 남아 있어야 다음 행을 이어서 입력할 수 있는데, 매번 표 맨 위로 튀어버리니 연속 입력이 사실상 불가능해집니다. 대량 입력 화면에서는 치명적인 사용성 문제가 됩니다.

기대 동작커서가 유지된다

값이 갱신된 뒤에도 방금 편집하던 셀(또는 다음 행)에 커서가 남아, 연속 입력을 이어갈 수 있다.

!실제 동작1행 1열로 초기화

Refresh 직후 커서가 표 맨 위로 튄다. i_soft_refresh를 줘도 마찬가지.

환경과 배경

  • 환경: S/4HANA + Web GUI(SAP GUI for HTML)
  • 발생 시점: SAP 커널 패치 이후부터 증상 확인
  • SAP GUI for Windows에서는 같은 코드가 문제없이 동작하는 경우가 많음

여기서 Web GUI라는 조건이 중요합니다. SAP GUI와 Web GUI의 차이에서 설명했듯, Web GUI는 서버가 화면을 HTML로 변환해 내려주는 구조입니다. Refresh가 발생하면 그리드 영역이 다시 그려지는데, 이때 브라우저 포커스가 어디로 갈지는 재생성된 HTML을 따라갑니다. 네이티브 클라이언트처럼 컨트롤 내부 상태로 포커스를 유지하지 못하는 것입니다.

먼저 알아둘 것 - is_stable과 soft refresh의 오해

refresh_table_display의 파라미터 두 개는 이름 때문에 오해를 사기 쉽습니다.

1CALL METHOD gr_grid->refresh_table_display
2  EXPORTING
3    is_stable      = VALUE lvc_s_stbl( row = abap_true col = abap_true )
4    i_soft_refresh = abap_true.
파라미터실제로 하는 일커서를 지켜주나
is_stable-row / col스크롤 위치(보이는 영역)를 유지
i_soft_refresh정렬·필터·합계 등 재계산을 생략해 성능 확보

둘 다 "커서(현재 셀) 위치"를 보장하는 옵션이 아닙니다. "soft가 안 먹는다"기보다, 애초에 커서 유지는 이 파라미터들의 역할이 아니라고 보는 편이 정확합니다. SAP GUI에서 커서가 유지되는 것처럼 보였던 것은 클라이언트 측 동작에 기댄 결과에 가깝습니다.

WARNING

아래 방법들은 환경(릴리스·커널 패치·ITS 설정)에 따라 효과가 다릅니다.특히 커서 관련 동작은 패치 레벨의 영향을 크게 받으므로, 적용 전 실제 시스템에서 검증하고 필요하면 해당 증상으로 SAP Note를 검색해 보시길 권합니다.

대응 1 (권장) - refresh 대신 modify_cell

가장 근본적인 해결은 애초에 전체 Refresh를 하지 않는 것입니다.

DATA_CHANGED 이벤트 핸들러에는 er_data_changed(cl_alv_changed_data_protocol)가 넘어옵니다. 여기에는 화면 전체를 다시 그리지 않고 특정 셀 값만 바꾸는 modify_cell 메서드가 있습니다.

!기존 방식내부 테이블 수정 후 전체 Refresh

내부 테이블을 직접 수정한 뒤 refresh_table_display로 전체 재출력 → 커서 초기화 발생

권장 방식대상 셀만 직접 갱신

er_data_changed->modify_cell로 해당 셀만 변경 → 전체 Refresh 자체가 불필요

1METHOD handle_data_changed.
2  " er_data_changed TYPE REF TO cl_alv_changed_data_protocol
3
4  DATA: lv_qty   TYPE menge_d,
5        lv_price TYPE netpr,
6        lv_amt   TYPE netwr.
7
8  " 변경된 셀만 순회 (전 행 루프 불필요)
9  LOOP AT er_data_changed->mt_good_cells INTO DATA(ls_good).
10
11    CASE ls_good-fieldname.
12      WHEN 'MENGE'.
13        " 입력된 값 읽기
14        er_data_changed->get_cell_value(
15          EXPORTING i_row_id    = ls_good-row_id
16                    i_fieldname = 'MENGE'
17          IMPORTING e_value     = lv_qty ).
18
19        er_data_changed->get_cell_value(
20          EXPORTING i_row_id    = ls_good-row_id
21                    i_fieldname = 'NETPR'
22          IMPORTING e_value     = lv_price ).
23
24        lv_amt = lv_qty * lv_price.
25
26        " 핵심: refresh 대신 해당 셀만 갱신
27        er_data_changed->modify_cell(
28          EXPORTING i_row_id    = ls_good-row_id
29                    i_fieldname = 'NETWR'
30                    i_value     = lv_amt ).
31    ENDCASE.
32
33  ENDLOOP.
34
35  " 이 핸들러 안에서는 refresh_table_display 를 호출하지 않는다
36ENDMETHOD.
INFO

modify_cell출력 테이블과 그리드를 함께 갱신하므로, 계산 결과 표시·값 보정·자동 채움 같은 용도라면 대부분 이것만으로 충분합니다. 먼저 Refresh를 쓰는 로직을 최대한 modify_cell로 옮기는 것이 이 이슈의 1순위 대응입니다.

대응 2 - 그리드 커서를 저장했다가 복원

행 추가·삭제, 필드카탈로그 변경 등 Refresh를 피할 수 없는 경우에는 커서를 직접 저장·복원합니다.

cl_gui_alv_grid가 제공하는 짝은 다음과 같습니다.

메서드역할
get_current_cell현재 커서가 있는 행/열 정보를 조회
set_current_cell_via_id지정한 행/열로 커서를 이동
1DATA: ls_row_id TYPE lvc_s_row,
2      ls_col_id TYPE lvc_s_col,
3      ls_row_no TYPE lvc_s_roid.
4
5" ① Refresh 전 - 현재 커서 위치 저장
6CALL METHOD gr_grid->get_current_cell
7  IMPORTING
8    es_row_id = ls_row_id
9    es_col_id = ls_col_id
10    es_row_no = ls_row_no.
11
12" ② 불가피한 Refresh 수행
13CALL METHOD gr_grid->refresh_table_display
14  EXPORTING
15    is_stable      = VALUE lvc_s_stbl( row = abap_true col = abap_true )
16    i_soft_refresh = abap_true.
17
18" ③ Refresh 후 - 커서 복원
19CALL METHOD gr_grid->set_current_cell_via_id
20  EXPORTING
21    is_row_id    = ls_row_id
22    is_column_id = ls_col_id
23    is_row_no    = ls_row_no.
24
25" ④ 필요 시 그리드로 포커스 반환
26CALL METHOD cl_gui_control=>set_focus
27  EXPORTING
28    control = gr_grid.
INFO

행이 추가·삭제되어 행 번호가 밀리는 경우에는 저장해 둔 행 번호를 그대로 되돌리면 엉뚱한 행으로 갑니다. 이럴 때는 행 번호 대신 키 값으로 대상 행을 다시 찾아 그 인덱스로 set_current_cell_via_id를 호출하는 편이 안전합니다.

대응 3 - 화면(Dynpro) 레벨의 GET / SET CURSOR

ALV 그리드 밖의 화면 필드까지 포함해 포커스를 관리해야 할 때는 Dynpro 레벨 명령을 함께 씁니다.

1DATA: lv_field TYPE screen-name,
2      lv_line  TYPE i.
3
4" PAI 등에서 현재 커서 저장
5GET CURSOR FIELD lv_field LINE lv_line.
6
7" ... 처리 ...
8
9" PBO에서 커서 복원
10IF lv_field IS NOT INITIAL.
11  SET CURSOR FIELD lv_field LINE lv_line.
12ENDIF.
WARNING

GET CURSOR / SET CURSORDynpro 화면 필드 기준으로 동작합니다. ALV 그리드가 커스텀 컨테이너 안에 있으면 그리드 내부의 개별 셀이 아니라 컨테이너 영역이 잡히므로, 셀 단위 복원은 대응 2가 맞습니다. 두 방식은 대체재가 아니라 적용 대상이 다르다고 이해하는 편이 좋습니다.

정리 - 어떤 순서로 접근할까

1순위Refresh 제거핸들러 로직을 modify_cell로 전환
2순위커서 복원GET / SET_CURRENT_CELL 조합
3순위화면 포커스GET / SET CURSOR 보완

핵심은 "Refresh를 어떻게 잘 할까"가 아니라 "Refresh를 얼마나 안 할 수 있을까" 입니다. 커서 복원은 어디까지나 Refresh를 없앨 수 없을 때의 차선책이고, 환경 변화(커널 패치 등)에 다시 흔들릴 여지가 있습니다.

ALV 기본 구성은 ALV 리포트(REUSE) 글을 함께 참고하시면 좋습니다.

Disclaimer — 이 포스트는 AI(Claude)를 활용하여 작성된 초안을 바탕으로 검수 및 보완하여 작성되었습니다. 내용 중 오류나 오타가 있다면 댓글로 알려주시면 감사하겠습니다.