Technology Blog Posts by Members
cancel
Showing results for 
Search instead for 
Did you mean: 

 

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.

 

1 Comment
Labels in this area