Splitstorm – Splitterkonfiguration auslesen und wiederherstellen

Auch wenn SAPGUI-Applikationen inzwischen schon lange nicht mehr the latest-greatest stuff sind, gibt es immer noch Dinge, die ich ausprobieren kann. In diesem Fall wollte ich die Splitter-Konfiguration generisch auslesen, um die Größe der Splitter wiederherstellen zu können.

Aber von Anfang an…

SAPGUI-Splitter

Die Klasse CL_GUI_SPLITTER_CONTAINER bietet bei der Dynpro-Programmierung die Möglichkeit, mehrere Container zu definieren. Hierbei kann man festlegen, wie viele Spalten und Zeilen der Splitter haben soll. In folgendem Beispiel habe ich vier Splitter-Container verwendet:

  • Der erste Splitter-Container teilt den gesamten Bildschirm in vier Container auf
  • Der zweite Container teilt A1 in zwei Spalten
  • Der dritte Container teilt A2 in zwei Zeilen
    • Der vierte Container teilt die obere Zeile in zwei Spalten
    • Der fünfte Container teilt die untere Zeile wiederum in zwei Zeilen

Das Problem bei bei den Splitter-Containern ist, dass der Anwender mit dem Layout leben muss, das einmal festgelegt wurde. Zugegeben, ein so verschachteltes Layout ist eher selten. Meistens hat man einen Splitter mit zwei Containern, deren Größe man dann verschieben kann. An dem Layout selbst kann man auch nur durch Programmierung noch etwas ändern. Allerdings kann man sich als Anwender die einzelnen Fenster so zurechtschieben, dass sie für das eingestellte Theme, Bildschirmauflösung und die speziellen Vorlieben besser passen.

Persönliche Einstellungen

Durch unterschiedliche Einstellungen im SAPGUI (Theme, Schriftgröße) und andere Bildschirmauflösungen kann die Darstellung deutlich von der abweichen, die der Entwickler vor Augen hatte. Das aktuelle Quartz-Theme zum Beispiel ist deutlich raumgreifender, als die immer noch beliebten Themes SAP-Signature und Blue-Chrystal. Dadurch kann es sein, dass bestimmte Container gerade um einen halben Zentimeter zu klein sind und deswegen wichtige Informationen nicht auf einen Blick erkennbar sind.

Splitstorm

Mit diesem Tool ist es nun einfach, für jede beliebige Transaktion, die mehrere Splitter beherbergt, die aktuelle Konfiguration benutzerspezifisch zu speichern und wieder abzurufen.

Known bugs

Ratio vs Pixel

Meine Programmierung geht davon aus, dass alle Splitter frei beweglich sind. Es gibt jedoch auch Splitter, die mit der Absicht programmiert wurden, nicht verschiebbar zu sein. Das ist häufig der Fall, wenn man über einem Control eine eigene Toolbar haben möchte. Eigentlich müsste beim Analysieren die korrekte Größe ermittelt und entsprechend wiederhergestellt werden. Allerdings arbeitet meine Klasse mit Prozentangaben. Dadurch kann es bei der Wiederherstellung zu Differenzen kommen, denn die besagte Toolbar wird in der Regel mit einer festen Pixelgröße definiert.

Änderung der Konfiguration

Das Programm erkennt keine Änderung der Konfiguration. Wenn sich die Darstellung ändert (mehr oder weniger Splitter, mehr oder weniger Splitter-Elemente), dann kann eine gespeicherte Konfiguration nicht mehr angewendet werden und es kommt eventuell zu merkwürdigen Nebeneffekten. In diesem Fall muss die persönliche Konfiguration einmal gelöscht und erneut gespeichert werden.

CL_GUI_EASY_SPLITTER_CONTAINER

Das Programm erkennt den CL_GUI_EASY_SPLITTER_CONTAINER nicht. Es werden ausschließlich CL_GUI_SPLITTER_CONTAINER erkannt und ausgewertet.

Sourcecode

Der komplette Sourcecode steht wie immer auf github “Splitstorm” zur Verfügung.

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.
Enno Wulff