Quantcast
Channel: ABAP OO Archive - Tricktresor
Viewing all 53 articles
Browse latest View live

Interne Tabelle mit COMPONENTS

$
0
0

Hier wird eine interne Tabelle anhand von einer beliebigen Anzahl Feldern generiert.

Die Tabelle, aus der die Daten gelesen werden sollen, muss angegeben werden. Dann müssen die Felder EINZELN in der Select-Option eingetragen werden. Die Feldnamen müssen mit dem Namen des Datenelements übereinstimmen.

Eine schönere Version wäre die Komponenten der angegebenen Tabelle zu ermitteln (CL_ABAP_STRUCTDESCR->GET_COMPONENTS).

Achtung:
Es erfolgt keine Prüfung ob die Felder in der Tabelle vorhanden sind! Wenn Feldnamen angegeben werden, die nicht in der angegebenen Tabelle existieren, kommt es zu einem Dump!

Es erfolgt keine Ausgabe der Daten!

Coding

*== Create internal table dynamically by given fields
DATA: gr_element   TYPE REF TO cl_abap_elemdescr,
gr_struc     TYPE REF TO cl_abap_structdescr,
gr_table     TYPE REF TO cl_abap_tabledescr,
gt_comp      TYPE cl_abap_structdescr=>component_table,
gs_comp      LIKE LINE OF lt_comp,
gr_data      TYPE REF TO data,
gt_fields    TYPE STANDARD TABLE OF string,
hf_fieldname TYPE fieldname.

FIELD-SYMBOLS <table> TYPE ANY TABLE.

*== selection screen
PARAMETERS p_table TYPE tabname DEFAULT 'MARA'.
SELECT-OPTIONS s_fields FOR hf_fieldname.

START-OF-SELECTION.

  LOOP AT s_fields.
CLEAR: gs_comp.

*== Element Description for field
gr_element ?= cl_abap_elemdescr=>describe_by_name( s_fields-low ).
*== Field name
gs_comp-name = s_fields-low.
*== Field type (element)
gs_comp-type = gr_element.
*== add element to components table
APPEND gs_comp TO gt_comp.

*== add column to selection table
APPEND s_fields-low TO gt_fields.

  ENDLOOP.

*== Create structure/ work area
gr_struc = cl_abap_structdescr=>create( gt_comp ).

*== create table by structure reference
gr_table = cl_abap_tabledescr=>create(
p_line_type  = gr_struc
p_table_kind = cl_abap_tabledescr=>tablekind_std
p_unique     = abap_false ).

*== create data handle for table
CREATE DATA gr_data TYPE HANDLE gr_table.

*== assign data to table-pointer
ASSIGN gr_data->* TO <table>.

*== Select data into dynamic internal table
SELECT (gt_fields) FROM  (p_table)
INTO CORRESPONDING FIELDS OF TABLE <table>
UP TO 10 ROWS.

 


ALV-Layout gegen Überschreiben schützen

$
0
0

Die Layoutverwaltung des ALV (Grid als auch Liste) hat ein paar Macken. Eine davon ist die, dass es nur ein Berechtigungsobjekt S_ALV_LAYO gibt. Mit diesem Objekt wird definiert, ob ein Anwender Layouts ändern darf, oder nicht. Es gibt keine Unterscheidung zwischen System- und Benutzerlayouts.

Speicherung

Die Varianten werden in den Clustertabellen LTDX und LTDXT gespeichert. Benutzerlayouts müssen mit einer Zahl oder einem Buchstaben anfangen, Systemlayouts mit einem Schrägstrich. Diesen Umstand machen wir uns zu nutze, indem wir ein vorhandenes Layout einfach auf ein Layout kopieren, das mit einem Sonderzeichen beginnt, wie z.B. der Tilde. Dieses Layout kann dann nicht mehr geändert werden, da der Name unzulässig ist:

Layout schützen

Der folgende Programmcode liest ein vorhandenes Layout ein und speichert es unter neuem Namen ab.

Der Eintrag für die Texte im Cluster LTDXT wird nicht kopiert. Dies sollte der Vollständigkeit halber ebenfalls gemacht werden!
  DATA: gt_ltdx TYPE STANDARD TABLE OF ltdx WITH NON-UNIQUE DEFAULT KEY,
        gs_ltdx LIKE LINE OF lt_ltdx.

  PARAMETERS: p_report TYPE repid       OBLIGATORY.
  PARAMETERS: p_handle TYPE slis_handl  OBLIGATORY.
  PARAMETERS: p_layout TYPE slis_vari   OBLIGATORY.
  SELECT *
    INTO TABLE gt_ltdx
    FROM ltdx
    WHERE relid   = 'LT'
      AND report  = p_report
      AND handle  = p_handle
      AND variant = p_layout
      AND type    = 'F'.
  CHECK sy-subrc = 0.

*== Replace first character in variant with "unallowed" character "~"
  gs_ltdx-variant = p_layout.
  gs_ltdx-variant(1) = '~'.

  MODIFY gt_ltdx FROM gs_ltdx TRANSPORTING variant WHERE variant <> gs_ltdx-variant.

  TRY.
      INSERT ltdx FROM TABLE gt_ltdx.
      COMMIT WORK.
      MESSAGE i000(oo) WITH 'Layout kopiert:' gs_ltdx-variant.
    CATCH cx_sy_open_sql_db.
      MESSAGE i000(oo) WITH 'Fehler beim Kopieren!'.
  ENDTRY.

Fertig

Nachdem ein Layout kopiert wurde, taucht es in der Layoutverwaltung auf und kann ausgewählt und verwendet werden. Allerdings kann es nicht mehr geändert werden, denn der Name kann aufgrund des Sonderzeichens im Dialog nicht verwendet werden.

Das Layout ist nur durch Überschreiben geschützt! Es kann jedoch nach wie vor gelöscht werden!

Komplettes Programm

Hier das komplette Programm mit Berücksichtigung der Texte und F4-Hilfe für vorhandene Layouts.

ZZ_PROTECT_ALV_LAYOUT
REPORT  zz_protect_alv_layout.

DATA c_protection_character TYPE c LENGTH 1 VALUE '~'.

SELECTION-SCREEN BEGIN OF BLOCK bl1 WITH FRAME TITLE text-bl1.
PARAMETERS  p_report TYPE repid       OBLIGATORY.
PARAMETERS: p_handle TYPE slis_handl  OBLIGATORY.
PARAMETERS: p_layout TYPE slis_vari   OBLIGATORY.
SELECTION-SCREEN END OF BLOCK bl1.

AT SELECTION-SCREEN ON VALUE-REQUEST FOR p_layout.
  PERFORM f4_layout.

AT SELECTION-SCREEN ON p_layout.
  IF p_layout(1)  <> '/'.
    MESSAGE 'Nur ungeschütze Standardlayouts (beginnend mit /) können geschützt werden' TYPE 'E'.
  ENDIF.

START-OF-SELECTION.

  PERFORM protect.
*&---------------------------------------------------------------------*
*&      Form  PROTECT
*&---------------------------------------------------------------------*
* Replace first character in ALV-Layout with "unallowed" character
* This way this variant can't be saved any more and thus not be overwritten
*&---------------------------------------------------------------------*
FORM protect .

  DATA lt_ltdx  TYPE STANDARD TABLE OF ltdx  WITH NON-UNIQUE DEFAULT KEY.
  DATA ls_ltdx  LIKE LINE OF lt_ltdx.
  DATA lt_ltdxt TYPE STANDARD TABLE OF ltdxt WITH NON-UNIQUE DEFAULT KEY.
  DATA ls_ltdxt LIKE LINE OF lt_ltdxt.

*== read layout (main data)
  SELECT * INTO TABLE lt_ltdx
    FROM ltdx
    WHERE relid   = 'LT'
      AND report  = p_report
      AND handle  = p_handle
      AND variant = p_layout
      AND type    = 'F'.
  CHECK sy-subrc = 0.

*--------------------------------------------------------------------*
* Replace first character in variant with unallowed character
*--------------------------------------------------------------------*
  ls_ltdx-variant = p_layout.
  ls_ltdx-variant(1) = c_protection_character.

  MODIFY lt_ltdx FROM ls_ltdx TRANSPORTING variant WHERE variant <> ls_ltdx-variant.

  TRY.
*== Insert layout (main data)
      INSERT ltdx FROM TABLE lt_ltdx.
*== read layout (texts)
      SELECT * FROM ltdxt INTO TABLE lt_ltdxt
        WHERE relid   = 'LT'
          AND report  = p_report
          AND handle  = p_handle
          AND variant = p_layout
          AND type    = 'F'.
      IF sy-subrc = 0.
*== Insert Layout (text)
        ls_ltdxt-variant = ls_ltdx-variant.
        MODIFY lt_ltdxt FROM ls_ltdxt TRANSPORTING variant WHERE variant <> ls_ltdxt-variant.
        INSERT ltdxt FROM TABLE lt_ltdxt.
      ENDIF.
      COMMIT WORK.
      MESSAGE i000(oo) WITH 'Geschützte Variante' ls_ltdx-variant 'wurde neu angelegt'.
    CATCH cx_sy_open_sql_db.
      MESSAGE 'ALV-Layout existiert schon und wird nicht überschrieben' TYPE 'I'.
  ENDTRY.

ENDFORM.                    " PROTECT

*&---------------------------------------------------------------------*
*&      Form  F4_LAYOUT
*&---------------------------------------------------------------------*
FORM f4_layout.

  DATA lv_repid      TYPE syrepid.
  data lv_dynnr      TYPE sydynnr.
  data lt_dynpfields TYPE STANDARD TABLE OF dynpread WITH NON-UNIQUE DEFAULT KEY.
  DATA ls_variant    TYPE disvariant.

  FIELD-SYMBOLS: <ls_dynpfield> LIKE LINE OF lt_dynpfields.

*== Layout parameters
  ls_variant-report  = p_report.
  ls_variant-variant = p_layout.
  ls_variant-handle  = p_handle.

*== updating from current screen
*--------------------------------------------------------------------*
  lv_repid = sy-repid.
  lv_dynnr = '1000'.

  CALL FUNCTION 'DYNP_VALUES_READ'                          "#EC
    EXPORTING
      dyname               = lv_repid
      dynumb               = lv_dynnr
      translate_to_upper   = 'X'
      request              = 'A'
    TABLES
      dynpfields           = lt_dynpfields
    EXCEPTIONS             = 11.

  READ TABLE lt_dynpfields WITH KEY fieldname = 'P_REPORT'   ASSIGNING <ls_dynpfield>.
  IF sy-subrc = 0.
    ls_variant-report = <ls_dynpfield>-fieldvalue.
  ENDIF.

  READ TABLE lt_dynpfields WITH KEY fieldname = 'P_HANDLE'   ASSIGNING <ls_dynpfield>.
  IF sy-subrc = 0.
    ls_variant-handle = <ls_dynpfield>-fieldvalue.
  ENDIF.

*--------------------------------------------------------------------*
* Standard ALV-F4
*--------------------------------------------------------------------*
  CALL FUNCTION 'REUSE_ALV_VARIANT_F4'
    EXPORTING
      is_variant         = ls_variant
      i_save             = 'X'
      i_display_via_grid = 'X'  " Only standard variants here
    IMPORTING
      es_variant         = ls_variant
    EXCEPTIONS
      not_found          = 1
      program_error      = 2
      OTHERS             = 3.
  CHECK sy-subrc = 0.
  CHECK ls_variant-variant IS NOT INITIAL.
  p_layout = ls_variant-variant.

ENDFORM.                    " F4_LAYOUT

Drop Files in ALV-Grid

$
0
0

drag files in grid

Seit etwa 2012 ist es möglich, Dateien aus dem Explorer in ein ALV-Grid zu ziehen und die Namen dort zu verarbeiten (SAP Note 1748468). Ob das in deinem SAP-System bereits funktioniert, kannst du daran erkennen, dass die Klasse CL_GUI_ALV_GRID die Methode GET_DROPPED_EXTERNAL_FILES hat.

Das Event DROP_EXTERNAL_FILES muss registriert werden und mit der Methode DRAG_ACCEPT_FILES( 1 ) muss die Funktionalität aktiviert werden.

Es kann das gesamte ALV-Grid-Control als "Ablagefläche" genutzt werden, also auch dort, wo keine Zeilen vorhanden sind. Werden die Dateien auf einer Zelle abgelegt, dann kann diese Zelle bestimmte werden. In dem Beispielcoding werden die Dateien der Spalte hinzugefügt, auf der sie abgelegt wurden.

Coding

REPORT zz_drop_files_on_grid.

*----------------------------------------------------------------------*
*       CLASS lcl_appl DEFINITION
*----------------------------------------------------------------------*
CLASS lcl_appl DEFINITION.

  PUBLIC SECTION.

*== type for Grid-Demonstration: Columns EINS, ZWEI and DREI
    TYPES: BEGIN OF ty_files,
             eins TYPE string,
             zwei TYPE string,
             drei TYPE string,
           END OF ty_files.

*== table containing the data (text/ files)
    CLASS-DATA gt_data TYPE STANDARD TABLE OF ty_files.
*== ALV-Grid
    CLASS-DATA gr_grid TYPE REF TO cl_gui_alv_grid.

*== setup ALV-Grid
    CLASS-METHODS init.
*== get dropped files and display in grid
    CLASS-METHODS get_dropped_files FOR EVENT drop_external_files OF cl_gui_alv_grid
                                    IMPORTING files.

ENDCLASS.                    "lcl_appl DEFINITION

*----------------------------------------------------------------------*
*       CLASS lcl_appl IMPLEMENTATION
*----------------------------------------------------------------------*
CLASS lcl_appl IMPLEMENTATION.

  METHOD init.

*== local data
    DATA lt_fcat         TYPE lvc_t_fcat.
    FIELD-SYMBOLS <fcat> LIKE LINE OF lt_fcat.
    FIELD-SYMBOLS <data> LIKE LINE OF gt_data.

    CHECK gr_grid IS INITIAL.

*== set initial data
    DO 5 TIMES.
      APPEND INITIAL LINE TO gt_data ASSIGNING <data>.
      <data>-eins = 'Drop'.
      <data>-zwei = 'Files'.
      <data>-drei = 'Here'.
    ENDDO.

*== Create Grid-control
    CREATE OBJECT gr_grid
      EXPORTING
        i_parent = cl_gui_container=>screen0.

*== build field catalog
    APPEND INITIAL LINE TO lt_fcat ASSIGNING <fcat>.
    <fcat>-fieldname = 'EINS'.
    <fcat>-reptext   = 'Eins'.
    <fcat>-style     = 1.
    <fcat>-datatype  = 'STRG'.
    APPEND INITIAL LINE TO lt_fcat ASSIGNING <fcat>.
    <fcat>-fieldname = 'ZWEI'.
    <fcat>-reptext   = 'Zwei'.
    <fcat>-style     = 2.
    <fcat>-datatype  = 'STRG'.
    APPEND INITIAL LINE TO lt_fcat ASSIGNING <fcat>.
    <fcat>-fieldname = 'DREI'.
    <fcat>-reptext   = 'Drei'.
    <fcat>-style     = 4.
    <fcat>-datatype  = 'STRG'.

*== display grid
    gr_grid->set_table_for_first_display(
      CHANGING it_fieldcatalog = lt_fcat
               it_outtab       = gt_data ).

*== Allow drop files action
    gr_grid->drag_accept_files( 1 ).

*== set handler to react on file drop
    SET HANDLER get_dropped_files FOR gr_grid ACTIVATION abap_true.

  ENDMETHOD.                                               "init

  METHOD get_dropped_files.

*== local data
    DATA lt_files        TYPE filetable.
    FIELD-SYMBOLS <file> LIKE LINE OF lt_files.
    DATA lv_row_id       TYPE i.
    DATA lv_col_id       TYPE i.
    FIELD-SYMBOLS <data> LIKE LINE OF gt_data.

*== get dropped files in a table
    gr_grid->get_dropped_external_files(
       IMPORTING  files     = lt_files
                  row_id    = lv_row_id
                  col_id    = lv_col_id
       EXCEPTIONS OTHERS    = 3 ).

*== put file names in grid
    IF lt_files IS NOT INITIAL.
      CLEAR gt_data.
      LOOP AT lt_files ASSIGNING <file>.
        APPEND INITIAL LINE TO gt_data ASSIGNING <data>.
        CASE lv_col_id.
          WHEN 1. <data>-eins = <file>.
          WHEN 2. <data>-zwei = <file>.
          WHEN 3. <data>-drei = <file>.
        ENDCASE.
      ENDLOOP.
*== refresh display
      gr_grid->refresh_table_display( ).
    ENDIF.

  ENDMETHOD.                                               "get_dropped_files

ENDCLASS.                                                  "lcl_appl IMPLEMENTATION

START-OF-SELECTION.

*== call screen
  CALL SCREEN 100.

*----------------------------------------------------------------------*
*  MODULE 100 OUTPUT
*----------------------------------------------------------------------*
MODULE pbo_100 OUTPUT.

*== Use system status (so no status must be created; we only need "Leave Screen")
  SET PF-STATUS 'STLI' OF PROGRAM 'SAPMSSY0'.
*== init controls/ application
  lcl_appl=>init( ).

ENDMODULE.                                                 "100 OUTPUT

*----------------------------------------------------------------------*
*  MODULE 100 INPUT
*----------------------------------------------------------------------*
MODULE pai_100 INPUT.
  CASE sy-ucomm.
    WHEN 'BACK' OR '%EX' OR 'RW'.
      SET SCREEN 0. LEAVE SCREEN.
  ENDCASE.
ENDMODULE.                                                 "100 INPUT

Drag’n’Drop-Framwork ALV-Grid

$
0
0

Heute bin ich beim Surfen im SAP-System über die Klasse CL_ALV_DD_LISTBOX gestolpert. Dolle Sache:

Bild_2014_06_13_170651

 

Einfach zwei Datentabellen definieren, zwei Grids erzeugen, der Instanz von  cl_alv_dd_double_listbox übergeben und die zwei Funktionscodes zum Bewegen der Einträge definieren. Aufrufen und am Ende hat man in seinen zwei Datentabellen die vom Anwender definierten Einträge.

Die Anregung stammt aus dem Report RSPLS_ENQUEUE_INFO.

Coding

DATA gr_dd     TYPE REF TO cl_alv_dd_double_listbox.
DATA gr_grid_s TYPE REF TO cl_alv_dd_listbox.              "source
DATA gr_grid_t TYPE REF TO cl_alv_dd_listbox.              "target
DATA gr_cont_s TYPE REF TO cl_gui_custom_container.        "source
DATA gr_cont_t TYPE REF TO cl_gui_custom_container.        "target
DATA gs_layo_s TYPE lvc_s_layo.
DATA gs_layo_t TYPE lvc_s_layo.

DATA gt_fcat   TYPE lvc_t_fcat.
DATA gt_sort   TYPE lvc_t_sort.

DATA gt_data_s TYPE STANDARD TABLE OF t006a.
DATA gt_data_t TYPE STANDARD TABLE OF t006a.


START-OF-SELECTION.

  PERFORM init.

  CALL SCREEN 100.


*----------------------------------------------------------------------*
*  MODULE pbo OUTPUT
*----------------------------------------------------------------------*
MODULE pbo OUTPUT.

  SET PF-STATUS 'DD_GRID'.

  PERFORM prepare_grid USING gr_grid_s
                             gr_cont_s
                             'CONT_SOURCE'
                             gs_layo_s
                             gt_data_s.

  PERFORM prepare_grid USING gr_grid_t
                             gr_cont_t
                             'CONT_TARGET'
                             gs_layo_t
                             gt_data_t.

  IF gr_dd IS INITIAL.
    CREATE OBJECT gr_dd
      EXPORTING
        i_grid1 = gr_grid_s
        i_grid2 = gr_grid_t.
  ENDIF.

ENDMODULE.                                                 "pbo OUTPUT


*----------------------------------------------------------------------*
*  MODULE pai INPUT
*----------------------------------------------------------------------*
MODULE pai INPUT.
  CASE sy-ucomm.
    WHEN 'BACK'.
      SET SCREEN 0. LEAVE SCREEN.

    WHEN 'MOVE_TO_TARGET'.
      CALL METHOD gr_dd->movetogrid2
        EXPORTING
          i_ok_code = sy-ucomm.

    WHEN 'MOVE_TO_SOURCE'.
      CALL METHOD gr_dd->movetogrid1
        EXPORTING
          i_ok_code = sy-ucomm.
  ENDCASE.

*  IF NOT gr_dd IS INITIAL.
*    CALL METHOD gr_dd->set_grids_state( abap_true ). "X = display/ space = edit
*  ENDIF.

ENDMODULE.                                                 "pai INPUT


*&---------------------------------------------------------------------*
*&      Form  prepare_grid
*&---------------------------------------------------------------------*
FORM prepare_grid USING grid TYPE REF TO cl_alv_dd_listbox
                        cont TYPE REF TO cl_gui_custom_container
                        name TYPE        clike
                        layo TYPE        lvc_s_layo
                        data TYPE STANDARD TABLE.

  IF cont IS INITIAL.
*==   create container
    CREATE OBJECT cont
      EXPORTING
        container_name = name.
  ENDIF.

  IF grid IS INITIAL.
*== create grid
    CREATE OBJECT grid
      EXPORTING
        i_parent     = cont
        i_grid_style = 1.

*== set  grid
    CALL METHOD grid->set_table_for_first_display
      EXPORTING
        is_layout       = layo
      CHANGING
        it_outtab       = data
        it_fieldcatalog = gt_fcat
        it_sort         = gt_sort.
  ELSE.
*== refresh
    CALL METHOD grid->set_frontend_fieldcatalog
      EXPORTING
        it_fieldcatalog = gt_fcat.
    CALL METHOD grid->set_frontend_layout
      EXPORTING
        is_layout = gs_layo_s.
    CALL METHOD grid->refresh_table_display.
  ENDIF.

ENDFORM.                    "prepare_grid

*&---------------------------------------------------------------------*
*&      Form  init
*&---------------------------------------------------------------------*
FORM init.

*== local data
  FIELD-SYMBOLS  LIKE LINE OF gt_fcat.

*== get source data
  SELECT * FROM t006a INTO TABLE gt_data_s
      UP TO 20 ROWS
   WHERE spras = sy-langu.


*== set title for source:
  gs_layo_s-grid_title = text-003.
  gs_layo_s-smalltitle = 'X'.
  gs_layo_s-cwidth_opt = 'X'.
  gs_layo_s-sel_mode   = 'A'.

*== set title for target:
  gs_layo_t-grid_title = text-004.
  gs_layo_t-smalltitle = 'X'.
  gs_layo_t-cwidth_opt = 'X'.
  gs_layo_t-sel_mode   = 'A'.

*== get fieldcatalog
  CALL FUNCTION 'LVC_FIELDCATALOG_MERGE'
    EXPORTING
      i_structure_name       = 'T006A'
      i_client_never_display = 'X'
    CHANGING
      ct_fieldcat            = gt_fcat
    EXCEPTIONS
      OTHERS                 = 3.

*== display only unit and text
  LOOP AT gt_fcat ASSIGNING .
    CASE -fieldname.
      WHEN 'MSEHI' OR 'MSEH3' OR 'MSEH6'.
        -tech = abap_true.
    ENDCASE.
  ENDLOOP.

ENDFORM.                    "init

Dynpro

Ablauflogik

PROCESS BEFORE OUTPUT.
MODULE pbo.
*
PROCESS AFTER INPUT.
MODULE pai.

Bild_2014_06_13_180645

GUI-Status

Bild_2014_06_13_180616

Leider haben die Klassen eine kleine Macken. Sobald die ersten Einträge per Funktionstasten verschoben wurden, erscheint die Markierspalte und es ist kein Drag-and-Drop mehr möglich... :(

 

ALV-Grid-Erweiterung „Langtext“

$
0
0

Heute war Tag der Ablenkung. Nachdem ich in der CL_GUI_ALV_GRID-Klasse etwas herum experimentiert hatte, bin ich nun auch noch über eine Ableitung dieser Klasse mit der Erweiterung Automatische Langtextermittlung gestoßen: Die Klasse heißt CL_ALV_GRID_XT, das zugehörige Demoprogramm lautet R_ALV_GRID_XT.

Um zu verstehen, was SAP in diesem Fall mit Langtext meint, habe ich eine abgespeckte Demoversion geschrieben. In diesem Programm wird die Tabelle TVKO, die die Definition der Verkaufsorganisationen enthält. Mit der automatischen Langtextermittlung  können Felder definiert werden, die entweder einen beschreibenden Text in einer zugehörigen Customizingtabelle haben, oder bei dem die Werte als Festwerte in der Domäne hinterlegt sind.

Der Parameter KEEP = X bewirkt, dass das Schlüsselfeld angezeigt wird. Bei KEEP = space wird das Feld automatisch ausgeblendet und es wird nur das Feld mit dem Langtext angezeigt.

Zusätzlich gibt es die Option Ausgabe optimieren. Wird diese Option aktiviert, dann werden bei der Ausgabe alle komplett leeren Spalten ausgeblendet. Ein nettes Feature!

Beschreibung

Das Demoprogramm liest die Tabelle TVKO. Die Beschreibung der Verkaufsorganisationen stehen in der Texttabelle TVKOT. Um diese in einem ALV anzuzeigen müsste man entweder einen View bemühen oder die Texte aus der Tabelle manuell nachlesen. Diese Arbeit nimmt uns die Option Automatische Langtextermittlung ab. Um nicht extra eine neue Tabelle bzw. einen Feldkatalog aufbauen zu müssen, verwende ich vorhandene Felder, in die die Langtexte geschrieben werden. Der Name der VKORG kommt in das Feld TXNAM_KOP, der zugehörige Festwert zum Feld BSTYP ins Feld TXNAM_SDB.

Bild_2014_06_13_200629

Coding

REPORT.

PARAMETER p_optim  TYPE xfeld DEFAULT 'X'.
PARAMETER p_ltxt   TYPE xfeld DEFAULT 'X'.
PARAMETER p_keep   TYPE xfeld DEFAULT 'X'.


DATA gt_autotext   TYPE alv_auto_text_t.
DATA gt_data       TYPE STANDARD TABLE OF tvko.
DATA gr_alv        TYPE REF TO cl_alv_grid_xt.
DATA gr_container  TYPE REF TO cl_gui_custom_container.


START-OF-SELECTION.
  PERFORM start_of_selection.

*---------------------------------------------------------------------*
*       Form  start_of_selection
*---------------------------------------------------------------------*
*       Start program execution
*---------------------------------------------------------------------*
FORM start_of_selection.

  PERFORM get_data.

  IF p_ltxt IS NOT INITIAL.
    PERFORM prep_auto_text.
  ENDIF.

  CALL SCREEN 100.

ENDFORM.                    "start_of_selection

*&---------------------------------------------------------------------*
*&      Form  prep_auto_text
*&---------------------------------------------------------------------*
FORM prep_auto_text.

  DATA ls_autotext LIKE LINE OF gt_autotext.

  ls_autotext-keep_fieldname_visible = p_keep.
  ls_autotext-fieldname              = 'VKORG'.
  ls_autotext-fieldname_longtext     = 'TXNAM_KOP'.
  INSERT ls_autotext INTO TABLE gt_autotext.

  ls_autotext-keep_fieldname_visible = p_keep.
  ls_autotext-fieldname              = 'BSTYP'.
  ls_autotext-fieldname_longtext     = 'TXNAM_SDB'.
  INSERT ls_autotext INTO TABLE gt_autotext.

ENDFORM.                    "prep_auto_text
*---------------------------------------------------------------------*
*       Form  get_data
*---------------------------------------------------------------------*
*       Select application data (here SFLIGHT)
*---------------------------------------------------------------------*
FORM get_data.

  FIELD-SYMBOLS <data> LIKE LINE OF gt_data.

*== read all sales organizations
  SELECT * FROM tvko INTO TABLE gt_data.

*== clear fields that shall contain the long text
  LOOP AT gt_data ASSIGNING .
    CLEAR <data>-txnam_kop.
    CLEAR <data>-txnam_sdb.
  ENDLOOP.

ENDFORM.                    "get_data

*----------------------------------------------------------------------*
*       Module  d0100_init  OUTPUT
*----------------------------------------------------------------------*
*       Initialize ALV Grid screen
*----------------------------------------------------------------------*
MODULE d0100_init OUTPUT.

  SET: PF-STATUS '100',
       TITLEBAR  '100'.

  CHECK gr_container IS INITIAL.


*== Create ALV container
  CREATE OBJECT gr_container
    EXPORTING
      container_name = 'ALV_GRID'.

*== Create ALV grid
  CREATE OBJECT gr_alv
    EXPORTING
      i_parent          = gr_container
      i_optimize_output = p_optim
      it_auto_text_det  = gt_autotext.


* Display ALV data
  CALL METHOD gr_alv->set_table_for_first_display
    EXPORTING
      i_save                        = 'A'
      i_structure_name              = 'TVKO'
    CHANGING
      it_outtab                     = gt_data
    EXCEPTIONS
      invalid_parameter_combination = 1
      program_error                 = 2
      too_many_lines                = 3
      OTHERS                        = 4.

ENDMODULE.                 " d0100_init  OUTPUT

*----------------------------------------------------------------------*
*       Module  d0100_exit  INPUT
*----------------------------------------------------------------------*
MODULE d0100_exit INPUT.

  CALL METHOD: gr_alv->free,
               gr_container->free.

  LEAVE TO SCREEN 0.
ENDMODULE.                 " d0100_exit  INPUT

Dynpro

Das Dynpro 100 muss angelegt werden und der GUI-Status "100" ebenfalls. Das PAI-Modul anzupassen, so dass es funktioniert, sollte ein Kinderspiel sein.

Der SAP-Demoreport bietet noch eine Funktionalität mit BAdI BADI_ALV_GRID_XT (Transaktion SE18). Diese werde ich mir evtl. demnächst noch einmal anschauen und berichten. Die Dokumentation verspricht einiges: "Es ist Ihnen mit Hilfe dieser Daten möglich, kundeneigene Felder im Grid zu füllen, den Feldkatalog zu ändern und sich auf Events des Grid
Controls zu registrieren, z.B. TOOLBAR oder USER_COMMAND.

 

 

Flexible Erzeugung des Feldkataloges

$
0
0

In dem Artikel DATA ... RENAMING WITH SUFFIX macht Enno die Aussage "Die ALVs haben Probleme mit komplexen Strukturen". Das stimmt so nicht. Natürlich ist es kein Problem des ALV sondern ein Problem bei der Erzeugung des Feldkatalogs. Ich habe mich deswegen einmal um die Erzeugung des Feldkatalogs zu einer komplexen Struktur gekümmert und ein Programm entwickelt, dass einen Feldkatalog zu einer tiefen Struktur aufbauen kann.

Arbeitsweise

Das Programm analysiert die komplexe Datenstruktur einer internen Tabelle und baut den Feldkatalog so auf, dass der ALV ihn zur Anzeige der Daten verwenden kann.

Die Anzeige ist etwas bunter ausgefallen... 😉

Bild_2014_06_23_180623

Coding

REPORT .

TYPES: BEGIN OF gts_data,
*         mara TYPE mara,
*         marc TYPE marc,
         t005  TYPE t005,
         t005t TYPE t005t,
       END OF gts_data.


DATA gt_data     TYPE STANDARD TABLE OF gts_data WITH NON-UNIQUE DEFAULT KEY.
DATA gt_fieldcat TYPE lvc_t_fcat.
DATA go_grid     TYPE REF TO cl_gui_alv_grid.
DATA gv_style    TYPE i.

*----------------------------------------------------------------------*
*       CLASS lcx_general_exceptions DEFINITION
*----------------------------------------------------------------------*
CLASS lcx_general_exceptions DEFINITION INHERITING FROM cx_static_check.
ENDCLASS.                    "lcx_general_exceptions DEFINITION

*----------------------------------------------------------------------*
*       CLASS lcl_alv_utilities DEFINITION
*----------------------------------------------------------------------*
CLASS lcl_alv_utilities DEFINITION.
  PUBLIC SECTION.
    CLASS-METHODS create_fieldcat_lvc  IMPORTING i_data TYPE  any
                                       RETURNING value(et_fieldcat) TYPE lvc_t_fcat
                                       RAISING lcx_general_exceptions.
  PRIVATE SECTION.
    CLASS-METHODS create_fieldcat_line IMPORTING i_data TYPE  any
                                       RETURNING value(et_fieldcat) TYPE lvc_t_fcat.
ENDCLASS.                    "lcl_alv_utilities DEFINITION


START-OF-SELECTION.
  WRITE '.'.

  SELECT * UP TO 100 ROWS
    INTO TABLE gt_data
*    FROM mara JOIN marc ON mara~matnr = marc~matnr.
    FROM t005 JOIN t005t ON t005~land1 = t005t~land1.

  CREATE OBJECT go_grid
    EXPORTING
      i_parent = cl_gui_container=>screen0.
  gt_fieldcat = lcl_alv_utilities=>create_fieldcat_lvc( gt_data ).
  go_grid->set_table_for_first_display( CHANGING
                                          it_outtab       = gt_data
                                          it_fieldcatalog = gt_fieldcat ).

*----------------------------------------------------------------------*
*       CLASS lcl_alv_utilities IMPLEMENTATION
*----------------------------------------------------------------------*
CLASS lcl_alv_utilities IMPLEMENTATION.

  METHOD create_fieldcat_lvc.

    FIELD-SYMBOLS: <lt_data> TYPE ANY TABLE,
                   <ls_line> TYPE any,
                   <ls_dref> TYPE any.

    DATA lr_dref                 TYPE REF TO data.
    data lo_descr                TYPE REF TO cl_abap_typedescr.
    data lo_structdescr          TYPE REF TO cl_abap_structdescr.
    DATA lt_components           TYPE cl_abap_structdescr=>component_table.
    FIELD-SYMBOLS <ls_component> LIKE LINE OF lt_components.

    lo_descr = cl_abap_typedescr=>describe_by_data( i_data ).

    CASE lo_descr->kind.

      WHEN cl_abap_typedescr=>kind_elem.                   
" Special case.  We see this when we have something like 
"... type standard table of syucomm.
* Tried various things but Grid won't show anything - 
" no matter what I try to provide a faked fieldcat
        RAISE EXCEPTION TYPE lcx_general_exceptions.       " Not foreseen yet

      WHEN cl_abap_typedescr=>kind_struct.                 
        " Structure is fine --> create fieldcat from this
        et_fieldcat = create_fieldcat_line( i_data ).

      WHEN cl_abap_typedescr=>kind_table.                  
        " Table --> restart with linetype of table
        ASSIGN i_data TO <lt_data>.
        CREATE DATA lr_dref LIKE LINE OF <lt_data>.
        ASSIGN lr_dref->* TO <ls_line>.
        et_fieldcat = create_fieldcat_lvc( <ls_line> ).

      WHEN cl_abap_typedescr=>kind_ref.                    
        " Reference --> restart with dereferenced value
        ASSIGN i_data->* TO <ls_dref>.
        et_fieldcat = create_fieldcat_lvc( <ls_dref> ).

*    when cl_abap_typedescr=>KIND_CLASS.
*    when cl_abap_typedescr=>KIND_INTF  .
      WHEN OTHERS.
        BREAK-POINT.  " Ich will das vorher sehen
        RAISE EXCEPTION TYPE lcx_general_exceptions.       " Not foreseen yet

    ENDCASE.

  ENDMETHOD.                                               "create_fieldcat_lvc


  METHOD create_fieldcat_line.

    DATA lr_dref               TYPE REF TO data.
    data lo_salv               TYPE REF TO cl_salv_table.
    data lo_salv_columns_table TYPE REF TO cl_salv_columns_table.
    data lo_salv_aggregations  TYPE REF TO cl_salv_aggregations.
    data lv_help_id            TYPE string.

    FIELD-SYMBOLS <ls_fc>      LIKE LINE OF et_fieldcat.
    FIELD-SYMBOLS <lv_field>   TYPE any.

    FIELD-SYMBOLS <lt_data>    TYPE STANDARD TABLE.

*--------------------------------------------------------------------*
* Create standard table of input
*--------------------------------------------------------------------*
    CREATE DATA lr_dref LIKE STANDARD TABLE OF i_data.
    ASSIGN lr_dref->* TO <lt_data>.
    TRY.
        cl_salv_table=>factory( IMPORTING
                                  r_salv_table   = lo_salv
                                CHANGING
                                  t_table        = <lt_data> ).
        lo_salv_columns_table = lo_salv->get_columns( ).
        lo_salv_aggregations  = lo_salv->get_aggregations( ).

        et_fieldcat = cl_salv_controller_metadata=>get_lvc_fieldcatalog(
                            r_columns      = lo_salv_columns_table
                            r_aggregations = lo_salv_aggregations ).
*--------------------------------------------------------------------*
* Internal fields w/o descriptions --> set fieldname into header line
*--------------------------------------------------------------------*
        LOOP AT et_fieldcat ASSIGNING <ls_fc> WHERE scrtext_s = space
                                                AND scrtext_m = space
                                                AND scrtext_l = space.
          <ls_fc>-scrtext_m = <ls_fc>-fieldname.
        ENDLOOP.

*--------------------------------------------------------------------*
* Add F1 and F4-Help
* Idea from http://wiki.scn.sap.com/wiki/display/Snippets/ALV+fieldcatalog+-+create+for+ANY+table
*--------------------------------------------------------------------*
        LOOP AT et_fieldcat ASSIGNING <ls_fc>.

          ASSIGN COMPONENT <ls_fc>-fieldname OF STRUCTURE i_data TO <lv_field>.
          CHECK sy-subrc = 0.
          <ls_fc>-style     = gv_style.
          ADD 1 TO gv_style.

        TRY.
          CLEAR lv_help_id.
          DESCRIBE FIELD <lv_field> HELP-ID lv_help_id.
          IF lv_help_id CA '-'.
            SPLIT lv_help_id AT '-' INTO <ls_fc>-ref_table <ls_fc>-ref_field.
          ENDIF.
          CATCH cx_root.
        ENDTRY.

        ENDLOOP.

      CATCH cx_salv_msg .
    ENDTRY.

  ENDMETHOD.                                               "create_fieldcat_line
ENDCLASS.                    "lcl_alv_utilities IMPLEMENTATION

What todo?

$
0
0

Bei der Programmierung, beim Debuggen, Testen und bei der Durchsicht von Programmen stolpere ich häufig über Ungereimtheiten, offensichtliche Fehler, Dinge, die verbessert werden können, unzureichendes Fehlerhandling und so weiter.

Da diese Dinge zwar geändert werden sollten, aber aktuell nicht im Fokus stehen, habe ich mir eine einfache aber recht wirkungsvolle Methode ausgedacht, diese Programmstellen zu kennzeichnen.

TODO

Ich lege eine Klasse ZCL_TODO an mit den statischen Methoden FRAGE, WICHTIG und ACHTUNG. Alle Methoden haben die Importing-Parameter WER (Optional) und WAS (preferred parameter).

Die Methoden haben keinen Quelltext. Dieser ist auch nicht nötig, denn die Methoden sollen lediglich als Merker für später dienen. Wenn ich z.B. im aktuellen Programm darüber stolpere, warum der ELSE-Zweig nicht prozessiert wird, dann schreibe ich an diese Stelle:

ZCL_TODO=>FRAGE( 'Was ist mit ELSE?' ).

Wenn ich bemerke, dass ein Funktionsaufruf falsch oder unvollständig ist, dann schreibe ich:

ZCL_TODO=>WICHTIG( wer = 'Enno' was = 'Parameter xyz prüfen! ).

Verwendungsnachweis

Das schöne an dieser Lösung ist, dass man mithilfe des Verwendungsnachweises für die Klasse ZCL_TODO sofort sehen kann, was man sich gerne merken wollte und was noch zu tun ist. Und das alles ohne großartige Hilfsmittel, Listen oder Dokumentationen.

Am besten eignet es sich für größere Programmierprojekte und Objekte, die aktuell bearbeitet werden. Wenn das Objekt gerade gar nicht in Bearbeitung ist, muss zur Quelltextänderung natürlich ein Transportauftrag angelegt werden. Aber auch das ist nicht weiter schlimm; dann hat derjenige, der das Objekt später ändern muss den schwarzen Peter. Hier muss selbstverständlich aufgepasst werden, dass der Entwickler mitbekommt, dass dieses Objekt nicht in seinen Transportauftrag übernommen wird. Diese wird deutlich dadurch, dass eine Meldung "Zu Auftrag 123 wurde eine Aufgabe hinzugefügt" (oder so ähnlich) erscheint.

Code Inspector

Um sicher zu gehen, dass Änderungen auch tatsächlich gemacht werden, kann der Code Inspector bemüht werden. Hier kann man die Prüfung auf Suchmuster entsprechend einstellen, dass der String "ZCL_TODO" einen Fehler auswirft. Wenn man jetzt noch einstellt, dass bei Freigabe des Transportauftrags die Code-Inspector-Prüfungen laufen, kann eigentlich nichts mehr schief gehen.

2015-05-07_18-55-29

Magic Filter

$
0
0

Vor kurzem war ich genervt von einer Anwendung, in der Daten in mehreren ALV-Grids angezeigt wurden. Die unterschiedlichen Grids haben teilweise gleiche Felder. Vielleicht ähnlich eines Cockpits in dem verschiedene Sichten zu Materialien angezeigt werden (Materialstamm, Werks-Sichten, Bestände auf Lagerortebene etc.)

Bei der Analyse bzw. Fehlersuche musste ich in mehreren Grids einen Filter setzen, z.B. auf das Werk. Das bei verschiedenen Grids ist zwar recht schnell gemacht, aber als ich es öfters machen musste, nervte es schon ziemlich.

Magic Filter

Dabei hatte ich die Idee, einen Filter global über alle verwendeten Grids zu setzen. Die Idee hat sich in dem unten zur Verfügung gestellten Programm manifestiert. Die Funktionalität ist in der Klasse gekapselt. Der Rest des Codes dient nur dazu, einen dreigeteilten Splitter mit jeweils einem Grid darstellen zu können.

Jedes Grid registriert sich am Magic-Filter-Controller. In P_FIELD kann ein Feldname gesetzt werden - z.B. WERKS - und in S_VALUES können die Werte eingeschränkt werden. Mit <ENTER> wird der Filter auf alle registrierten Grids angewendet.

Sofern ein Feld nicht im Feldkatalog des Grids ist, wird es vor dem Setzen des Filters gelöscht.

2015-05-08_18-51-36

Code

REPORT zz_magic_filter.

class lcl_magic_filter DEFINITION DEFERRED.
DATA gr_gfil      TYPE REF TO lcl_magic_filter.
DATA gv_value     TYPE c LENGTH 20.

DATA gs_filter    TYPE lvc_s_filt.
DATA gt_filter    TYPE lvc_t_filt.

DATA gr_docker    TYPE REF TO cl_gui_docking_container.

DATA gr_splitter1 TYPE REF TO cl_gui_easy_splitter_container.
DATA gr_splitter2 TYPE REF TO cl_gui_easy_splitter_container.

DATA gr_cont1     TYPE REF TO cl_gui_container.
DATA gr_cont2     TYPE REF TO cl_gui_container.
DATA gr_cont3     TYPE REF TO cl_gui_container.
DATA gr_cont4     TYPE REF TO cl_gui_container.

DATA gr_grid1     TYPE REF TO cl_gui_alv_grid.
DATA gr_grid2     TYPE REF TO cl_gui_alv_grid.
DATA gr_grid3     TYPE REF TO cl_gui_alv_grid.

TYPES: BEGIN OF ty_1,
         matnr TYPE matnr,
         werks TYPE werks_d,
         lgort TYPE lgort_d,
         mtart TYPE mtart,
       END OF ty_1.

TYPES: BEGIN OF ty_2,
         matnr TYPE matnr,
         mtart TYPE mtart,
       END OF ty_2.

TYPES: BEGIN OF ty_3,
         werks TYPE werks_d,
         lgort TYPE lgort_d,
       END OF ty_3.

DATA gt_1 TYPE STANDARD TABLE OF ty_1.
DATA gt_2 TYPE STANDARD TABLE OF ty_2.
DATA gt_3 TYPE STANDARD TABLE OF ty_3.

DATA gs_1 TYPE ty_1.
DATA gs_2 TYPE ty_2.
DATA gs_3 TYPE ty_3.


CLASS lcl_magic_filter DEFINITION.

  PUBLIC SECTION.

    METHODS register
      IMPORTING
        ir_grid TYPE REF TO cl_gui_alv_grid .
    METHODS set_filter
      IMPORTING
        filter TYPE lvc_t_filt .
  PROTECTED SECTION.

    TYPES:
      BEGIN OF ty_object,
        grid   TYPE REF TO cl_gui_alv_grid,
        fcat   TYPE lvc_t_fcat,
        filter TYPE lvc_t_filt,
        status TYPE c LENGTH 1,
      END OF ty_object .
    TYPES:
      ty_objects TYPE STANDARD TABLE OF ty_object .

    DATA mt_objects TYPE ty_objects .
    DATA mt_filter TYPE lvc_t_filt .

    METHODS set_filter_on_objects .

ENDCLASS.



CLASS lcl_magic_filter IMPLEMENTATION.


* ---------------------------------------------------------------------------------------+
* | Instance Public Method ZCL_GFIL_CONTROLLER->REGISTER
* +-------------------------------------------------------------------------------------------------+
* | [--->] IR_GRID                        TYPE REF TO CL_GUI_ALV_GRID
* +--------------------------------------------------------------------------------------
  METHOD register.

    DATA ls_object LIKE LINE OF mt_objects.

    READ TABLE mt_objects TRANSPORTING NO FIELDS WITH KEY grid = ir_grid.
    IF sy-subrc > 0.
*== Objekt hinzufügen
      ls_object-grid   = ir_grid.
      ls_object-status = '1'.
      ir_grid->get_frontend_fieldcatalog( IMPORTING et_fieldcatalog = ls_object-fcat ).
      APPEND ls_object TO mt_objects.
    ENDIF.

  ENDMETHOD.


* ---------------------------------------------------------------------------------------+
* | Instance Public Method ZCL_GFIL_CONTROLLER->SET_FILTER
* +-------------------------------------------------------------------------------------------------+
* | [--->] FILTER                         TYPE        LVC_T_FILT
* +--------------------------------------------------------------------------------------
  METHOD set_filter.

    mt_filter = filter.
    set_filter_on_objects( ).

  ENDMETHOD.


* ---------------------------------------------------------------------------------------+
* | Instance Protected Method ZCL_GFIL_CONTROLLER->SET_FILTER_ON_OBJECTS
* +-------------------------------------------------------------------------------------------------+
* +--------------------------------------------------------------------------------------
  METHOD set_filter_on_objects.

    DATA lt_filter TYPE lvc_t_filt.
    DATA lv_index TYPE i.

*== Set filter
    LOOP AT mt_objects ASSIGNING FIELD-SYMBOL(<object>).
      lt_filter = mt_filter.

      LOOP AT lt_filter ASSIGNING FIELD-SYMBOL(<filter>).
        lv_index = sy-tabix.
        READ TABLE <object>-fcat TRANSPORTING NO FIELDS WITH KEY fieldname = <filter>-fieldname.
        IF sy-subrc > 0.
          DELETE lt_filter INDEX lv_index.
        ENDIF.
        <object>-grid->set_filter_criteria( lt_filter ).
      ENDLOOP.
      <object>-grid->refresh_table_display( is_stable = VALUE #( col = 'X' row = 'X' )
                                            i_soft_refresh = space ).
    ENDLOOP.


  ENDMETHOD.
ENDCLASS.


PARAMETER p_field TYPE fieldname.
SELECT-OPTIONS s_values FOR gv_value.

INITIALIZATION.

  CREATE OBJECT gr_gfil.

  PERFORM create_base.
  PERFORM create_1.
  PERFORM create_2.
  PERFORM create_3.



AT SELECTION-SCREEN.

  CLEAR gs_filter.
  CLEAR gt_filter.
  gs_filter-fieldname = p_field.
  LOOP AT s_values.
    gs_filter-low    = s_values-low.
    gs_filter-sign   = s_values-sign.
    gs_filter-option = s_values-option.
    APPEND gs_filter TO gt_filter.
  ENDLOOP.

  gr_gfil->set_filter( gt_filter ).

START-OF-SELECTION.

FORM create_base.


  CREATE OBJECT gr_docker
    EXPORTING
      side                    = cl_gui_docking_container=>dock_at_bottom
      ratio                   = 80
      no_autodef_progid_dynnr = abap_true.


  CREATE OBJECT gr_splitter1
    EXPORTING
      parent        = gr_docker
      orientation   = 0
      sash_position = 50.

  gr_cont1 = gr_splitter1->top_left_container.
  gr_cont4 = gr_splitter1->bottom_right_container.

  CREATE OBJECT gr_splitter2
    EXPORTING
      parent        = gr_cont4
      orientation   = 1
      sash_position = 50.

  gr_cont2 = gr_splitter2->top_left_container.
  gr_cont3 = gr_splitter2->bottom_right_container.

ENDFORM.

FORM create_1.

  DATA ls_fcat TYPE lvc_s_fcat.
  DATA lt_fcat TYPE lvc_t_fcat.

  gs_1-matnr = 'A'.
  gs_1-werks = '1000'.
  gs_1-lgort = 'A001'.
  gs_1-mtart = 'FERT'.
  APPEND gs_1 TO gt_1.

  gs_1-matnr = 'B'.
  gs_1-werks = '1000'.
  gs_1-lgort = 'A001'.
  gs_1-mtart = 'HALB'.
  APPEND gs_1 TO gt_1.

  gs_1-matnr = 'C'.
  gs_1-werks = '2000'.
  gs_1-lgort = 'B001'.
  gs_1-mtart = 'FERT'.
  APPEND gs_1 TO gt_1.

  gs_1-matnr = 'C'.
  gs_1-werks = '1000'.
  gs_1-lgort = 'A001'.
  gs_1-mtart = 'FERT'.
  APPEND gs_1 TO gt_1.

  gs_1-matnr = 'D'.
  gs_1-werks = '1000'.
  gs_1-lgort = 'B001'.
  gs_1-mtart = 'HALB'.
  APPEND gs_1 TO gt_1.

  ls_fcat-fieldname = 'MATNR'.
  ls_fcat-rollname  = 'MATNR'.
  APPEND ls_fcat TO lt_fcat.
  ls_fcat-fieldname = 'WERKS'.
  ls_fcat-rollname  = 'WERKS_D'.
  APPEND ls_fcat TO lt_fcat.
  ls_fcat-fieldname = 'LGORT'.
  ls_fcat-rollname  = 'LGORT_D'.
  APPEND ls_fcat TO lt_fcat.
  ls_fcat-fieldname = 'MTART'.
  ls_fcat-rollname  = 'MTART'.
  APPEND ls_fcat TO lt_fcat.

  PERFORM create_grid USING gr_cont1 lt_fcat gt_1.

ENDFORM.

FORM create_2.

  DATA ls_fcat TYPE lvc_s_fcat.
  DATA lt_fcat TYPE lvc_t_fcat.

  gs_2-matnr = 'A'.
  gs_2-mtart = 'FERT'.
  APPEND gs_2 TO gt_2.
  gs_2-matnr = 'B'.
  gs_2-mtart = 'HALB'.
  APPEND gs_2 TO gt_2.
  gs_2-matnr = 'C'.
  gs_2-mtart = 'FERT'.
  APPEND gs_2 TO gt_2.
  gs_2-matnr = 'D'.
  gs_2-mtart = 'HALB'.
  APPEND gs_2 TO gt_2.


  ls_fcat-fieldname = 'MATNR'.
  ls_fcat-rollname  = 'MATNR'.
  APPEND ls_fcat TO lt_fcat.
  ls_fcat-fieldname = 'MTART'.
  ls_fcat-rollname  = 'MTART'.
  APPEND ls_fcat TO lt_fcat.

  PERFORM create_grid USING gr_cont2 lt_fcat gt_2.

ENDFORM.

FORM create_3.

  DATA ls_fcat TYPE lvc_s_fcat.
  DATA lt_fcat TYPE lvc_t_fcat.


  gs_3-werks = '1000'.
  gs_3-lgort = 'A001'.
  APPEND gs_3 TO gt_3.
  gs_3-werks = '2000'.
  gs_3-lgort = 'A001'.
  APPEND gs_3 TO gt_3.
  gs_3-werks = '1000'.
  gs_3-lgort = 'B001'.
  APPEND gs_3 TO gt_3.
  gs_3-werks = '2000'.
  gs_3-lgort = 'B001'.
  APPEND gs_3 TO gt_3.



  ls_fcat-fieldname = 'WERKS'.
  ls_fcat-rollname  = 'WERKS_D'.
  APPEND ls_fcat TO lt_fcat.
  ls_fcat-fieldname = 'LGORT'.
  ls_fcat-rollname  = 'LGORT_D'.
  APPEND ls_fcat TO lt_fcat.

  PERFORM create_grid USING gr_cont3 lt_fcat gt_3.

ENDFORM.

FORM create_grid USING ir_container TYPE REF TO cl_gui_container
                       it_fcat      TYPE lvc_t_fcat
                       it_table     TYPE table.

  DATA lr_grid TYPE REF TO cl_gui_alv_grid.

  CREATE OBJECT lr_grid
    EXPORTING
      i_parent = ir_container.

  lr_grid->set_table_for_first_display(
    CHANGING
      it_outtab                     = it_table
      it_fieldcatalog               = it_fcat ).

  gr_gfil->register( lr_grid ).

ENDFORM.


Manchmal, aber nur manchmal…♫

$
0
0

hält SAP Überraschungen bereit, das glaubt man kaum...

Aber von vorne.

In einem Projekt haben wir uns gewundert, warum es in einer dynamisch generierten internen Tabelle einen CONVERSION OVERFLOW gab, obwohl das Feld vom Typ DEC ausreichend groß dimensioniert war. Die Lösung war offensichtlich. Hinterher...

2015-08-10_15-31-23

Erzeugung

Wir sind bei der Erzeugung der internen Tabelle wie folgt vorgegangen:

  • Definition des Feldkataloges
  • Erzeugung der internen Tabelle mit Hilfe von cl_alv_table_create=>create_dynamic_table

Beispielhaft kann die Erzeugung folgendermaßen veranschaulicht werden:

  FIELD-SYMBOLS <fcat>  TYPE lvc_s_fcat.
  DATA gt_table         TYPE lvc_t_fcat.

  APPEND INITIAL LINE TO gt_fcat ASSIGNING .
  <fcat>-fieldname = 'FELD1'.
  <fcat>-scrtext_l = 'Feld 1: CHAR'.
  <fcat>-inttype   = 'C'.
  <fcat>-intlen    = 20.
  <fcat>-datatype  = 'CHAR'.

  APPEND INITIAL LINE TO gt_fcat ASSIGNING .
  <fcat>-fieldname = 'FELD2'.
  <fcat>-scrtext_l = 'Feld 2: DEC'.
  <fcat>-inttype   = 'P'.
  <fcat>-intlen    = 16.
  <fcat>-datatype  = 'DEC'.
  <fcat>-decimals  = 2.

  CALL METHOD cl_alv_table_create=>create_dynamic_table
    EXPORTING
      it_fieldcatalog           = gt_fcat
      i_length_in_byte          = abap_true
    IMPORTING
      ep_table                  = gd_table
    EXCEPTIONS
      generate_subpool_dir_full = 9.

Das macht SAP

Im Debugger sieht man die erzeugte Datenstruktur im Detail.

2015-08-10_15-40-25

Hier ist auch erkennbar, dass die gepackte Zahl im internen Format nicht 16 Bytes groß ist, sondern nur 8. Dadurch passt nur eine vierzehnstellige Zahl (plus Vorzeichen) in das Feld. Im Coding wurde jedoch die maximale Größe von intern 16 Bytes verwendet.

Versteckte Parameter

Ich habe mir die Erzeugung der internen Tabelle durch cl_alv_table_create=>create_dynamic_table genauer angesehen und bin dann recht schnell auf den Parameter I_LENGTH_IN_BYTE gestoßen. Dieser Parameter ist in der Schnittstelle - wie so oft - sehr anschaulich und gut dokumentiert:

boolsche Variable (X=true, space=false)

Der Parameter ist optional und hat als Vorgabewert ABAP_FALSE.

Um es vorweg zu nehmen: Nachdem wir den Parameter auf ABAP_TRUE gesetzt haben, funktionierte alles, wie erwartet.

Beim Debuggen bin ich irgendwann auf folgende Stelle gestossen:

if ls_fieldcat-inttype eq 'P'.
  if r_length_in_byte eq abap_true.
    l_leng = ls_fieldcat-intlen.
  else.
    l_leng =  ( ls_fieldcat-intlen + 1 ) / 2.
  endif.
endif.

Meiner Meinung nach ist das Coding hier verkehrt, da gepackte Zahlen immer aus Halbbytes bestehen. An dieser Stelle darf nicht einfach die Länge halbiert werden. Warum genau diese "Halbierung" statt findet, habe ich auch nicht verstanden. Es hat wahrscheinlich mit Unicode zu tun.

Fazit

Die Erzeugung von internen Tabellen sollte meiner Meinung nach eh nicht mehr mit dem erwähnten Funktion erzeugt werden, da hier im Hintergrund Subroutine-Pools generiert werden. Die Verwendung ist nur eingeschränkt möglich (maximale Anzahl Aufrufe: 36). Inzwischen sind die Möglichkeiten mit CREATE DATA & RTTC deutlich eleganter und zukunftssicherer. Allerdings habe ich die Verwendung von CREATE_DYNMIC_TABLE auch schon mal als elegant bezeichnet...

Die Methode mit CREATE_DYNAMIC_TABLE hat auch einen großen Vorteil: Bei dem Aufbau der internen Tabelle kann ich gleich semantische Informationen mitgeben/ anreichern (Titel, Texte, Suchhilfen etc.), die nicht automatisch übernommen werden. Ich dann diesen Feldkatalog nicht nur zur Erzeugung der internen Tabelle verwenden, sondern auch für die Anzeige.

Bei der Variante über CREATE DATA und RTTC werden fast ausschließlich die technischen Informationen ausgewertet. Wenn ein verwendetes Datenelement korrekte Überschriften hat, ist es okay, aber wenn ich eigene Felder mit einem generischen Datenelement aufbaue (FELD1, FELD2) und diese im gleichen Zug benennen will, dann muss ich dies separat tun.

Log-Points zur Performancemessung

$
0
0

Performance ist ein komplexes Thema. Häufig ist es schwer überhaupt einen Ansatzpunkt zu finden, wo man mit der Performanceoptimierung beginnen soll. Ich habe ein einfaces Testprogramm geschrieben, um das Problem zu verdeutlichen. Es berechnet in einer Schleife ein Ergebnis mit unterschiedlichen Rechenoperationen.

Der folgende Tipp zeigt in erster Linie, wie man die Log-Points zur Protokollierung einsetzen kann. eine zuverlässige Laufzeitanalyse ist so nicht unbedingt möglich.

Die Transaktion SAT bietet die Möglichkeit, eine definierte Laufzeitmessung durchzuführen. Mittels SET RUN TIME ANALYZER ON/ OFF können explizit Blöcke für die Messung definiert werden. In der Variante kannst du definieren, dass nur diese Blöcke analysiert werden sollen. Zudem kann die Aggregation ausgestellt werden.

Testprogramm

Zuerst wird die Tabellen mit Testdaten gefüllt (TESTDATA). Diese Tabelle wird per LOOP durchlaufen und die Berechnung durchgeführt (CALCULATE). Bei Bedarf kann das Ergebnis ausgegeben werden (OUTPUT).

Testprogramm anzeigen
REPORT zzenno38. CLASS lcl_test DEFINITION. PUBLIC SECTION. CLASS-METHODS: main, output. PROTECTED SECTION. TYPES: BEGIN OF ty_data, type TYPE c LENGTH 1, count TYPE i, result TYPE f, p1 TYPE f, p2 TYPE f, END OF ty_data. CLASS-DATA gt_data TYPE STANDARD TABLE OF ty_data. CLASS-METHODS: testdata, calculate IMPORTING type TYPE char1 count TYPE i p1 TYPE f p2 TYPE f RETURNING VALUE(result) TYPE f . ENDCLASS. CLASS lcl_test IMPLEMENTATION. METHOD testdata. FIELD-SYMBOLS <data> TYPE ty_data. APPEND INITIAL LINE TO gt_data ASSIGNING <data>. <data>-type = 'A'. <data>-count = 1000. <data>-p1 = 123. <data>-p2 = 456. APPEND INITIAL LINE TO gt_data ASSIGNING <data>. <data>-type = 'A'. <data>-count = 10000. <data>-p1 = 123. <data>-p2 = 456. APPEND INITIAL LINE TO gt_data ASSIGNING <data>. <data>-type = 'A'. <data>-count = 10000. <data>-p1 = 123456. <data>-p2 = 4567890. APPEND INITIAL LINE TO gt_data ASSIGNING <data>. <data>-type = 'B'. <data>-count = 100000. <data>-p1 = 123. <data>-p2 = 456. APPEND INITIAL LINE TO gt_data ASSIGNING <data>. <data>-type = 'C'. <data>-count = 10000. <data>-p1 = 1234567. <data>-p2 = 4. ENDMETHOD. METHOD main. FIELD-SYMBOLS <data> TYPE ty_data. testdata( ). LOOP AT gt_data ASSIGNING <data>. <data>-result = calculate( type = <data>-type count = <data>-count p1 = <data>-p1 p2 = <data>-p2 ). ENDLOOP. ENDMETHOD. METHOD output. DATA lr_grid TYPE REF TO cl_salv_table. cl_salv_table=>factory( IMPORTING r_salv_table = lr_grid CHANGING t_table = gt_data ). lr_grid->display( ). ENDMETHOD. METHOD calculate. DO count TIMES. CASE type. WHEN 'A'. result = p1 * p2. WHEN 'B'. result = p1 / p2. WHEN 'C'. result = p1 ** p2. ENDCASE. ENDDO. ENDMETHOD. ENDCLASS. PARAMETERS p_output AS CHECKBOX DEFAULT space. START-OF-SELECTION. lcl_test=>main( ). IF p_output = abap_true. lcl_test=>output( ). ENDIF.

Auswertung

In der Transaktion SAT erhält man leider zu wenig aussagekräftige Informationen:

2015-08-19_01-14-26

2015-08-19_01-14-44

Wir brauchen also eine andere Möglichkeit, um die Performance weiter zu analysieren.

Eigene Performanceprotokollierung

Die Protokollierung der Performance ist mit GET RUN TIME möglich. Der Befehl muss jedoch zu Beginn des Tests aufgerufen werden und zum Ende der Messung. Die Zeitendifferenz - also die Laufzeit - muss noch berechnet werden. Das ist umständlich, wenn man mehrere Routinen protokollieren möchte.

Es liegt also nah, diese Funktion auszulagern:

CLASS lcl_performance DEFINITION.
 PUBLIC SECTION.
 CLASS-METHODS:
   start IMPORTING id TYPE any,
   stopp IMPORTING id TYPE any.
 PROTECTED SECTION.
   TYPES: BEGIN OF ty_id_table,
            id TYPE string,
            count TYPE i,
            runtime TYPE i,
          END OF ty_id_table.
  CLASS-DATA gt_id_table TYPE STANDARD TABLE OF ty_id_table.
ENDCLASS.

CLASS lcl_performance IMPLEMENTATION.
  METHOD start.
    FIELD-SYMBOLS <id_table> TYPE ty_id_table.
    READ TABLE gt_id_table ASSIGNING <id_table> WITH KEY id = id.
    IF sy-subrc > 0.
      APPEND INITIAL LINE TO gt_id_table ASSIGNING <id_table>.
      <id_table>-id = id.
    ENDIF.
    ADD 1 TO <id_table>-count.
    GET RUN TIME FIELD <id_table>-runtime.
  ENDMETHOD.
METHOD stopp.
FIELD-SYMBOLS <id_table> TYPE ty_id_table.
DATA lv_runtime TYPE i.
DATA lv_subkey TYPE c LENGTH 200.
READ TABLE gt_id_table ASSIGNING <id_table> WITH KEY id = id.
CHECK sy-subrc = 0.
GET RUN TIME FIELD lv_runtime.
<id_table>-runtime = lv_runtime - <id_table>-runtime.
lv_subkey = |{ id }, Run { <id_table>-count }: { <id_table>-runtime NUMBER = USER }ms|.
LOG-POINT ID z_performance SUBKEY lv_subkey FIELDS <id_table>-runtime.
ENDMETHOD.
ENDCLASS.

Aufruf der Protokollierung

Wir müssen die Methoden unserer Protokollklasse nun nur noch einbinden:

METHOD main.
  FIELD-SYMBOLS <data> TYPE ty_data.
  testdata( ).
  LOOP AT gt_data ASSIGNING <data>.
    lcl_performance=>start( <data>-type ).
    <data>-result = calculate( type = <data>-type
                              count = <data>-count
                                 p1 = <data>-p1
                                 p2 = <data>-p2 ).
    lcl_performance=>stopp( <data>-type ).
  ENDLOOP.
ENDMETHOD.

Log-Points

In dem Coding zur Performanceprotokollierung benutze ich bereits eines Log-Point. Diesen kannst du mit der Transaktion SAAB anlegen. Log-Points haben den Vorteil, dass man diese in jedem System bei Bedarf aktivieren kann. Du benötigst also keine separate Tabelle, um einstellen zu können, was wann protokolliert werden muss, sondern kannst du Standardfunktionalität nutzen.

Mit der Transaktion SAAB musst du den Log-Point auch einschalten. Du kannst wählen, für welchen Zeitraum die Protokollierung aktiv sein soll:

2015-08-19_00-46-37

Normalerweise würde man den Log-Point in dieser Form verwenden:

LOG-POINT ID z_performance SUBKEY id FIELDS <id_table>-runtime.

Diese Protokollierung ist jedoch sehr umständlich auszuwerten: Man sieht erstens nur den letzten Aufruf des Log-Points und zweitens muss man die Hierarchie aufklappen.

2015-08-19_01-50-34

Aber ich wollte die Auswertung ja einfacher machen. Also machen wir uns den Umstand zunutze, dass die Log-Point-ID bis zu 200 Zeichen lang sein darf und basteln uns für jeden Aufruf eine eigene ID. Das Ergebnis ist viel aussagekräftiger:

2015-08-19_00-50-19

Variante

Es ist auch möglich, einem Log-Point eine interne Tabelle zu übergeben! Da jeweils nur der letzte Log-Point gesichert wird, können wir uns diesen Umstand ebenfalls gut zunutze machen:

Wir rufen den Log-Point einfach mit einem festen Wert auf und übergeben bei FIELDS die interne Tabelle:

LOG-POINT ID z_performance SUBKEY 'Default' FIELDS gt_id_table.

Das Ergebnis sieht folgendermaßen aus:

2015-08-19_02-01-47

Erweiterungsmöglichkeiten

Die Protokollierung ist natürlich auch noch sehr begrenzt. Du kannst die Methoden jedoch um Parameter erweitern, so dass bei jedem Aufruf zusätzliche Informationen übergeben werden können. Auch könnte die Protokollklasse sich selbst je Log-Id instanziieren, so dass je Log-Id nur die eigenen Aufrufe an den Log-Point übergeben werden. So würde die Messung von mehreren Abschnitten wahrscheinlich übersichtlicher werden.

Doku

Für die genauen Möglichkeiten der Log-Points solltest du dir die Hilfe durchlesen: LOG-POINT.

Redefinierte Methoden?

$
0
0

Heute habe ich in einer Klassenhierarchie (Superklasse -> vererbte Klassen) eine Klasse gesucht habe, bei der ich eine bestimmte Methode redefiniert habe.

Als anständiger Programmierer war ich natürlich zu faul, mich auch nur durch drei vererbte Klassen durchzuklicken... Also bin ich auf die Suche nach einem passenden Funktionsbaustein gegangen und dabei auf SEO_INHERITANC_READ gestossen. Diesem Funktionsbaustein kannst du eine Klasse übergeben und erhältst alle redefinierten Methoden.

Nicht ganz das, was ich gesucht habe, aber die verwendete Tabelle SEOREDEF lieferte mir die gewünschten Informationen im Handumdrehen.

Hier ein Beispiel in dem du siehst, welche Methoden von CL_GUI_CONTAINER in direkt geerbten Klassen redefiniert wurden:

2015-11-09_18-17-16

Allerdings ist diese Methode nicht 100%ig, denn es wird natürlich nur die direkte Vererbung gespeichert. Von der Klasse CL_GUI_DOCKING_CONTAINER zum Beispiel erben weitere Klassen. Deren Redefinitionen sind nur sichtbar, wenn du die Klasse CL_GUI_DOCKING_CONTAINER direkt eingibst.

Um zu sehen, wo entlang einer Vererbungshierarchie welche Methoden redefiniert wurden, muss also noch ein Programm geschrieben werden.

Programm

Hier ist das kleine Programm. Quick and dirty. Methode GET_SUBCLASSES der Klasse CL_OO_CLASS gibt alle vererbten Klassen zurück.

von diesen Klassen werden die redefinierten Methoden ermittelt.

Um zu sehen, welche Methode in welcher Klasse redefiniert wurde, muss nach Methode gefiltert oder sortiert werden:

2015-11-09_18-49-58

Code

REPORT.

PARAMETERS p_class TYPE seoclsname DEFAULT 'CL_GUI_CONTAINER'.

DATA gt_redef TYPE STANDARD TABLE OF seoredef.

START-OF-SELECTION.

 PERFORM do USING p_class 'X'.
 PERFORM display.

*&---------------------------------------------------------------------*
*& Form display
*&---------------------------------------------------------------------*
FORM display.

 DATA lr_table TYPE REF TO cl_salv_table.
 DATA lr_funcs TYPE REF TO cl_salv_functions_list.

 CALL METHOD cl_salv_table=>factory
 IMPORTING
 r_salv_table = lr_table
 CHANGING
 t_table = gt_redef.

 lr_funcs = lr_table->get_functions( ).
 lr_funcs->set_all( ).

 lr_table->display( ).
ENDFORM. "display


*&---------------------------------------------------------------------*
*& Form do
*&---------------------------------------------------------------------*
FORM do USING i_class TYPE seoclsname
 i_start TYPE boolean.

 DATA lr_class TYPE REF TO cl_oo_class.
 DATA lt_subclasses TYPE seo_relkeys.
 DATA ls_subclass LIKE LINE OF lt_subclasses.

 TRY .
 lr_class ?= cl_oo_class=>get_instance( i_class ).

 APPEND LINES OF lr_class->redefinitions TO gt_redef.
 lt_subclasses = lr_class->get_subclasses( ).

 IF i_start = abap_true.
 LOOP AT lt_subclasses INTO ls_subclass.
 PERFORM do USING ls_subclass-clsname space.
 ENDLOOP.
 ENDIF.

 CATCH cx_class_not_existent.

 ENDTRY.

ENDFORM. "do


                       

Befehlsverkettung – Don’t try this at home!

$
0
0

Den CL_SALV_TABLE kann man leider nicht "elegant" erzeugen. Darunter verstehe ich, dass man die SALV-Referenz per RECEIVING bekommt. Sobald ein Exporting- oder Changing-Parameter in der Methode verwendet wird, kann RETURNING nicht mehr benutzt werden.

Der Aufruf ist zwar kompakt:

CALL METHOD cl_salv_table=>factory
 EXPORTING
 r_container = gr_dock
 IMPORTING
 r_salv_table = gr_salv
 CHANGING
 t_table = gt_data.
gr_salv->display( ).

aber man kann ihn nicht wie folgt verwenden:

cl_salv_table=>factory( ... )->display( ).

Ich wollte nun wissen, ob man die Erzeugung eines SALV-Tables plus Anzeige trotzdem in einen funktionalen Aufruf schreiben kann. Geklappt hat es zwar, jedoch wiederum mit einem Nachteil.

Befehlsverkettung

Methoden von durch RETURNING übergebenen Objektreferenzen können direkt durch Befehlsverkettung ausgeführt werden.

Dazu ein kleines Testprogramm mit einer FACTORY-Methode:

*----------------------------------------------------------------------*
* CLASS lcl_test DEFINITION
*----------------------------------------------------------------------*
CLASS lcl_test DEFINITION.
 PUBLIC SECTION.
 CLASS-METHODS factory RETURNING value(object) TYPE REF TO lcl_test.
 METHODS show.
ENDCLASS. "lcl_salv DEFINITION

*----------------------------------------------------------------------*
* CLASS lcl_salv IMPLEMENTATION
*----------------------------------------------------------------------*
CLASS lcl_test IMPLEMENTATION.
 METHOD factory.
 CREATE OBJECT object.
 ENDMETHOD. "factory

 METHOD show.
 MESSAGE 'Hallo' TYPE 'I'.
 ENDMETHOD. "show

ENDCLASS. "lcl_test IMPLEMENTATION

Normalerweise würde man die Methode SHOW folgendermaßen aufrufen:

DATA lr_test TYPE REF TO lcl_test.
lr_test = lcl_test=>factory( ).
lr_test->show( ).

Durch Verwendung einer Befehlsverkettung kann der Aufruf wie folgt aussehen:

lcl_test=>factory( )->show( ).

In diesem Umfang ist die Befehlsverkettung auch vertretbar. Sie kann jedoch Auswüchse annehmen, die ich nicht mehr warten möchte...

CL_SALV_TABLE

Zurück zum meinem eigentlich Problem: Die Anzeige des SALV-Grid mit einem Aufruf. Hindernis dabei ist, dass die SALV-Referenz eben nicht per RETURNING zurück gegeben wird, sondern als IMPORTING-Parameter.

Ich schrieb also ein kleines Wrapper-Programm. Ich habe die Übergabe der einzelnen Parameter in einzelne Methoden ausgelagert um diese nacheinander "funktional" aufrufen zu können. Dafür muss die eigene Instanz jeweils an den Aufrufer übergeben werden.

Auch dabei musste ich tricksen, denn die anzuzeigende Tabelle kann nicht als CHANGING übergeben werden. Ich musste deswegen mit einer Referenz auf die Tabelle arbeiten.

 DATA gd_table TYPE REF TO data.
 GET REFERENCE OF gt_data INTO gd_table.

Dadurch erreiche ich natürlich nicht das, was ich eigentlich wollte, nämlich einen kompakten Methodenaufruf. Immerhin: Der Aufruf sieht nun wirklich interessant aus:

lcl_salv=>factory( )->set_table( gd_table )->create_grid( )->display( ).

DISPLAY ist hierbei bereits die Methode des SALV-Table, die durch CREATE_GRID erzeugt wird!

Sicherlich nicht elegant, aber es lässt sich gut demonstrieren, wie die funktionalen Aufrufe funktionieren.

Diese Methode ist selbstverständlich nur dann sinnvoll, wenn man sicher ist, dass man das SALV-Objekt nicht mehr benötigt, denn bei diesem Aufruf erzeugt man das Objekt, benutzt es, aber die Referenz darauf ist sofort wieder weg.

Code

*----------------------------------------------------------------------*
* CLASS lcl_salv DEFINITION
*----------------------------------------------------------------------*
CLASS lcl_salv DEFINITION.
 PUBLIC SECTION.
 DATA mt_table TYPE REF TO data.
 CLASS-METHODS factory RETURNING value(object) TYPE REF TO lcl_salv.
 METHODS create_grid RETURNING value(salv) TYPE REF TO cl_salv_table.
 METHODS set_table IMPORTING table TYPE REF TO data
 RETURNING value(object) TYPE REF TO lcl_salv.
ENDCLASS. "lcl_salv DEFINITION

*----------------------------------------------------------------------*
* CLASS lcl_salv IMPLEMENTATION
*----------------------------------------------------------------------*
CLASS lcl_salv IMPLEMENTATION.
 METHOD factory.
 create object object.
 ENDMETHOD. "factory

 METHOD create_grid.

 FIELD-SYMBOLS <table> TYPE ANY TABLE.
 ASSIGN mt_table->* TO <table>.

 CALL METHOD cl_salv_table=>factory
 IMPORTING
 r_salv_table = salv
 CHANGING
 t_table = <table>.

 ENDMETHOD. "factory

 METHOD set_table.
 mt_table = table.
 object = me.
 ENDMETHOD. "set_table
ENDCLASS. "lcl_salv IMPLEMENTATION

Befehlsverkettung mit Strukturzugriff

$
0
0

Jeder kennt inzwischen die Möglichkeit der funktionalen Methodenaufrufe, bei denen man das Ergebnis einer Funktion direkt einer Variablen zuweisen

rnd = CL_ABAP_RANDOM_INT=>CREATE( ).

oder direkt in Vergleichen verwenden kann:

CHECK CL_ABAP_DEMO_SERVICES=>IS_PRODUCTION_SYSTEM( ) = abap_false.

Direkter Zugriff

Was die wenigsten wissen ist, dass man direkt auf einzelne Felder einer zurück gegebenen Struktur zugreifen kann. Durch die Befehlsverkettung können Methoden direkt aneinander gereiht werden:

layout = gr_salv->get_layout( )->get_current_layout( ).

Handelt es sich bei dem Übergabeparameter um eine Struktur, so kann auch hierauf direkt zugegriffen  werden:

default = gr_salv->get_layout( )->get_current_layout( )-default.

 

Smart Filter

$
0
0

Vor einiger Zeit habe ich euch den Magic Filter vorgestellt. Hier habe ich den aktuellen Filter eines Grids auf mehrere Objekte angewendet.

Heute möchte ich euch eine weitere Möglichkeit der Filterung vorstellen: Die Filterung anhand einer Mehrfachselektion. Damit es ins Konzept passt und weil jedes Kind einen Namen braucht, habe ich es Smart Filter genannt.

Smart Filter: Anwendung

Am Anfang war das Grid (oder genauer: Der SALV-Table). Als Beispieldaten dienen die Länderbezeichnungen.

Das Grid besitzt eine selbst definierte Funktion "Filter":

filter_sel1

Um diese Funktion zu verwenden, müssen mindestens eine Zeile und mindestens eine Spalte markiert werden (STRG gedrückt halten für Mehrfachselektion!):

filter_sel2

Mit einem Klick auf "Filter" wird dann die "Schnittmenge" gefiltert:

filter_sel3

Die Anwendungsmöglichkeiten sind sicherlich beschränkt, aber bei größeren Listen, die anhand bestimmter Felder und Feldwerte kontrolliert werden müssen, sicherlich sehr hilfreich.

Anmerkungen zum Code

Da der zusätzliche Button in dieser Variante nicht im Fullscreen-Grid funktioniert, muss die Darstellung in einem Docking-Container erfolgen.

Um dies einfach zu tun, habe ich den PARAMETER dummy verwendet, damit ein Selektionsbild angezeigt wird.

Mit Druck auf die Enter-Taste wird das Grid angezeigt.

Um den Zusatzbutton anzuzeigen, wird Command Chaining verwendet:

mr_salv->get_functions( )->add_function( ... )

Code

REPORT.

PARAMETERS dummy.

*----------------------------------------------------------------------*
* CLASS lcl_grid DEFINITION
*----------------------------------------------------------------------*
CLASS lcl_grid DEFINITION.
 PUBLIC SECTION.
 CLASS-METHODS start.
 PROTECTED SECTION.
 CLASS-DATA mt_data TYPE STANDARD TABLE OF t005t.
 CLASS-DATA mr_salv TYPE REF TO cl_salv_table.
 CLASS-METHODS add_filter_value
 IMPORTING
 structure TYPE any
 fieldname TYPE fieldname
 CHANGING
 filter TYPE lvc_t_filt.
 CLASS-METHODS add_filter_selection.
 CLASS-METHODS set_filter
 IMPORTING
 filter TYPE lvc_t_filt.
 CLASS-METHODS add_function.
 CLASS-METHODS on_function FOR EVENT added_function
 OF cl_salv_events_table
 IMPORTING e_salv_function.


ENDCLASS. "lcl_grid DEFINITION

*----------------------------------------------------------------------*
* CLASS lcl_grid IMPLEMENTATION
*----------------------------------------------------------------------*
CLASS lcl_grid IMPLEMENTATION.
 METHOD start.

*== Daten lesen
 SELECT * FROM t005t INTO TABLE mt_data.

*== Docker erzeugen
 DATA lr_docker TYPE REF TO cl_gui_docking_container.
 CREATE OBJECT lr_docker
 EXPORTING
 side = cl_gui_docking_container=>dock_at_bottom
 ratio = 80.

*== SALV-Table erzeugen
 CALL METHOD cl_salv_table=>factory
 EXPORTING
 r_container = lr_docker
 IMPORTING
 r_salv_table = mr_salv
 CHANGING
 t_table = mt_data.

*== alle Funktionen einblenden, um Filter löschen zu können
 DATA lr_funcs TYPE REF TO cl_salv_functions_list.
 lr_funcs = mr_salv->get_functions( ).
 lr_funcs->set_all( ).
*== Eigene Funktion einblenden
 add_function( ).

*== ON_Usercommand registrieren
 DATA lr_events TYPE REF TO cl_salv_events_table.
 lr_events = mr_salv->get_event( ).
 SET HANDLER on_function FOR lr_events.

*== Anzeige
 mr_salv->display( ).

 ENDMETHOD. "start

 METHOD add_function.

*== Eigene Funktion einfügen
 mr_salv->get_functions( )->add_function(
 name = 'ZFilterSel'
 icon = |{ icon_filter }|
 text = |Filter|
 tooltip = |Filter anhand Selektion|
 position = if_salv_c_function_position=>right_of_salv_functions ).

 ENDMETHOD. "add_function

 METHOD on_function.

 CASE e_salv_function.
 WHEN 'ZFilterSel'.
*== Filter anhand Selektion setzen
 add_filter_selection( ).
 ENDCASE.

 ENDMETHOD. "on_function

 METHOD add_filter_value.

 DATA ls_filter TYPE lvc_s_filt.
 FIELD-SYMBOLS <value> TYPE any.

*== Filterzeile hinzufügen
 ls_filter-fieldname = fieldname.
 ls_filter-sign = 'I'.
 ls_filter-option = 'EQ'.
 ASSIGN COMPONENT fieldname OF STRUCTURE structure TO <value>.
 IF sy-subrc = 0.
 ls_filter-low = <value>.
 SHIFT ls_filter-low LEFT DELETING LEADING space.
 READ TABLE filter WITH KEY fieldname = ls_filter-fieldname
 sign = ls_filter-sign
 option = ls_filter-option
 low = ls_filter-low
 TRANSPORTING NO FIELDS.
 IF sy-subrc > 0.
 APPEND ls_filter TO filter.
 ENDIF.
 ENDIF.

 ENDMETHOD. "add_filter_field

 METHOD add_filter_selection.

*== Lokale Daten
 DATA lt_filter TYPE lvc_t_filt.

 DATA lt_rows TYPE salv_t_row.
 FIELD-SYMBOLS <row> LIKE LINE OF lt_rows..
 DATA lt_cols TYPE salv_t_column.
 FIELD-SYMBOLS <col> LIKE LINE OF lt_cols.
 DATA lr_selection TYPE REF TO cl_salv_selections.

 FIELD-SYMBOLS <table_line> TYPE any.


*== Selektierte Spalten und Zeilen ermitteln
 lr_selection = mr_salv->get_selections( ).
 lt_rows = lr_selection->get_selected_rows( ).
 lt_cols = lr_selection->get_selected_columns( ).

*== Filter anhand der aktuellen Selektion setzen
 LOOP AT lt_rows ASSIGNING <row>.
 IF sy-subrc = 0.
 READ TABLE mt_data ASSIGNING <table_line> INDEX <row>.
 IF sy-subrc = 0.
 LOOP AT lt_cols ASSIGNING <col>.
 add_filter_value( EXPORTING structure = <table_line> fieldname = <col>
 CHANGING filter = lt_filter ).

 ENDLOOP.
 ENDIF.
 ENDIF.
 ENDLOOP.

*== Filter setzen
 set_filter( filter = lt_filter ).

 ENDMETHOD. "add_filter_selection

 METHOD set_filter.

*== Filter auf SAL-Table anwenden
 DATA lv_index TYPE i.
 DATA lr_display TYPE REF TO cl_salv_display_settings.
 DATA lv_title TYPE lvc_title.
 FIELD-SYMBOLS <filter> TYPE lvc_s_filt.

 cl_salv_controller_metadata=>set_lvc_filter( t_filter = filter
 r_filters = mr_salv->get_filters( ) ).
 lr_display = mr_salv->get_display_settings( ).

 LOOP AT filter ASSIGNING <filter>.
 IF lv_title IS INITIAL.
 lv_title = <filter>-fieldname.
 ELSE.
 CONCATENATE '/' <filter>-fieldname INTO lv_title.
 ENDIF.
 ENDLOOP.

 CONCATENATE 'Filter ist aktiv (' lv_title ')' INTO lv_title .

 lr_display->set_list_header( lv_title ).

 mr_salv->refresh( ).
 ENDMETHOD. "set_filter

ENDCLASS. "lcl_grid IMPLEMENTATION

AT SELECTION-SCREEN.

 lcl_grid=>start( ).

 

Serialize me

$
0
0

Das Interface IF_SERIALIZABLE_OBJECT läuft einem hin und wieder in SAP-Standardklassen über den Weg.

Was ist Serialisierung?

Wikipedia sagt über Serialisierung:

Die Serialisierung ist in der Informatik eine Abbildung von strukturierten Daten auf eine sequenzielle Darstellungsform.

Eine der bekanntesten Anwendungen für die Serialisierung ist das JSON-Format, in dem komplexe Daten in einer lesbaren Form dargestellt werden können.

Eine andere Form der Serialisierung lässt sich mit XML bewerkstelligen.

Interface IF_SERIALIZABLE_OBJECT

Damit ein Objekt (Klasse) serialisierbar ist, muss es das Interface IF_SERIALIZABLE_OBJECT implementieren:

CLASS lcl_serialize_me DEFINITION.
 PUBLIC SECTION.
 INTERFACES if_serializable_object.

 DATA mt_t005  TYPE STANDARD TABLE OF t005.
 DATA mt_t005t TYPE STANDARD TABLE OF t005t.

ENDCLASS.

Zur Demonstration habe ich die zwei öffentlichen Attribute MT_T005 und MT_T005T hinzugefügt.

Um die Klasse nutzen zu können, muss sie instantiiert werden:

DATA ref TYPE REF TO lcl_serialize_me.
CREATE OBJECT ref.

In die Tabellen laden wir nun alle EG-Länder inklusive Texte:

 SELECT * FROM t005 INTO TABLE ref->mt_t005 WHERE xegld = abap_true.
 IF sy-subrc = 0.
   SELECT * FROM t005t INTO TABLE ref->mt_t005t
      FOR ALL ENTRIES IN ref->mt_t005
    WHERE land1 = ref->mt_t005-land1
      AND spras = sy-langu.
 ENDIF.

Somit haben wir eine Objektreferenz erzeugt, die ein paar Daten enthält.

Diese Daten sind auch serialisierbar. Andere Daten, wie zum Beispiel Attribute mit Referenzen zu anderen Klassen, sind nicht serialisierbar.

Deswegen darf das Interface IF_SERIALIZABLE_OBJECT nur dann implementiert werden, wenn alle Attribute der Klasse für die Serialisierung geeignet sind.

Serialisierung

Nun soll das Objekt mittels XML serialisiert werden. Das geht schnell und einfach:

DATA ser TYPE string.
CALL TRANSFORMATION id
     SOURCE model = ref
     RESULT XML ser.

Das Ergebnis ist ein lesbarer XML-String (Ausschnitt):

2015-12-02_18-20-37

Tipp: Im Debugger ist es möglich, einen XML-String komplett darstellen zu lassen:

2015-12-02_18-21-08

Speicherplatz sparen

Da die Tabellen gefüllt sind, ist das Objekt recht groß geworden. Mit GZIP schrumpfen wir es auf eine kleinere Größe:

DATA zip TYPE xstring.
cl_abap_gzip=>compress_text(
     EXPORTING text_in  = ser
     IMPORTING gzip_out = zip ).

Dies aber nur nebenbei...

Deserialisierung

Nun möchten wir das serialisierte Objekt natürlich irgendwo speichern, ablegen oder verschicken.

Das ist jedoch nur sinnvoll, wenn wir es auch wieder deserialisieren können...

Die Objektreferenz ist in dem Fall natürlich leer:

CLEAR ref.

Die Deserialisierung funktioniert ebenfalls mittel CALL TRANSFORMATION in der Standardvariante:

CALL TRANSFORMATION id
     SOURCE XML ser
     RESULT model = ref.

Im Debugger kannst du überprüfen, dass die komplette Referenz wiederhergestellt wurde. Das ist fast schon Zauberei... :)

Vielen Dank an Haubi für diese Idee!

Nutzen

Wozu die Serialisierung und Deserialisierung tatsächlich nützlich ist, wird man wahrscheinlich erst wissen, wenn man es braucht. Daher ist es in jedem Fall gut zu wissen, dass es funktioniert.

DEMO

Ein einfaches Demoprogramm ist DEMO_SERIALIZABLE_OBJECT. Hier wird ebenfalls die Serialisierung demonstriert.

2015-12-02_18-41-07

Wer ein komplexes Demoprogramm zur Konvertierung von (Daten-) Typen sehen möchte, sollte sich das Programm STRANSDEMO_FLIGHTS anschauen.

2015-12-02_18-37-23

Viel Spaß damit; mir ist es zu kompliziert!


Der Doppelpunkt…

$
0
0

Eine Kleinigkeit aus der Welt des Doppelpunkts.

Jeder ABAP-Programmierer kennt den Doppelpunkt und weiß, dass er damit Befehlsausführungen - durch Komma voneinander getrennt - verketten kann:

DATA: a TYPE string,
      b TYPE c LENGTH 3.
MOVE: a TO b, c TO d.

Was die wenigsten jedoch wissen ist, dass man auch Methodenaufrufe durch einen Doppelpunkt unübersichtlicher machen kann:

o_ref->umbenennen( : alt = 'A' neu = 'B' ),
                     alt = 'X' neu = 'Y' ).

Das Ganze noch mit "echter" Befehlsverkettung verbunden und das Chaos ist perfekt.

PS: Das habe ich in dem sehr guten Buch von Paul Hardy entdeckt: ABAP To The Future

Debugger-Scripting (1)

$
0
0

Lange habe ich mich vor den umfangreichen Funktionen es Debugger-Scripting gedrückt. Durch das sensationelle Buch von Paul Harding "ABAP To The Future" habe ich mich nun endlich getraut.

Mein erstes Debugger-Skript

Es passiert des Öfteren, dass einem Authority-Checks in den Weg geworfen werden. Einem einzelnen kann man schnell Herr werden, in dem man sich einen Break-Point auf die Anweisung "AUTHORITY-CHECK" setzt, F5 (Einzelschritt) drückt, den SY-SUBRC auf "0" ändert und dann weiter macht.

Wenn es mehrere Checks sind, kann es schnell nerven.

Mein erstes Debugger-Skript habe ich genau hierfür geschrieben. Es macht genau das, was ich eben beschrieben habe.

METHOD script.

  debugger_controller->debug_step( 
       p_command = cl_tpda_script_debugger_ctrl=>debug_step_over ).

  cl_tpda_script_data_descr=>change_value(
         p_new_value = '4'
         p_varname = 'SY-SUBRC' ).

ENDMETHOD. "script

Damit das Skript funktioniert, musst du an geeigneter Stelle den Debugger anschalten (/h, geht auch vor dem Aufruf einer Transaktion!!) und zum Tab "Script" wechseln.

Dort setzt du einen Break-Point bei der Anweisung AUTHORITY-CHECK:

2015-12-03_17-55-27

Dann musst du nur noch das oben vorgestellte Coding in der Methode SCRIPT einfügen.

"Skript starten" und auf einmal werden alle Berechtigungsprüfungen wahr...

Leider...

funktioniert der Trace bei dieser Methode nicht. Normalerweise kann man das aktuelle Ereignis tracen:

trace->add_src_info( ).

Entweder ist ein AUTHORITY-CHECK kein Ereignis, oder es funktioniert aus anderen Gründen nicht.

Wahrscheinlich letzteres, denn auch ein eigener Eintrag in den Trace bleibt erfolglos:

 DATA trace_entry TYPE tpda_trace_custom.
 trace_entry-value = 'hier steht was...'.
 trace->add_custom_info( p_trace_entry = trace_entry ).

Wer hier noch Tipps hat: Immer her damit in die Kommentare!

Wizard

Wer das Debugger-Skripting weiter erforschen möchte, kann das sehr komfortabel über den Wizard machen:

2015-12-03_18-01-43

Alle möglichen Befehle werden hier als Muster eingefügt. Alle möglichen Konstanten, die in diesem Zusammenhang möglich sind, werden als Kommentar eingebunden:

*************************************************
* debugger commands (p_command):
* Step into(F5) -> CL_TPDA_SCRIPT_DEBUGGER_CTRL=>DEBUG_STEP_INTO
* Execute(F6) -> CL_TPDA_SCRIPT_DEBUGGER_CTRL=>DEBUG_STEP_OVER
* Return(F7) -> CL_TPDA_SCRIPT_DEBUGGER_CTRL=>DEBUG_STEP_OUT
* Continue(F8) -> CL_TPDA_SCRIPT_DEBUGGER_CTRL=>DEBUG_CONTINUE
*************************************************
****************************************************************
*Interface (CLASS = CL_TPDA_SCRIPT_DEBUGGER_CTRL / METHOD = DEBUG_STEP )
*Importing
* REFERENCE( P_COMMAND ) TYPE I
****************************************************************

*TRY.
DEBUGGER_CONTROLLER->DEBUG_STEP( P_COMMAND = P_COMMAND ).
* CATCH cx_tpda_scr_rtctrl_status .
* CATCH cx_tpda_scr_rtctrl .
*ENDTRY.

Speichern & Laden

Die Skripts können - inklusive erstellter Breakpoints!! - gespeichert werden. Entweder direkt im System im ABAP-Repository oder lokal auf dem Rechner.

Da es sich bei den Skripten um "normale" Programme handelt (Programmtyp "Subroutinenpool"), ist es sinnvoll, sich an Namenskonventionen zu halten. Alle SAP-eigenen vorgefertigten Skripte beginnen mit "RSTPDA_SCRIPT".

Gruppensummenstufenberechnung

$
0
0

Heute mal wieder ein Work-around ganz besonderer Güte: Das Beeinflussen von Gruppensummenstufen.  Das ist leider nicht ganz so einfach, wie es sich anhört, da bei einem Refresh des Grids die aufgebauten Gruppenstufen wieder zerstört werden. Also muss ein kleiner Trick herhalten...

Vielen Dank an Stefan, der sich die Mühe gemacht hat, ein Minimaldemo zu erstellen.

Gruppenstufen

Nach dem Start des Demoprogramms erscheint ein "normaler" ALV mit Daten aus der Flugdatenbank:

2015-12-15_22-48-55

Bei normaler Summierung und Bildung von Gruppenstufen, gibt es keine Bezeichnung der gebildeten Gruppen:

2015-12-15_22-50-50

Die Bildung der Gruppenbezeichnung kann sehr komplex werden. Das Beispiel demonstriert die Bezeichnung der Gruppenstufen im Feld "PLANETYPE":

2015-12-15_22-41-44

 

Code

REPORT zdemo_alv_summenzeilen.

*----------------------------------------------------------------------*
* CLASS lcl_helper DEFINITION
*----------------------------------------------------------------------*
CLASS lcl_helper DEFINITION FINAL.
 PUBLIC SECTION.

 CLASS-METHODS: read_data,
 display,
 handle_after_user_command FOR EVENT after_user_command OF cl_gui_alv_grid,
 summenzeilen_anpassen.

 CLASS-DATA: mo_grid TYPE REF TO cl_gui_alv_grid,
 mt_data TYPE STANDARD TABLE OF saplane WITH NON-UNIQUE DEFAULT KEY.
ENDCLASS. "lcl_helper DEFINITION


START-OF-SELECTION.
 lcl_helper=>read_data( ).

END-OF-SELECTION.
 lcl_helper=>display( ).


*----------------------------------------------------------------------*
* CLASS lcl_helper IMPLEMENTATION
*----------------------------------------------------------------------*
CLASS lcl_helper IMPLEMENTATION.

 METHOD read_data.

 SELECT *
 INTO TABLE mt_data
 FROM saplane.

 ENDMETHOD. "read_data

 METHOD display.

 DATA: ls_variant TYPE disvariant.
 WRITE:/ 'Wenn man das hier liest, ist ein interner Fehler aufgetreten'. "#EC NOTEXT

*--------------------------------------------------------------------*
* ALV erzeugen
*--------------------------------------------------------------------*
 CREATE OBJECT mo_grid
 EXPORTING
 i_parent = cl_gui_container=>screen0
 EXCEPTIONS
 OTHERS = 1.

*--------------------------------------------------------------------*
* Event AFTER_USER_COMMAND nutzbar machen
* Da sehr viele Usercommands ( auch SAP-Usercommands ) implizit einen
* full-refresh des Grid durchführen, müssen wir uns stets dahinter klemmen
* um unsere eigene Zwischensummenzeilengenerierung zu erhalten
*--------------------------------------------------------------------*
 SET HANDLER handle_after_user_command FOR mo_grid.

*--------------------------------------------------------------------*
* Defaultlayouts ermöglichen, um Zwischensummen ohne Userinteraktion zu demonstrieren
*--------------------------------------------------------------------*
 ls_variant-handle = '0001'.
 ls_variant-report = sy-repid.

*--------------------------------------------------------------------*
* Anzeigen des grid
*--------------------------------------------------------------------*
 mo_grid->set_table_for_first_display( EXPORTING
 i_structure_name = 'SAPLANE'
 is_variant = ls_variant
 i_save = 'A'
 i_default = 'X'
 CHANGING
 it_outtab = mt_data
 EXCEPTIONS
 OTHERS = 1 ).

*--------------------------------------------------------------------*
* Summen- oder Zwischensummenzeilen manipulieren
*--------------------------------------------------------------------*
 summenzeilen_anpassen( ).

 ENDMETHOD. "display

 METHOD handle_after_user_command.
*--------------------------------------------------------------------*
* SAP hat evtl. noch keinen Refresh gemacht.
* Daher würden Änderungen, die in der Methode summenzeilen_anpassen
* gemacht und dann mit soft-refresh an den Grid gereicht würden im
* Nachgang durch den ausstehenden full-refresh zunichte gemacht, da
* der Grid beim full refresh auch die Summen- und Zwischensummenzeilen
* neu generiert
* Daher wird der full-refresh jetzt explizit vor unserer Anpassung
* ausgeführt und der nachfolgende soft_refresh lässt unsere
* Summenzeilen stehen.
*--------------------------------------------------------------------*
 mo_grid->refresh_table_display( i_soft_refresh = ' ' ).


*--------------------------------------------------------------------*
* Summen- oder Zwischensummenzeilen manipulieren
*--------------------------------------------------------------------*
 summenzeilen_anpassen( ).

 ENDMETHOD. "handle_AFTER_USER_COMMAND

 METHOD summenzeilen_anpassen.

 DATA: lr_data_summe TYPE REF TO data,
 lr_data_zwischensumme TYPE REF TO data,
 lt_grouplevels TYPE lvc_t_grpl, "#EC NEEDED Normalerweise braucht man das um gezielt die Zwischensummen zu manipulieren
 lv_tabix TYPE numc2.

 FIELD-SYMBOLS: <lt_data> LIKE mt_data,
 <ls_data> LIKE LINE OF <lt_data>.
*--------------------------------------------------------------------*
* Zwischensummenzeilen holen -
*--------------------------------------------------------------------*
 mo_grid->get_subtotals( IMPORTING
 ep_collect00 = lr_data_summe " Summenzeile
 ep_collect01 = lr_data_zwischensumme " Zwischensummenzeile - Stufe 1
* EP_COLLECT02 - EP_COLLECT09 Zwischensummenzeilen - Stufe 2-9
 et_grouplevels = lt_grouplevels ). " Informationen welche Zwischensummenzeile(n) zu welchen Gridzeilen gehören

*--------------------------------------------------------------------*
* Hier kann das jetzt hinreichend komplex werden
* Zur Demo werde ich in alle Summen und Zwischensummen im Feld
* "PLANETYPE" etwas einfüllen
*--------------------------------------------------------------------*
 IF lr_data_summe IS BOUND.
 ASSIGN lr_data_summe->* TO <lt_data>.
 LOOP AT <lt_data> ASSIGNING <ls_data>.
 lv_tabix = sy-tabix.
 CONCATENATE 'Stufe1-' lv_tabix INTO <ls_data>-planetype. "#EC NOTEXT
 ENDLOOP.
 ENDIF.

 IF lr_data_zwischensumme IS BOUND.
 ASSIGN lr_data_zwischensumme->* TO <lt_data>.
 LOOP AT <lt_data> ASSIGNING <ls_data>.
 lv_tabix = sy-tabix.
 CONCATENATE 'Stufe2-' lv_tabix INTO <ls_data>-planetype. "#EC NOTEXT
 ENDLOOP.
 ENDIF.


*--------------------------------------------------------------------*
* ALV-Anzeige neu aufbauen lassen, ohne Zwischensummen vom ALV generieren zu lassen
*--------------------------------------------------------------------*
 mo_grid->refresh_table_display( i_soft_refresh = 'X' ).

 ENDMETHOD. "summenzeilen_anpassen
ENDCLASS. "lcl_helper IMPLEMENTATION

Werte aus Excel per DOI (unsichtbar)

$
0
0

Desktop-Office-Integration - kurz DOI - ermöglicht das Bearbeiten von Office-Dokumenten innerhalb einer SAP-Anwendung. Auf Wunsch kann dies auch inplace passieren. Mit den von SAP zur Verfügung gestellten Klassen lassen sich die gängigsten Arbeiten erledigen. Allerdings erfordern diese auch immer eine Anzeige des Office-Dokumentes.

Ich möchte euch einen Trick vorstellen, bei dem zwar DOI verwendet wird, ihr aber nichts davon mitbekommt...

Bereichsleiter

Ich stelle euch unten ein Programm vor, das eine Excel-Datei einliest und die Daten abgreift. Der Zugriff erfolgt dabei über Bereiche. In meinem Beispiel-Excel habe ich ein paar Bereich benannt:

2016-02-05_16-13-08

Gibt es in dem Excel keine Bereiche, so muss ein Bereich definiert werden.

Die Daten werden in einer Tabelle geliefert mit den Feldern:

  • ROW
  • COLUMN
  • VALUE

Mit einem Zugriff können mehrere Bereiche angefordert werden. In der Bereichstabelle wird zwar übermittelt, wie viele Zeilen und Spalten je Bereich enthalten waren. Leider gibt es in der Wertetabelle keinen Bezug mehr zu den Bereichen.

Hier die Rückgabe der Bereiche:

2016-02-05_16-19-26

Und hier die Werte dazu:

2016-02-05_16-19-56

Wenn du sicher gehen möchtest, welcher Bereich mit welchen Werten gefüllt ist, dann solltest du je Bereich eine Abfrage machen.

Achtung! DOI kann nur maximal 9999 Spalten und Zeilen verwalten! Das liegt an der internen Verwendung von CHAR4-Typen für die Speicherung der Zeilen und Spalten.

Wenn du also mehr benötigst, musst du mit mehreren Bereichen arbeiten.

Ablauf

Die Desktop-Office-Integration wird über Interfaces abgebildet. Alles beginnt jedoch mit einem konkreten Erbauer:

c_oi_container_control_creator=>get_container_control

Dieser erzeugt ein Container-Control für das Dokument. Das Control muss an einen Container gebunden werden. Das geschieht bei der Initialisierung:

lr_control->init_control( ... ).

Danach kann man sich das Dokument über eine allgemeine Dokumentenschnittstelle holen. Alle Funktionen, die hier angeboten sind, gelten für Excel- als auch für Word-Dokumente:

lr_control->get_document_proxy( ... ).

Um direkt auf Excel-Funktionen zugreifen zu können, müssen wir uns das konkrete Dokument-Objekt holen:

lr_document->get_spreadsheet_interface( IMPORTING sheet_interface = lr_spreadsheet ).

Mit dieser Klasse haben wir nun endlich Excel-spezifische Funktionen, wie zum Beispiel der Zugriff auf die Tabellenzellen:

lr_spreadsheet->get_ranges_data( ... ).

Anzeige unterdrücken

Es gibt bei diesem Verfahren meines Wissens keine Möglichkeit, die Nutzung unsichtbar für den Benutzer ablaufen zu lassen. Mit einem Trick gelingt es dennoch...

Das Dynpro, das in Listen Verwendet wird, ist normalerweise das Dynpro CL_GUI_CONTAINER=>SCREEN0. Wenn du also etwas in diesen Container einhängst, dann wird er Bildschirmfüllend angezeigt: Beitrag SCREEN0.

Für verschiedene Anwendungsfälle gibt es jedoch weitere SCREENS. Ich glaube, mit jedem Popup-Level wird der SCREEN hochgezählt. Das können wir uns zu nutze machen, in dem wir das DOI-Control einfach an einen Screen hängen, der nicht angezeigt wird: CL_GUI_CONTAINER=>SCREEN9

CALL METHOD lr_control->init_control
  EXPORTING
    inplace_enabled       = 'X'
    no_flush              = 'X'
    r3_application_name   = 'Test DOI'
    parent                = cl_gui_container=>screen9
  IMPORTING
    error                 = error
  EXCEPTIONS
    OTHERS                = 1.

Wir können also trotzdem eine WRITE-Ausgabe machen. Die Ausgabe erfolgt also quasi parallel.

2016-02-05_16-26-04

Den kompletten Quelltext findest du hier.

Tentactics – Puzzle Game

$
0
0

Seit einiger Zeit fesselt mich das simple Puzzlespiel Numberama2, das für iOS im Appstore verfügbar ist. Neben dem Spielen habe ich mir auch Gedanken darüber gemacht, wie man das wohl am einfachsten programmieren könnte. Ich hatte dann eine Idee, die ich leider doch verwerfen musste. Am Ende ist das Spiel deutlich komplexer und aufwändiger geworden, als ich dachte. So ist das halt häufig...

Gameplay

Gespielt wird auf einem 9 Spielfelder breiten und nach unten offenem Spielfeld. Die Spielfelder werden gleichmäßig mit Zahlen von 1 bis 9 aufgefüllt. Nun gilt es, Paare zu finden. Ein gültiges Paar sind zwei Zahlen, die

  • direkt neben-, oder untereinander stehen
  • Ohne andere Zahlen neben- oder untereinander stehen
  • in einer Reihe rechts aufhören und in der nächsten Reihe links wieder beginnen

Paare müssen folgende Eigenschaften haben:

  • Die Summe muss 10 ergeben (1 + 9, 2 + 8 usw)
  • Die Zahlen müssen gleich sein (1 + 1, 2 + 2 usw)

Die Zahlen gültiger Paare können angeklickt und dadurch vom Spielfeld entfernt werden.

Ziel des Spieles ist es, alle Zahlen zu löschen.

Ausgangsbasis sind Zufallszahlen (random mode) oder die Startreihe (classic mode)

123456789
111213141
516171819

2016-03-28_17-04-33

Sind alle Paare gefunden, dann muss der "Next" Button gedrückt werden. Hierdurch werden allen verbliebenen Zahlen ohne Lücken an die vorhandenen Zahlen angehängt. Dadurch ergeben sich wieder neue Kombinationsmöglichkeiten.

Man startet mit einer Punktzahl von 1000. Jedes eliminierte Zahlenpaar verringert die Punktzahl um 5. Gelöschte Reihen erhöhen die Punktzahl um 10.

Die Punktzahl ist jedoch meiner Meinung nach nebensächlich. Es ist schon schwer genug, die Zahlen zu eliminieren.

Der Name

Eigentlich wollte ich - da es sich um eine Programmieraufgabe mit ABAP handelt - das Spiel NUMBERABAP nennen. Zwischendurch fiel mir jedoch der Name TENTACTICS ein, den ich deutlich spannender finde. TEN in Anlehnung an das Spielprinzip, dass die Summe der Paare 10 ergeben muss und TACTICS, da man durchaus taktisch agieren muss, um alle Zahlen zu eliminieren. Und Alliterationen sind immer gut.

Programmbeschreibung

Die erste Idee, um das Spielprinzip abbilden zu können, war die Verwendung einer internen Tabelle mit den Feldern

  • number
  • column
  • row

und zwei Indizes:

  • column - row
  • row - column

So wäre es einfach, Paare zu finden, denn diese müssen entweder im einen oder im anderen Index direkt "nebeneinander" liegen.

Leider brauchte ich natürlich zusätzlich eine Tabelle für das Spielfeld. Die verschiedenen Zugvariationen machten es schwierig, beide Tabellen auf dem gleichen Stand zu halten. Deswegen habe ich in der zweiten Version nur mit dem Spielfeld als "Zahlenspeicher" gearbeitet. Für die Lösungssuche bin ich dann allerdings wieder auf die erste Programmidee zurück gekommen.

Die Lösungssuche funktioniert nicht zuverlässig, denn die Idee mit den "nebeneinander" liegenden Zahlen war zwar gut, findet aber nicht alle möglichen Paare. Um alle Paare zuverlässig identifizieren zu können, müsste man oben links im Spielfeld beginnen und jede Zahl der Reihe nach nach möglichen Kombinationen (nach oben, unten, rechts und links) absuchen.

Das Spielfeld ist ein simples ALV-Grid mit einer kleinen Besonderheit: Die Klasse CL_GUI_ALV_GRID habe ich abgeleitet und um die Methode SET_NO_RESIZE erweitert. Diese ruft die geschützte Methode SET_RESIZE_COLS( 0 ) auf, mit der die Größenänderung der Spalten verhindert wird.

Ich habe bei der Programmierung mit "Buttons" und "Links" experimentiert und bin zu dem Schluss gekommen, dass die Buttons am besten aussehen. Die Links sind alle unterstrichen und das machte die Zahlen unübersichtlich.

Spieltipps2016-03-28_17-05-03

Eliminiere zuerst die eindeutigen Paare, also Zahlen, die direkt nebeneinander stehen und bei denen es nur eine Lösungsmöglichkeit gibt.

Dreier-Kombinationen lasse außen vor und versuche bessere Paare außen herum zu finden, so dass es eine passende vierte Zahl in Frage kommt.

Auch bei eindeutigen Paaren darauf achten, ob es eventuell Kombinationen gibt, die besser vorher gelöst werden sollten. In folgendem Beispiel sind ZWEI Paare versteckt. Offensichtlich sind die zwei Vieren ganz links. Weniger offensichtlich, jedoch besser (da man zwei Paare löst), ist es, die Vier oben rechts mit der Vier aus der nächsten Reihe zu lösen und gleiches in der folgenden Zeile.

1 2 3 4 5 6 7 8 4
4 5   5 6 5 6 4
4 7 1 8   1     7
  8 4 7 1 3 9

Der Button "Check" prüft, wie viele Paare noch vorhanden sind und gibt Hinweise darauf, wo sich noch Paare verstecken.

Eventuell ist es besser, nicht alle möglichen Paare zu löschen, sondern schon vorher den NEXT-Button zu betätigen.

Known Bugs

Es wird nicht erkannt, ob das Spiel zu Ende ist.

Ich dachte, dass es nur möglich wäre, höchstens eine Zeile zur Zeit zu eliminieren. Das stimmt nicht, es können auch zwei Zeilen gelöscht werden. In folgender Konstellation bilden die beiden Vieren ein Paar und können gelöscht werden. Dadurch werden auch die beiden Zeilen gelöscht:

1 2 3 4 5 6 7 8 9
              4  
3 4 5 6 7 8     2
              4
1 7             5

Wie bereits oben beschrieben, ist die Lösungssuche nicht 100% zuverlässig. Einige Paare werden nicht korrekt erkannt.

Unterschiede zum Original

Du wirst nicht durch einen blinkenden NEXT-Button darüber informiert, dass es keine Züge mehr gibt. Dies wäre noch leicht einzubauen.

Du bekommst Hinweise, wo sich noch Paare verstecken. Das ist leider in der Originalversion nicht so und hat mich schon manches Mal zur Verzweiflung getrieben.

Der größte Unterschied ist jedoch, dass dieses Spiel in ABAP geschrieben wurde. 😉

How To Start

Lege ein neues Programm in der Transaktion SE38 an und kopiere den Code von TENTACTICS Editor und aktiviere das Programm. Führe das Programm aus und wähle den Spielmodus "Classic" oder "Random". Drücke <ENTER> um das Spiel zu starten (NICHT Ausführen!).

2016-03-28_17-06-06

Quelltext

Hier gibt es den Quelltext zum Download: tentactics.abap

Viewing all 53 articles
Browse latest View live


<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>