Explore a vibrant mix of technical expertise, industry insights, and tech buzz in member blogs covering SAP products, technology, and events. Get in the mix!
Learn how to use dynamic programming to create context nodes and UI elements in run time, how to use context menus, assistance classes and more, in a fun way; creating the clasic game: MineSweeper.
Let’s start:
Goto SE80 and create a new WD component:
Go to the context in MAIN view and create a new node called SETTINGS, with Cardinality 1:1 and Init. Lead Selection = X.
This node will store the settings of the game: Number of columns, number of rows and number of mines.
Then create within this node the following attributes:
Attribute name
Type
Default value
COLUMNS
I
15
ROWS
I
15
MINES
I
20
Ok, go to the layout tab and create a new Group for show these settings:
Create now an Input Field with its Label for show the field ‘Columns’, and create the binding with the context:
Repeat the process with the fields ‘Rows’ and ‘Mines’.
Then create a Toolbar with two buttons: NEW_GAME and RESTART.Create also an action for each button:
Create now other Group for the game itself. And then create in this group a Transparent Container for store the boxes of the game. Create it with the name BOXES_CONTAINER.
In this transparent container we will create dynamically the boxes of the game. We need to set the property Layout to GridLayout in order to control the number of boxes we want per row depending on the field ‘Columns’ in Settings.
The property colCount represents the number of the grid columns. But this property hasn’t a button for create the binding with the field ‘Columns’. So, how to do it? Later we will change this value dynamically using the method SET_COL_COUNT of the class CL_WD_GRID_LAYOUT.
At this point you should have something like this:
Assistance class
We’re going to create an assistance class for store some texts which we’ll use in the messages. And also some public attributes to be used in an easy way from the methods in the WD component.
Goto SE24 and create a new class:
Set the Superclass CL_WD_COMPONENT_ASSISTANCE:
Create the following text elements:
Create also the following attributes:
Attribute
Description
COLUMNS
Number of Columns
ROWS
Number of Rows
MINES
Number of Mines
MAX_BOXES
Number of Boxes
BOXES_CHEKED
Number of Boxes Checked
BOXES_WITHOUT_MINES
Number of Boxes without a Mine
BOX
Reference to a context element
MINE_TABLE
Table with the numbers of the boxes with a Mine
BOXES_VALIDATED
Boxes already validated when a box is used
BOX_PATH
First part of the binding path: Value: 'BOXES.BOX_'
Add the assistance class to the component:
Quick Tip: How to use the assistance class.
When you add an assistance class to a WD component a new attribute called WD_ASSIST is created in each controller.
For get a text element stored in that class you can use the code wizard.
The proper place for create new UI elements dynamically is in the method WDDOMODIFYVIEW. This method has a parameter called VIEW type ref to IF_WD_VIEW that we need to use for create the UI elements. But we want to create the UI Elements from the button we’ve created, so a trick for do it is create an attribute in the view:
Then within the method WDDOMODIFYVIEW add the following code to initialize this attribute:
Method WDDOMODIFYVIEW
* Save the parameter 'View' in the attribute 'View_ref' IF first_time EQ'X'.
wd_this->view_ref = view.
ENDIF.
Now we can create the UI elements from the method we want in this view.
So, go to the tab Methods and create a new method START_GAME. Add a parameter to this method called RESTART, using this parameter we know if we have to start a new game or if we have to restart the current game with the settings saved in the assistance class.
This method set the property colCount of the grid dependind on the field Columns. Then create a root node for store all the boxes nodes. And call to the method Create_Box for creates a context node and a toggle button for each box.
Method START_GAME
DATA: lo_nd_settings TYPEREFTO if_wd_context_node, lo_el_settings TYPEREFTO if_wd_context_element, ls_settings TYPE wd_this->element_settings.
* Get the transparent container where we'll create the Boxes lo_trans_cont ?= wd_this->view_ref->get_element( 'BOXES_CONTAINER' ).
* New game; IF restart EQ space.
* Read the settings: lo_el_settings->get_static_attributes( IMPORTING static_attributes = ls_settings ).
* Get the layot data: CALLMETHOD lo_trans_cont->get_layout RECEIVING the_layout = lo_layout. lo_grid_layout ?= lo_layout.
* Set the column count to the value filled in the settings: CALLMETHOD lo_grid_layout->set_col_count EXPORTING value = ls_settings-columns.
* Save the settings in the assistance class attributes: wd_assist->rows = ls_settings-rows. wd_assist->columns = ls_settings-columns. wd_assist->mines = ls_settings-mines. wd_assist->max_boxes = ls_settings-columns * ls_settings-rows. wd_assist->boxes_without_mines = wd_assist->max_boxes - ls_settings-mines.
* Validations;
* Number of mines exceeds the number of boxes ? IF ls_settings-mines GT wd_assist->max_boxes. lv_msg = wd_assist->if_wd_component_assistance~get_text( '003' ). CALLMETHOD lo_message_manager->report_error_message EXPORTING message_text = lv_msg. RETURN. ENDIF.
ENDIF. * Max num of boxes exceeded? IF wd_assist->max_boxes GT 1000.
CALLMETHOD lo_message_manager->report_error_message EXPORTING message_text = 'Max number of boxes exceeded: Max 1000'. RETURN. ENDIF.
* Fill the table with the numbers of the boxes which contain a mine: wd_this->fill_mine_table( ).
ELSE. "Restart ?
* If we are restarting the game read the settings stored in the assistence class: ls_settings-rows = wd_assist->rows. ls_settings-columns = wd_assist->columns. ls_settings-mines = wd_assist->mines. lo_el_settings->set_static_attributes( EXPORTING static_attributes = ls_settings ). ENDIF.
CLEAR: wd_assist->boxes_cheked. * Remove all the UI Toggle buttons created within the
* transparent container in previous games: CALLMETHOD lo_trans_cont->remove_all_children.
* Get meta data info of root context node lr_context_info = wd_context->get_node_info( ).
* Remove the nodes created in previous games; DATA lo_nd_mines TYPEREFTO if_wd_context_node. TRY. lo_nd_mines = wd_context->get_child_node( name = c_boxes ). CATCH cx_wd_context. ENDTRY.
* For the first game lo_nd_mines will be empty IF lo_nd_mines ISNOTINITIAL.
* Removing the main root node we are removing also all its childs: CALLMETHOD lr_context_info->remove_child_node EXPORTING name = c_boxes. ENDIF.
* Create a main root node for all the boxes; CALLMETHOD lr_context_info->add_new_child_node EXPORTING name = c_boxes is_static = abap_false is_mandatory = abap_true is_mandatory_selection = abap_false is_multiple = abap_false is_multiple_selection = abap_true is_singleton = abap_true is_initialize_lead_selection = abap_true RECEIVING child_node_info = lr_mines_info.
* Create the boxes: Context nodes and UI Elements; DO wd_assist->max_boxes TIMES.
The method CREATE_BOX create a new context element as child of the ‘root’ context node ‘BOXES’ and then create a new Toggle Button creating a binding with the just created context node.
* Create the path for the bindings; CONCATENATE wd_assist->box_path num_box '.CHECKED' INTO lv_checked. CONCATENATE wd_assist->box_path num_box '.TEXT' INTO lv_text. CONCATENATE wd_assist->box_path num_box '.ICON' INTO lv_image.
* Bind the property "Enabled" with the inverse of the property Checked
* With this you can only use the button one time. CONCATENATE wd_assist->box_path num_box '.CHECKED:NOT' INTO lv_enabled.
* Create a new toggle button and do the binding with the node; CALLMETHOD cl_wd_toggle_button=>new_toggle_button EXPORTING id = lv_name bind_checked = lv_checked bind_enabled = lv_enabled bind_text = lv_text bind_image_source = lv_image on_toggle = 'TOGGLE_BOX' width = '20px' RECEIVING control = lo_button.
* New grid data element: lo_grid_data = cl_wd_grid_data=>new_grid_data( element = lo_button ).
* Add the button to the transparent container MINES_CONTAINER io_trans_cont->add_child( lo_button ).
We are setting the action TOGGLE_BOX for the event ON_TOGGLE. We need to create this action, by the moment without code.
We are using the static element type ZZMINE. We need to create it, so go to SE11 and create it with the following fields:
We need to call to this method from the actions NEW_GAME and RESTART:
Action NEW_GAME
wd_this->start_game( ).
Action RESTART
wd_this->start_game( restart = 'X' ).
We are going to do the first test to see if everything works properly. First we need to create the Webdynpro Application:
Now do a quick test:
Try to create some games with different amount of boxes:
Context Menus
We are going to create a context menu with two options for mark the boxes as ‘Mine’ or clear this mark.
Go to the layout tab and create a context menu called MENU. Then insert in this menu two menu options. Create also two actions for these options: MARK_AS_MINE and CLEAR_MARK.
We’re going to active these options only when the originator will be a toggle button.
The option MARK_AS_MINE will be enabled when the toggle button is unchecked and the button hasn't an icon assigned.
The option CLEAR_MARK will be enabled when the toggle button is unchecked and the button has an icon assigned.
For do it we need to add some code to the method WDDOONCONTEXTMENU.
This method has three parameters:
The parameter CONTEXT_MENU_EVENT contains the originator element. With the parameter CONTEXT_MENU_MANAGER we can obtain the object MENU that we need to return.
We haven’t the originator element reference within the actions assigned to the Menu Options, so we are going to save this reference in the attribute BOX of the assistance class.
Method WDDOONCONTEXTMENU
DATA: lv_node TYPE string, lv_subnode TYPE string, lv_attribute TYPE string, lo_mine TYPEREFTO cl_wd_toggle_button, lo_menu_item TYPEREFTO cl_wd_menu_action_item, lv_path TYPE string.
DATA: lo_nd_mine TYPEREFTO if_wd_context_node, lo_el_mine TYPEREFTO if_wd_context_element, ls_mine TYPE zzmine. * Get the originator toggle button TRY. lo_mine ?= context_menu_event->originator.
CATCH cx_sy_move_cast_error.
* IF is not a button exit the method; RETURN. ENDTRY.
* Get the path of the primary property biding; CALLMETHOD lo_mine->bound__primary_property RECEIVING path = lv_path.
* Obtain the path to the node; SPLIT lv_path AT'.'INTO lv_node lv_subnode lv_attribute. CONCATENATE lv_node lv_subnode INTO lv_path SEPARATEDBY'.'. * Get the data of this node; lo_nd_mine = wd_context->path_get_node( path = lv_path ).
* Save the element in the attribute mine of this view
* because in the actions CLEAR_MARK and MARK_AS_MINE we haven't* this reference: wd_assist->box = lo_nd_mine->get_element( ). wd_assist->box->get_static_attributes( IMPORTING static_attributes = ls_mine ).
* If it's checked: Exit the method; CHECK ls_mine-checked ISINITIAL.
menu = context_menu_manager->get_context_menu( 'MENU' ).
* If haven't an icon assigned: Active only the option: MARK_AS_MINE. lo_menu_item ?= menu->get_item( id = 'MARK_AS_MINE' ). IF ls_mine-iconEQ SPACE. lo_menu_item->set_enabled( abap_true ). ELSE. lo_menu_item->set_enabled( abap_false ). ENDIF.
* IF have an icon assigned: Active only the option CLEAR_MARK. lo_menu_item ?= menu->get_item( id = 'CLEAR_MARK' ). IF ls_mine-iconEQ SPACE. lo_menu_item->set_enabled( abap_false ). ElSE. lo_menu_item->set_enabled( abap_true ). ENDIF.
Now we need to add some code to the actions of these buttons:
- - - When a box is checked the action TOGGLE_BOX is triggered.
- - - When a box is checked we validate if contains a Mine reading the table WD_ASSIST->MINE_TABLE. If has a Mine the game is finished: You lose!
- - If doesn’t contain a Mine then we call the method CHECK_BOX.
- - The method CHECK_BOX first of all validates if the number of boxes checked (WD_ASSIST->BOXES_CHEKED) is equal to the number of boxes without Mines (WD_ASSIST->BOXES_WITHOUT_MINES), if yes: game finished: You win!
- - If not, counts the number of neighbors boxes that contain a Mine. If this number is not equal to zero then we print this number in the box and leave the method.
- - If the number of neighbors’ boxes with a mine is zero then we call recursively to this method with all the neighbors of this box.
Know the number of the neighbors boxes is easy:
Add the following code to the action assigned to the toggle buttons:
Action TOGGLE_BOX
DATA: lo_api_controller TYPEREFTO if_wd_controller, lo_message_manager TYPEREFTO if_wd_message_manager, lv_msg TYPE string.
DATA: lv_id TYPE string, lv_path TYPE string, lv_mine TYPE numc3, lo_toggle TYPEREFTO cl_wd_toggle_button.
DATA: lo_nd_mine TYPEREFTO if_wd_context_node, lo_el_mine TYPEREFTO if_wd_context_element, ls_mine TYPE zzmine.
DATA: lv_node TYPE string, lv_subnode TYPE string, lv_attribute TYPE string, lv_name TYPE string, lv_num TYPE numc3.
* Read the content of the box; SPLIT lv_path AT'.'INTO lv_node lv_subnode lv_attribute.
* Is a mine? SPLIT lv_subnode AT'_'INTO lv_name lv_mine. READTABLE wd_assist->mine_table WITHKEY num = lv_mine TRANSPORTINGNOFIELDS. IF sy-subrc EQ0.
* Display a message: You lose ! lv_msg = wd_assist->if_wd_component_assistance~get_text( '002' ). CALLMETHOD lo_message_manager->report_error_message EXPORTING message_text = lv_msg. * Show all the mines in red: wd_this->show_mines( icon = icon_led_red ). RETURN. ENDIF.
* If haven't a mine, call the method check_box * The method will be called recursively to check the neighboring boxes.
* The table wd_assist->boxes_validated have the number of the boxes
* that are already validated: REFRESH wd_assist->boxes_validated. SPLIT lv_subnode AT'_'INTO lv_subnode lv_num. wd_this->check_box( box_num = lv_num first_time = 'X' ).
The method CHECK_BOX will be called recursively:
Method CHECK_BOX
TYPE-POOLS: icon.
DATA: lo_nd_settings TYPEREFTO if_wd_context_node, lo_el_settings TYPEREFTO if_wd_context_element, ls_settings TYPE wd_this->element_settings.
DATA: lo_nd_mine TYPEREFTO if_wd_context_node, lo_el_mine TYPEREFTO if_wd_context_element, ls_mine TYPE zzmine.
DATA: lv_id TYPE string, lv_num TYPE numc3, lv_int TYPEi, lv_row TYPEi, lv_column TYPEi, lv_mines TYPE n, lv_path TYPE string.
DATA: lo_api_controller TYPEREFTO if_wd_controller, lo_message_manager TYPEREFTO if_wd_message_manager, lv_msg TYPE string. * Get the message manager; lo_api_controller ?= wd_this->wd_get_api( ). CALLMETHOD lo_api_controller->get_message_manager RECEIVING message_manager = lo_message_manager. * Check if the box has already been validated; READTABLE wd_assist->boxes_validated WITHKEY num = box_num TRANSPORTINGNOFIELDS. IF sy-subrc EQ0. RETURN. "Exit the method ELSE. APPEND lv_int TO wd_assist->boxes_validated. ENDIF.
* Check if is checked; CONCATENATE zcl_test_ms_assistence=>box_path box_num INTO lv_path. lo_nd_mine = wd_context->path_get_node( path = lv_path ). lo_el_mine = lo_nd_mine->get_element( ). lo_el_mine->get_static_attributes( IMPORTING static_attributes = ls_mine ). IF ls_mine-checked EQ'X'AND first_time EQ space. RETURN. ENDIF.
* Add 1 to the number of boxes checked ADD1TO wd_assist->boxes_cheked.
* If the number of boxes chequed is equal to the boxes without mines:
* then: You win ! IF wd_assist->boxes_cheked EQ wd_assist->boxes_without_mines.
* Display a message: You win ! lv_msg = wd_assist->if_wd_component_assistance~get_text( '001' ). CALLMETHOD lo_message_manager->report_success EXPORTING message_text = lv_msg.
* Show all the mines in green: wd_this->show_mines( icon = icon_led_green ). RETURN. ENDIF.
* Check if the neighbors boxes have a mine.
* We have stored the number of boxes with a mine
* in the attribute mine_table of the assistence class
* Number of column of the current box lv_column = box_num MOD wd_assist->columns. IF lv_column EQ0. lv_column = wd_assist->columns. ENDIF.
* Number of row of the current box lv_row = CEIL( box_num / wd_assist->columns ).
* Check the upper left box: IF lv_row GT1AND lv_column GT1. lv_int = box_num - ls_settings-columns - 1. IF lv_int ISNOTINITIAL. READTABLE wd_assist->mine_table WITHKEY num = lv_int TRANSPORTINGNOFIELDS. IF sy-subrc EQ0. ADD1TO lv_mines. ENDIF. ENDIF. ENDIF.
* Check the upper box IF lv_row GT1. lv_int = box_num - ls_settings-columns. IF lv_int ISNOTINITIAL. READTABLE wd_assist->mine_table WITHKEY num = lv_int TRANSPORTINGNOFIELDS. IF sy-subrc EQ0. ADD1TO lv_mines. ENDIF. ENDIF. ENDIF.
* Check the upper right box IF lv_row GT1AND lv_column LT wd_assist->columns. lv_int = box_num - ls_settings-columns + 1. IF lv_int ISNOTINITIAL. READTABLE wd_assist->mine_table WITHKEY num = lv_int TRANSPORTINGNOFIELDS. IF sy-subrc EQ0. ADD1TO lv_mines. ENDIF. ENDIF. ENDIF.
* Check the left box IF lv_column GT1. lv_int = box_num - 1. IF lv_int ISNOTINITIAL. READTABLE wd_assist->mine_table WITHKEY num = lv_int TRANSPORTINGNOFIELDS. IF sy-subrc EQ0. ADD1TO lv_mines. ENDIF. ENDIF. ENDIF.
* Check the right box IF lv_column LT wd_assist->columns. lv_int = box_num + 1. IF lv_int LE wd_assist->max_boxes. READTABLE wd_assist->mine_table WITHKEY num = lv_int TRANSPORTINGNOFIELDS. IF sy-subrc EQ0. ADD1TO lv_mines. ENDIF. ENDIF. ENDIF.
* Check the lower left box IF lv_column GT1AND lv_row LT wd_assist->rows. lv_int = box_num + ls_settings-columns - 1. IF lv_int LT wd_assist->max_boxes. READTABLE wd_assist->mine_table WITHKEY num = lv_int TRANSPORTINGNOFIELDS. IF sy-subrc EQ0. ADD1TO lv_mines. ENDIF. ENDIF. ENDIF.
* Check the lower box IF lv_row LT wd_assist->rows. lv_int = box_num + ls_settings-columns. IF lv_int LT wd_assist->max_boxes. READTABLE wd_assist->mine_table WITHKEY num = lv_int TRANSPORTINGNOFIELDS. IF sy-subrc EQ0. ADD1TO lv_mines. ENDIF. ENDIF. ENDIF.
* Check the lower right box IF lv_row LT wd_assist->rows AND lv_column LT wd_assist->columns. lv_int = box_num + ls_settings-columns + 1. IF lv_int LT wd_assist->max_boxes. READTABLE wd_assist->mine_table WITHKEY num = lv_int TRANSPORTINGNOFIELDS. IF sy-subrc EQ0. ADD1TO lv_mines. ENDIF. ENDIF. ENDIF.
* If the number of the neighbors boxes with a mine is greater than 0
* print this number in this box and exit the method: IF lv_mines GT0.