Extracting and Restoring Splitter Container Configurations in SAP GUI with ABAP
When working with complex SAP GUI applications, it's common to use CL_GUI_SPLITTER_CONTAINER to structure screens. These splitter configurations often evolve over time and differ between users, environments, or sSAPGUI-settings.
This article presents a simple but effective way to extract and later restore the current configuration of SAP splitter containers using a small local ABAP utility that I call SplitStorm.
The core idea is not product-related. It's about treating layout configurations as data that can be stored and restored. While SAP provides APIs to define and modify split containers, it lacks built-in support for introspection and persistence of these layout states.
Problem Statement
There are a few typical situations where this becomes relevant:
Users have different SAPGUI settings (theme, fonts or fontsize) which results in different output and hidden information.
- Different users have different interest on different areas. One user needs to have a bigger view on the global ALV-Grid, another colleague wants to see more of the detail view.
- Different screen resolutions result in unused screen areas, e.g. when the ratio is set to 30% for the left object tree, what fits great on FullHD but is to much for higher resolutions or wide screens
Approach
Using recursive traversal and some clever techniques, it’s possible to extract the layout structure and size ratios of a splitter hierarchy. The data is stored in a simple, format that can later be used to reconstruct the exact same configuration.
The main features include:
Reading the layout hierarchy of nested CL_GUI_SPLITTER_CONTAINER instances
Capturing the orientation, pane sizes, and nesting relationships
Rebuilding the structure with the same layout at a later time
How does it work?
The method ANALYZE_AND_SAVE analyzes the container hierarchy of the given main container. To retrieve the number of rows and columns of a splitter, I simply try to get all containers in a 20x20-Grid. If I do not get a valid container for row 3 and column 4 then I know that there are max 2 rows or max 3 columns.
The height and width for each pane will be read and stored in a simple matrix information
Data will be saved in the Index-Table INDX where data in any format can be stored easily. A better way would be to transform the data to JSON or serialize in a data object and store it in an own table. But for my purposes I think that INDX is okay.
Restoring the data reads the path to the current container and sets height and width.
It's easy to build the standard layout and the restore the configuration from saved data.
The Code
The actual complete source code is available at github: Splitstorm
Demo report
The Demo report builds a sample configuration using splitter containers without any controls in it.
You can save the current configuration, load or delete an existing layout.
REPORT zt9r_splitstorm_demo01.
SELECTION-SCREEN PUSHBUTTON /1(20) TEXT-sav USER-COMMAND zsave.
SELECTION-SCREEN PUSHBUTTON /1(20) TEXT-lod USER-COMMAND zload.
SELECTION-SCREEN PUSHBUTTON /1(20) TEXT-del USER-COMMAND zdelete.
INITIALIZATION.
DATA(docker) = NEW cl_gui_docking_container( ratio = 90 side = cl_gui_docking_container=>dock_at_bottom ).
DATA(sca) = NEW cl_gui_splitter_container( parent = docker rows = 2 columns = 2 ).
DATA(sca1) = NEW cl_gui_splitter_container( parent = sca->get_container( row = 1 column = 1 ) rows = 1 columns = 2 ).
DATA(sca2) = NEW cl_gui_splitter_container( parent = sca->get_container( row = 2 column = 1 ) rows = 2 columns = 1 ).
DATA(scb1) = NEW cl_gui_splitter_container( parent = sca2->get_container( row = 1 column = 1 ) rows = 1 columns = 2 ).
DATA(scb2) = NEW cl_gui_splitter_container( parent = sca2->get_container( row = 2 column = 1 ) rows = 2 columns = 1 ).
AT SELECTION-SCREEN.
DATA(info) = NEW zt9r_splitstorm( sy-cprog ).
CASE sy-ucomm.
WHEN 'ZSAVE'.
info->analyze_and_save( docker ).
WHEN 'ZLOAD'.
info->load_and_restore( docker ).
WHEN 'ZDELETE'.
info->delete( ).
ENDCASE.Main class
CLASS zt9r_splitstorm DEFINITION PUBLIC.
PUBLIC SECTION.
TYPES: BEGIN OF ty_splitter_info,
path TYPE string,
rows TYPE i,
columns TYPE i,
row_heights TYPE STANDARD TABLE OF i WITH DEFAULT KEY,
column_widths TYPE STANDARD TABLE OF i WITH DEFAULT KEY,
END OF ty_splitter_info.
TYPES ty_splitter_info_tab TYPE STANDARD TABLE OF ty_splitter_info WITH DEFAULT KEY.
METHODS delete.
METHODS load_and_restore
IMPORTING
i_container TYPE REF TO cl_gui_container.
METHODS analyze_and_save
IMPORTING
i_container TYPE REF TO cl_gui_container.
METHODS constructor
IMPORTING
repid TYPE clike.
PRIVATE SECTION.
METHODS save.
METHODS load.
CONSTANTS mc_root TYPE string VALUE `ROOT`.
METHODS analyze_recursive
IMPORTING
i_container TYPE REF TO cl_gui_control
i_path TYPE string
RETURNING
VALUE(result) TYPE abap_bool.
METHODS get_row_count
IMPORTING
i_splitter TYPE REF TO cl_gui_splitter_container
RETURNING
VALUE(result) TYPE i.
METHODS get_column_count
IMPORTING
i_splitter TYPE REF TO cl_gui_splitter_container
RETURNING
VALUE(result) TYPE i.
METHODS restore_recursive
IMPORTING
i_container TYPE REF TO cl_gui_control
i_path TYPE string.
METHODS get_splitter_row_height
IMPORTING
io_splitter TYPE REF TO cl_gui_splitter_container
RETURNING
VALUE(r_size) TYPE i.
METHODS get_splitter_col_width
IMPORTING
io_splitter TYPE REF TO cl_gui_splitter_container
RETURNING
VALUE(r_size) TYPE i.
METHODS analyze
IMPORTING
i_container TYPE REF TO cl_gui_container
RETURNING
VALUE(result) TYPE ty_splitter_info_tab.
METHODS restore_sizes
IMPORTING
i_container TYPE REF TO cl_gui_container.
METHODS get_id
RETURNING
VALUE(r_id) TYPE char20.
DATA size_info TYPE ty_splitter_info_tab.
DATA repid TYPE c LENGTH 40.
ENDCLASS.
CLASS ZT9R_SPLITSTORM IMPLEMENTATION.
* <SIGNATURE>---------------------------------------------------------------------------------------+
* | Instance Private Method ZT9R_SPLITSTORM->ANALYZE
* +-------------------------------------------------------------------------------------------------+
* | [--->] I_CONTAINER TYPE REF TO CL_GUI_CONTAINER
* | [<-()] RESULT TYPE TY_SPLITTER_INFO_TAB
* +--------------------------------------------------------------------------------------</SIGNATURE>
METHOD analyze.
LOOP AT i_container->children INTO DATA(child).
analyze_recursive( i_container = child
i_path = mc_root ).
ENDLOOP.
result = size_info.
ENDMETHOD.
* <SIGNATURE>---------------------------------------------------------------------------------------+
* | Instance Public Method ZT9R_SPLITSTORM->ANALYZE_AND_SAVE
* +-------------------------------------------------------------------------------------------------+
* | [--->] I_CONTAINER TYPE REF TO CL_GUI_CONTAINER
* +--------------------------------------------------------------------------------------</SIGNATURE>
METHOD analyze_and_save.
analyze( i_container = i_container ).
save( ).
ENDMETHOD.
* <SIGNATURE>---------------------------------------------------------------------------------------+
* | Instance Private Method ZT9R_SPLITSTORM->ANALYZE_RECURSIVE
* +-------------------------------------------------------------------------------------------------+
* | [--->] I_CONTAINER TYPE REF TO CL_GUI_CONTROL
* | [--->] I_PATH TYPE STRING
* | [<-()] RESULT TYPE ABAP_BOOL
* +--------------------------------------------------------------------------------------</SIGNATURE>
METHOD analyze_recursive.
TRY.
DATA(lo_splitter) = CAST cl_gui_splitter_container( i_container ).
result = abap_true.
CATCH cx_sy_move_cast_error.
result = abap_false.
RETURN.
ENDTRY.
DATA(lv_rows) = get_row_count( lo_splitter ).
DATA(lv_columns) = get_column_count( lo_splitter ).
DATA lt_row_heights TYPE STANDARD TABLE OF i WITH DEFAULT KEY.
DATA lt_col_widths TYPE STANDARD TABLE OF i WITH DEFAULT KEY.
DO lv_rows TIMES.
APPEND get_splitter_row_height( lo_splitter ) TO lt_row_heights.
ENDDO.
DO lv_columns TIMES.
APPEND get_splitter_col_width( lo_splitter ) TO lt_col_widths.
ENDDO.
APPEND VALUE ty_splitter_info( path = i_path
rows = lv_rows
columns = lv_columns
row_heights = lt_row_heights
column_widths = lt_col_widths )
TO size_info.
DATA(index_row) = 0.
DATA(index_col) = 0.
DO lv_rows TIMES.
index_col = 0.
index_row = index_row + 1.
DO lv_columns TIMES.
index_col = index_col + 1.
DATA(lo_container) = lo_splitter->get_container( row = index_row
column = index_col ).
LOOP AT lo_container->children INTO DATA(lo_child).
IF NOT analyze_recursive(
i_container = lo_child
i_path = |{ i_path }[{ index_row },{ index_col }]| ).
EXIT. " from do
ENDIF.
ENDLOOP.
ENDDO.
ENDDO.
ENDMETHOD.
* <SIGNATURE>---------------------------------------------------------------------------------------+
* | Instance Public Method ZT9R_SPLITSTORM->CONSTRUCTOR
* +-------------------------------------------------------------------------------------------------+
* | [--->] REPID TYPE CLIKE
* +--------------------------------------------------------------------------------------</SIGNATURE>
METHOD constructor.
me->repid = repid.
ENDMETHOD.
* <SIGNATURE>---------------------------------------------------------------------------------------+
* | Instance Public Method ZT9R_SPLITSTORM->DELETE
* +-------------------------------------------------------------------------------------------------+
* +--------------------------------------------------------------------------------------</SIGNATURE>
METHOD delete.
DATA(id) = get_id( ).
DELETE FROM DATABASE indx(z1) ID id.
IF sy-subrc = 0 AND size_info IS NOT INITIAL.
MESSAGE 'Splitter configuration deleted' TYPE 'S'.
ENDIF.
ENDMETHOD.
* <SIGNATURE>---------------------------------------------------------------------------------------+
* | Instance Private Method ZT9R_SPLITSTORM->GET_COLUMN_COUNT
* +-------------------------------------------------------------------------------------------------+
* | [--->] I_SPLITTER TYPE REF TO CL_GUI_SPLITTER_CONTAINER
* | [<-()] RESULT TYPE I
* +--------------------------------------------------------------------------------------</SIGNATURE>
METHOD get_column_count.
result = 0.
DO 20 TIMES.
DATA(container) = i_splitter->get_container( row = 1
column = sy-index ).
IF container IS INITIAL.
RETURN.
ELSE.
result = result + 1.
ENDIF.
ENDDO.
ENDMETHOD.
* <SIGNATURE>---------------------------------------------------------------------------------------+
* | Instance Private Method ZT9R_SPLITSTORM->GET_ID
* +-------------------------------------------------------------------------------------------------+
* | [<-()] R_ID TYPE CHAR20
* +--------------------------------------------------------------------------------------</SIGNATURE>
METHOD get_id.
r_id = |SPLITTER-{ repid }-{ sy-uname }|.
ENDMETHOD.
* <SIGNATURE>---------------------------------------------------------------------------------------+
* | Instance Private Method ZT9R_SPLITSTORM->GET_ROW_COUNT
* +-------------------------------------------------------------------------------------------------+
* | [--->] I_SPLITTER TYPE REF TO CL_GUI_SPLITTER_CONTAINER
* | [<-()] RESULT TYPE I
* +--------------------------------------------------------------------------------------</SIGNATURE>
METHOD get_row_count.
result = 0.
DO 20 TIMES.
DATA(container) = i_splitter->get_container( row = sy-index
column = 1 ).
IF container IS INITIAL.
RETURN.
ELSE.
result = result + 1.
ENDIF.
ENDDO.
ENDMETHOD.
* <SIGNATURE>---------------------------------------------------------------------------------------+
* | Instance Private Method ZT9R_SPLITSTORM->GET_SPLITTER_COL_WIDTH
* +-------------------------------------------------------------------------------------------------+
* | [--->] IO_SPLITTER TYPE REF TO CL_GUI_SPLITTER_CONTAINER
* | [<-()] R_SIZE TYPE I
* +--------------------------------------------------------------------------------------</SIGNATURE>
METHOD get_splitter_col_width.
io_splitter->get_column_width( EXPORTING
id = sy-index
IMPORTING
result = r_size ).
cl_gui_cfw=>flush( ).
ENDMETHOD.
* <SIGNATURE>---------------------------------------------------------------------------------------+
* | Instance Private Method ZT9R_SPLITSTORM->GET_SPLITTER_ROW_HEIGHT
* +-------------------------------------------------------------------------------------------------+
* | [--->] IO_SPLITTER TYPE REF TO CL_GUI_SPLITTER_CONTAINER
* | [<-()] R_SIZE TYPE I
* +--------------------------------------------------------------------------------------</SIGNATURE>
METHOD get_splitter_row_height.
io_splitter->get_row_height( EXPORTING
id = sy-index
IMPORTING
result = r_size ).
cl_gui_cfw=>flush( ).
ENDMETHOD.
* <SIGNATURE>---------------------------------------------------------------------------------------+
* | Instance Private Method ZT9R_SPLITSTORM->LOAD
* +-------------------------------------------------------------------------------------------------+
* +--------------------------------------------------------------------------------------</SIGNATURE>
METHOD load.
DATA(id) = get_id( ).
IMPORT splitter_info TO size_info FROM DATABASE indx(z1) ID id.
IF sy-subrc = 0 AND size_info IS NOT INITIAL.
MESSAGE 'Splitter configuration loaded' TYPE 'S'.
ENDIF.
ENDMETHOD.
* <SIGNATURE>---------------------------------------------------------------------------------------+
* | Instance Public Method ZT9R_SPLITSTORM->LOAD_AND_RESTORE
* +-------------------------------------------------------------------------------------------------+
* | [--->] I_CONTAINER TYPE REF TO CL_GUI_CONTAINER
* +--------------------------------------------------------------------------------------</SIGNATURE>
METHOD load_and_restore.
load( ).
restore_sizes( i_container = i_container ).
ENDMETHOD.
* <SIGNATURE>---------------------------------------------------------------------------------------+
* | Instance Private Method ZT9R_SPLITSTORM->RESTORE_RECURSIVE
* +-------------------------------------------------------------------------------------------------+
* | [--->] I_CONTAINER TYPE REF TO CL_GUI_CONTROL
* | [--->] I_PATH TYPE STRING
* +--------------------------------------------------------------------------------------</SIGNATURE>
METHOD restore_recursive.
TRY.
DATA(splitter) = CAST cl_gui_splitter_container( i_container ) ##NO_TEXT.
CATCH cx_sy_move_cast_error.
RETURN.
ENDTRY.
READ TABLE size_info INTO DATA(info) WITH KEY path = i_path.
IF sy-subrc <> 0 OR splitter IS INITIAL.
RETURN.
ENDIF.
DATA(idx) = 1.
LOOP AT info-row_heights INTO DATA(lv_r).
splitter->set_row_height( id = idx height = lv_r ).
idx = idx + 1.
ENDLOOP.
idx = 1.
LOOP AT info-column_widths INTO DATA(lv_c).
splitter->set_column_width( id = idx width = lv_c ).
idx = idx + 1.
ENDLOOP.
" Rekursiv weiter
DATA(index_row) = 0.
DATA(index_col) = 0.
DO info-rows TIMES.
index_col = 0.
index_row = index_row + 1.
DO info-columns TIMES.
index_col = index_col + 1.
DATA(lo_container) = splitter->get_container( row = index_row
column = index_col ).
DATA(lv_subpath) = |{ i_path }[{ index_row },{ index_col }]|.
LOOP AT lo_container->children INTO DATA(lo_child).
restore_recursive( i_container = lo_child
i_path = lv_subpath ).
ENDLOOP.
ENDDO.
ENDDO.
ENDMETHOD.
* <SIGNATURE>---------------------------------------------------------------------------------------+
* | Instance Private Method ZT9R_SPLITSTORM->RESTORE_SIZES
* +-------------------------------------------------------------------------------------------------+
* | [--->] I_CONTAINER TYPE REF TO CL_GUI_CONTAINER
* +--------------------------------------------------------------------------------------</SIGNATURE>
METHOD restore_sizes.
IF size_info IS INITIAL.
RETURN.
ENDIF.
LOOP AT i_container->children INTO DATA(child).
restore_recursive( i_container = child
i_path = mc_root ).
ENDLOOP.
ENDMETHOD.
* <SIGNATURE>---------------------------------------------------------------------------------------+
* | Instance Private Method ZT9R_SPLITSTORM->SAVE
* +-------------------------------------------------------------------------------------------------+
* +--------------------------------------------------------------------------------------</SIGNATURE>
METHOD save.
IF size_info IS INITIAL.
RETURN.
ENDIF.
DATA(id) = get_id( ).
EXPORT splitter_info FROM size_info TO DATABASE indx(z1) ID id.
MESSAGE 'Splitter configuration saved' TYPE 'S'.
ENDMETHOD.
ENDCLASS.