How to deal with SAPGUI controls in a generic way
A few days ago I wrote a blog post about one of my favorite learning projects that taught me a lot about GUI programming: guidrasil
Today I would like to explain some of the techniques that I used in this project.
What Are Controls?
Controls are ActiveX-components (also called OCX-components), which will be used in the SAPGUI. These Windows-components are controlled by the appropriate classes in SAP. The creation of a control mostly follows the following schema:
- CREATE OBJECT <object reference>
- <object reference>-SET_….
- some Controls need an extra DISPLAY call.
Typical GUI-Controls are:
Controls need a Container, where they are placed in. All Container controls inherit from CL_GUI_CONTAINER:
Splitter controls are a special kind of control because they again offer child containers:
Programming of Controls
A typical programming looks like this:
- Create a Container
- Create a Control inside this Container
- Set attributes of the Control
In this Demoprogram I show how to create a Textedit-Control inside a docking-Container.
Each control has different attributes and each control needs a different setup. A textedit control needs to be told that it is read-only and a picture control needs to know the picture to display. That’s the problem: If you know a control very well than you know all necessary attributes. If not, you will have to read documentation, search examples or browse the controls methods.
Sometimes the setting of flags differs. Sometimes you need a boolean flag (X or space) and sometimes the methods needs to have an integer (1 or 0).
The idea of a GUI-Designers
Because all controls are inherited from CL_GUI_CONTROL, it is possible to pass any control to a method or get it back. Or a control can be created and stored in a reference table.
The following report does exactly this: You can set the attributes on the selection screen and create a control by pressing <enter>. All created control will be stored in an internal table.
REPORT zguidrsail_demo_generic_ctrl. SELECTION-SCREEN BEGIN OF BLOCK ctrl WITH FRAME TITLE TEXT-ctl. PARAMETERS p_text RADIOBUTTON GROUP ctrl DEFAULT 'X'. PARAMETERS p_icon RADIOBUTTON GROUP ctrl. SELECTION-SCREEN END OF BLOCK ctrl. SELECTION-SCREEN BEGIN OF BLOCK side WITH FRAME TITLE TEXT-sid. PARAMETERS p_left RADIOBUTTON GROUP side DEFAULT 'X'. PARAMETERS p_rigt RADIOBUTTON GROUP side. PARAMETERS p_botm RADIOBUTTON GROUP side. SELECTION-SCREEN END OF BLOCK side. CLASS ctrl_demo DEFINITION. PUBLIC SECTION. METHODS add_text IMPORTING side TYPE i. METHODS add_icon IMPORTING side TYPE i. PROTECTED SECTION. TYPES: BEGIN OF ts_object, container TYPE REF TO cl_gui_container, control TYPE REF TO cl_gui_control, END OF ts_object. DATA objects TYPE STANDARD TABLE OF ts_object. METHODS append_control IMPORTING container TYPE REF TO cl_gui_container control TYPE REF TO cl_gui_control. ENDCLASS. CLASS ctrl_demo IMPLEMENTATION. METHOD add_text. DATA(parent) = NEW cl_gui_docking_container( side = side ratio = 20 ). DATA(textedit) = NEW cl_gui_textedit( parent = parent ). textedit->set_text_as_stream( VALUE texttab( ( tdline = `This is a demonstration` ) ) ). append_control( container = parent control = textedit ). ENDMETHOD. METHOD add_icon. DATA(parent) = NEW cl_gui_docking_container( side = side ratio = 20 ). DATA(icon) = NEW cl_gui_picture( parent = parent ). icon->load_picture_from_sap_icons( icon_message_question ). icon->set_display_mode( cl_gui_picture=>display_mode_fit_center ). append_control( container = parent control = icon ). ENDMETHOD. METHOD append_control. APPEND VALUE #( container = container control = control ) TO objects. ENDMETHOD. ENDCLASS. INITIALIZATION. DATA(demo) = NEW ctrl_demo( ). AT SELECTION-SCREEN. CASE 'X'. WHEN p_left. DATA(side) = cl_gui_docking_container=>dock_at_left. WHEN p_rigt. side = cl_gui_docking_container=>dock_at_right. WHEN p_botm. side = cl_gui_docking_container=>dock_at_bottom. ENDCASE. CASE 'X'. WHEN p_text. demo->add_text( side = side ). WHEN p_icon. demo->add_icon( side = side ). ENDCASE.
Once I have all created containers and controls in a table, I can read the type and attributes. For example I could loop all created controls and ask if the container is of type CL_GUI_DOCKING_CONTAINER. If yes, I cast to a local object reference and read the attributes side and ratio:
IF itab-container IS INSTANCE OF cl_gui_docking_container. DATA dock TYPE REF TO cl_gui_docking_container. dock ?= itab-container. DATA(side) = dock->get_docking_side( ). dock->get_ratio( ratio = DATA(ratio) ). ENDIF.
In this way I could gather all important attributes of any control.
With the help of RTTI (Run Time Type Information) and class CL_ABAP_TYPEDESCR I am able to get the classname of the object:
DATA(clsnam) = cl_abap_typedescr=>describe_by_object_ref( itab-container )->get_relative_name( ).
Once I have the name I can create the object dynamically:
DATA: container TYPE REF TO cl_gui_container, exc_ref TYPE REF TO cx_root. DATA: ptab TYPE abap_parmbind_tab. ptab = VALUE #( ( name = 'SIDE' kind = cl_abap_objectdescr=>exporting value = REF #( side ) ) ( name = 'RATIO' kind = cl_abap_objectdescr=>exporting value = REF #( ratio ) ) ). TRY. CREATE OBJECT container TYPE (clsnam) PARAMETER-TABLE ptab. CATCH cx_sy_create_object_error INTO exc_ref. MESSAGE exc_ref->get_text( ) TYPE 'I'. ENDTRY.
A dynamic creation is not necessary because it is easier to roll this part out to a creator class.
The GUI-Designer guidrasil works somehow like described above. One important thing is that guidrasil offers a much more convenient way for creating containers and controls: Once you chose the side of the container, the GUI-Designer will split the container and place a toolbar at top and show the empty container at the bottom. In the toolbar you are able to select on of the provided containers and controls.
The Gui-Designer know which controls have been created and where. The creator class does the creation of the control and saves the attributes.
Additionally the creator class offers a dialog or visual settings of the attributes:
I’d rather write code that writes code than write code
One further quality of guidrasil is, that each creator class exactly knows how the control must be created. that means that it can offer the source code for creating this control.
The GUI-Designer exactly knows about the controls and simply can ask each control creator for the creation code. The GUI-Designer than “simply” will have to put the parts together to have a generated report for re-creating all the controls.