ABAP Dynpro-复合屏幕元素

返回主页image164[4]

图标Status Icons(ICON_CREATE)

可以使用ICON_CREATE函数来为Status Field  image165[4] 生成符合某种格式的Icon信息串(选择屏幕上带图标按钮实例请参考这里):

image166[4] image167[4]

 

REPORT DEMO_DYNPRO_STATUS_ICONS.

DATA value TYPE i VALUE 1.
DATA: status_icon TYPE icons-text,
icon_name(20),
icon_text(10).
CALL SCREEN 100.
MODULE set_icon OUTPUT.
CASE value.
WHEN 1.
icon_name = 'ICON_GREEN_LIGHT'.
icon_text =  'Low'.
WHEN 2.
icon_name = 'ICON_YELLOW_LIGHT'.
icon_text = 'Middle'.
WHEN 3.
icon_name = 'ICON_RED_LIGHT'.
icon_text =  'High'.
ENDCASE.
CALL FUNCTION 'ICON_CREATE'
EXPORTING
name  = icon_name"图标名称
text  = icon_text"图标文本(显示在图标的后面)
info  = 'Status'"图标快速提示信息
add_stdinf = 'X'"图标快速提示信息开关
IMPORTING
RESULT  = status_icon."图标字符串
ENDMODULE.
MODULE change."切换图标
CASE value.
WHEN 1.
value = 2.
WHEN 2.
value = 3.
WHEN 3.
value = 1.
ENDCASE.
ENDMODULE.

image168[4]

image169[4] image170[4] image171[4]

右键菜单Context Menus for Screen

Context menu是右键点击的时候在鼠标的位置弹出的。在定义screen object的时候可以定义是否提供context menu(screen,input fields,table controls和box等),当选择context menu的一个object时,事件处理就会执行应用程序的一个subroutine,这种机制传递了一个指向subroutine的reference,程序通过这个menu reference来显示menu。可以通过menu painter来创建menu或者动态创建menu。当执行一个menu function时,application program得到control并响应用户输入。Context menu被分配给output fields,当你给box,table control或screen分配context menu时,所有没有分配context menu从属于它的output fields都会继承这个context menu。可以通过object navigator来创建context menu,也可以通过menu painter来创建。Context menu是一种特殊的GUI STATUS,它有name,descripative text和status type:context menu

可以通过CL_CTMENU类的静态方法load_gui_status来load通过Menu painter预先定义好的context menu。通过其他方法可以重新创建和动态修改context menu。如果用户通过context menu激活了一个function(菜单项),这个function的function code就会填到command field中并根据function type触发相应的PAI。Cl_ctmen除了静态方法load_gui_status外还有一系列的方法用来动态修改context menu,这些方法在ON_CTMENU_<context>中调用。

在Screen Painter中可以通过元素的 image172[4] 来设置context menu(不是具体某个菜单项,它是一个菜单上下文,相当于一个菜单容器,它里面有什么样的菜单项)的构造处理。如果没有给某个元素设置context menu,则该元素会继承上一级的元素的菜单(如某个Frame中某个元素未设置,则它会继续直Frame所设置的上下文菜单,如果Frame未设置,则会继续屏幕所设置的)

如果某元素绑定context menu后,当在元素上鼠标右键(不会触发PAI事件,只有当选择某个具体菜单项时才会触发PAI)时,会自动调用ON_CTMENU_<context>(<context>为context menu的ID)这样的一个Form,所以可以在回调该From时动态的定义context menu的构造过程

对于每一个分配给元素的context menu,在运行时会自动创建一个CL_CTMENU对象,当用户右击请求这个context menu时,会将这个context menu的引用传递给下面所示的这个Form:

FORM ON_CTMENU_<context> USING <l_menu> TYPE REF TO cl_ctmenu.
...
ENDFORM.

对象传递给这个Form时,context menu中是没有菜单项的,这时可以在这个Form中使用CL_CTMENU对象相关方法来动态的构造context menu

右键菜单示例:

利用“CL_CTMENU”类中的各种方法可以实现许多功能,如方法“load_gui_status”允许使用系统预定义的上下文菜单;可通过方法“add_menu”或者“add_submenu”来添加其他的同级菜单对象或者子菜单,以及添加分隔线、激活或者禁止选择(菜单项呈灰色)等。

 

通过Menu Painter创建context menu时,类型要选择Context Menu:

image173[4]

这里创建CONTEXT_MENU_1、CONTEXT_MENU_2、CONTEXT_MENU_3三个context menu:

image174[4]

image175[4]

image176[4]

以及普通类型的SCREEN_101:

image177[4]

REPORT demo_dynpro_context_menu.

DATA: field1 TYPE i VALUE 10,
field2 TYPE p DECIMALS 4.
DATA: prog TYPE sy-repid,
flag(1) TYPE c VALUE 'X'.
DATA: ok_code TYPE sy-ucomm,
save_ok TYPE sy-ucomm.
prog = sy-repid.
CALL SCREEN 100.
MODULE status_0100 OUTPUT.
IF flag = 'X'.
SET PF-STATUS 'SCREEN_101' EXCLUDING 'REVEAL'.
ELSEIF flag = ' '.
SET PF-STATUS 'SCREEN_101' EXCLUDING 'HIDE'.
ENDIF.
LOOP AT SCREEN.
IF screen-group1 = 'MOD'.
IF flag = 'X'.
screen-active = '1'.
ELSEIF flag = ' '.
screen-active = '0'.
ENDIF.
MODIFY SCREEN.
ELSEIF screen-name = 'TEXT_IN_FRAME'.
IF flag = 'X'.
screen-active = '0'.
ELSEIF flag = ' '.
screen-active = '1'.
ENDIF.
MODIFY SCREEN.
ENDIF.
ENDLOOP.
ENDMODULE.
MODULE cancel INPUT.
LEAVE PROGRAM.
ENDMODULE.
MODULE user_command_0100.
save_ok = ok_code.
CLEAR ok_code.
CASE save_ok.
WHEN 'HIDE'.
flag = ' '.
WHEN 'REVEAL'.
flag = 'X'.
WHEN 'SQUARE'.
field2 = field1 ** 2.
WHEN 'CUBE'.
field2 = field1 ** 3.
WHEN 'SQUAREROOT'.
field2 = field1 ** ( 1 / 2 ).
WHEN 'CUBICROOT'.
field2 = field1 ** ( 1 / 3 ).
ENDCASE.
ENDMODULE.
FORM on_ctmenu_text USING l_menu TYPE REF TO cl_ctmenu.
"加载已定义好的静态上下文菜单
CALL METHOD l_menu->load_gui_status
EXPORTING
program = prog
status  = 'CONTEXT_MENU_1'"CANCEL
menu    = l_menu.
ENDFORM.
FORM on_ctmenu_frame USING l_menu TYPE REF TO cl_ctmenu.
CALL METHOD cl_ctmenu=>load_gui_status
EXPORTING
program = prog
status  = 'CONTEXT_MENU_2'"HIDE
menu    = l_menu.
CALL METHOD cl_ctmenu=>load_gui_status
EXPORTING
program = prog
status  = 'CONTEXT_MENU_1'"CANCEL
menu    = l_menu.
IF flag = ' '."如果已点击过 HIDE ,则此时HIDE菜单设置为不可用
DATA: fcodes TYPE ui_functions .
APPEND 'HIDE' TO fcodes .
CALL METHOD l_menu->disable_functions
EXPORTING
fcodes = fcodes.
ENDIF.
ENDFORM.
FORM on_ctmenu_reveal USING l_menu TYPE REF TO cl_ctmenu.
CALL METHOD cl_ctmenu=>load_gui_status
EXPORTING
program = prog
status  = 'CONTEXT_MENU_3'"REVEAL
menu    = l_menu.
CALL METHOD cl_ctmenu=>load_gui_status
EXPORTING
program = prog
status  = 'CONTEXT_MENU_1'"CANCEL
menu    = l_menu.
IF flag = 'X'."如果已点击过 REVEAL ,则此时REVEAL菜单设置为不可用
DATA: fcodes TYPE ui_functions .
APPEND 'REVEAL' TO fcodes .
CALL METHOD l_menu->disable_functions
EXPORTING
fcodes = fcodes.
ENDIF.
ENDFORM.
FORM on_ctmenu_input USING l_menu TYPE REF TO cl_ctmenu.
DATA calculate_menu TYPE REF TO cl_ctmenu.
CREATE OBJECT calculate_menu."下面通过程序动态创建上下文菜单
"======给上下文菜单添加菜单项
CALL METHOD calculate_menu->add_function
EXPORTING
fcode = 'SQUARE'
text  = '平方'.
CALL METHOD calculate_menu->add_function
EXPORTING
fcode = 'CUBE'
text  = '立方'.
CALL METHOD calculate_menu->add_function
EXPORTING
fcode = 'SQUAREROOT'
text  = '开平方'.
CALL METHOD calculate_menu->add_function
    EXPORTING
fcode = 'CUBICROOT'
text  = '开立方'.
"=======将上面动态创建的上下文菜单挂到当前(计算)菜单下面,成为子菜单
CALL METHOD l_menu->add_submenu
EXPORTING
menu = calculate_menu
text = '计算'.
ENDFORM.

image178[4]

image179[4]

运行效果如下:

image180[4]

image181[4] 由于Field2已经隐藏,所Hide Result右键不可用

image182[4] Field2上没有设置Menu,所以继承自FRAME

image183[4]

image184[4]

image185[4] 在FRAME之外,显示屏幕的标准Context Menus

子屏幕Subscreens

如果是通过屏幕编辑器创建的子屏幕是,则在创建子屏幕时,需要指定屏幕的类型为子屏幕类型:

image186[4]

并且Next Screen一定要设置成自己。子屏幕需要放在子屏幕区域,且不能超过区域大小。

 

另外,除了像上面那样通过屏幕编辑器来创建子屏幕外,还可以通过通过SELECTION-SCREEN BEGIN OF SCREEN dynnr AS SUBSCREEN语句在ABAP程序代码中来定义子屏幕,定义后也可能通过下面的CALL SUBSCREEN语句来调用,而不一定要使用屏幕绘制器绘制的屏幕

 

子屏幕限制:

?       尽量不要将子屏幕中的元素名称与主屏幕或其他子屏幕设置相同,因为相同时需要对ABAP程序的相应全局部分做额外处理(只是尽量,但也是可以将多个不同子屏幕中相同名称的屏幕元素对应到同一个ABAP程序中的全局变量,如后面例子中4个子屏幕共用同一ABAP程序中全局变量field,而且这4个子屏幕上都有一个名为field的输入框)

?       子屏幕中没有OK_CODE字段,使用的是主屏幕中的OK_CODE field;

?       子屏幕的flowlogic不能包括MODULE... AT EXIT- COMMAND的语句,类型为E的Function Code仅只能在主屏幕中处理;

?       子屏幕flowlogic中不能包含SET TITLEBAR,SET PF-STATUS,SETSCREEN,LEAVE SCREEN,or LEAVE TO SCREEN这些语句,任何这些语句都会引起运行错误,不能在子屏幕中改变主屏幕的GUI Status

 

在主屏幕的flow logic中使用CALL SUBSCREEN将子屏幕include进行来,语句如下:

PROCESS BEFORE OUTPUT.
...
CALL SUBSCREEN <area> INCLUDING [<prog>] <dynp>.
...
子屏幕都需要放在主屏幕中的某个指定的<area>区域元素中。

 

为了调用子屏幕的PAI事件,需要在主屏幕的PAI flow logic里如下调用:

PROCESS AFTER INPUT.
...
CALL SUBSCREEN <area>.
...
该语句的作用等效于调用(触发)了子屏幕中的PAI事件块

 

不能够将CALL SUBSCREEN语句放在CHAIN and ENDCHAIN 或者 LOOP and ENDLOOP之间调用。SY-DYNNR会在CALL SUBSCREEN开始到结束之前发生修改,且为子屏幕的屏幕号,并在返回到主屏幕时修改成主屏幕的屏幕号

 

示例:DEMO_DYNPRO_SUBSCREENS

先要画两个可以存储子屏幕的区域控件:

image187[4] image188[4]

效果:

image189[4] image190[4]

image191[4]

报表主程序:

REPORT demo_dynpro_subscreens.

DATA: ok_code TYPE sy-ucomm,
save_ok TYPE sy-ucomm.

DATA: number1(4) TYPE n VALUE'0110',"需显示的子屏幕
number2(4) TYPE n VALUE'0130', "
field(10) TYPEc, field1(10) TYPEc, field2(10) TYPEc."field为4个子屏幕共有的输入框
CALLSCREEN100."调用主屏幕
MODULE status_100 OUTPUT.
SET PF-STATUS 'SCREEN_100'."主屏幕的UI设置,只有一个标准工具栏上“取消”按钮
ENDMODULE.

MODULE fill_0110 OUTPUT.
field = 'Eingabe 1'(001)."110子屏幕显示前,为子屏幕上输入框赋初始值
ENDMODULE.

MODULE fill_0120 OUTPUT."120子屏幕显示前,为子屏幕上输入框赋初始值
field = field1.
ENDMODULE.

MODULE fill_0130 OUTPUT.
field = 'Eingabe 2'(002).
ENDMODULE.

MODULE fill_0140 OUTPUT.
field = field2.
ENDMODULE.

MODULE cancel INPUT."如果点击了标准工具栏上的“取消”按钮后退出整个程序
LEAVEPROGRAM.
ENDMODULE.

MODULE save_ok INPUT.
save_ok = ok_code.
CLEAR ok_code.
ENDMODULE.

MODULE user_command_0110 INPUT.
IF save_ok = 'OK1'.
number1 = '0120'.
field1 = field.
CLEARfield.
ENDIF.
ENDMODULE.

MODULE user_command_0130 INPUT.
IF save_ok = 'OK2'.
number2 = '0140'.
field2 = field.
CLEARfield.
ENDIF.
ENDMODULE.

MODULE user_command_100 INPUT.
CASE save_ok.
WHEN'SUB1'.
number1 = '0110'.
WHEN'SUB2'.
number1 = '0120'.
CLEAR field1.
WHEN'SUB3'.
number2 = '0130'.
WHEN'SUB4'.
number2 = '0140'.
CLEAR field2.
ENDCASE.
ENDMODULE.

100主对话屏幕:

image192[4] image193[4]

image194[4]

子屏幕:

image195[4] image196[4] image197[4]

image198[4] image199[4] image200[4]

image201[4] image202[4] image203[4]

image204[4] image205[4] image206[4]

 

 

 

Tabstrip(Tab条)

从技术角度来看,Tab条中的每一个Tab page是由子屏幕与Table Title按钮组成的:

image207[4]

所以,每一个Tab page是由一个Tab Title(实质上就是一个按钮,Tab title与按钮具有一样的属性)、一个子屏幕区域(用来Include子屏幕)组成的:

image208[4]

image209[4]

功能代码类型设置为“P”表示当用户按下这个TAB页按钮时,不触发主屏幕后台的PAI事件,如果FctType留空,则会触发

Ref.Field表示该TAB页按钮与其对应的要显示的子屏幕的连接,在PBO流逻辑中将使用这个“Ref.Field”调用对应的子屏幕

image210[4]

静态的Tabstrip(多个子屏幕区域)

必须为每一个Tab title分配一个单独的子屏幕区域,并且tabe title的Funcode的类型为P,还需在主屏幕的PBO块中加载所有的subscreens,以及在主屏幕的PAI块中调用每个subscreens的PAI事件。

当切换Tab page时,主屏幕的PAI事件不会触发(但子屏幕事件是否触PAI发看是否满足以下两个条件:一是在主屏幕的PAI块中调用了子屏幕的PAI事件了,即调用了CALL SUBSCREEN: <area>这样的语句;二是子屏幕中的元素确是触发了PAI事件,如点击了子屏幕中的某个按钮),所以在切换期间是不会对子屏幕中进行input checks数据检测动作,input checks数据检测动作需要等到点击主屏幕上的某个具有Function Code的元素时才触发(如F8)。所以静态的Tabstrip适用于显示数据,而不适合于输入数据。

对于静态的Tabstrip,需要在主屏幕的PBO与PAI中一次性(仅需一次)加载所有的子屏幕:

PROCESS BEFORE OUTPUT.
...
CALL SUBSCREEN: <area1> INCLUDING [<prog1>] <dynp1>,
<area2> INCLUDING [<prog2>] <dynp2>,
<area3> INCLUDING [<prog3>] <dynp3>,
...

PROCESS AFTER INPUT.
...
CALL SUBSCREEN: <area1>,
<area2>,
<area3>,
...

动态的Tabstrip(共用一个子屏幕区域)

所有的table title共用一个子屏幕区域,这是通过Screen Painter中Table Title元素的 image211[4] 属性来设定共享的。此时Table Title的Function code类型不指定,因此当每次进行切换时,会触发主屏幕的PAI事件,这样如果主屏幕的PAI块中调用了子屏幕的PAI事件,则子屏幕的PAI事件也会被调用。另外,在切换到其他Tab page之前会进行当前子屏幕的input checks数据检测动作,只有通过了input checks数据检测动作后才能切换到其他Tab page。另外,不像静态Tabstrip那样一次性全部将子屏幕加载到Tabstrip控件中,而是每次切换时include相应的子屏。

对于动态的Tabstrip,需要在主屏幕的PBO与PAI中多次(但每次只加载一个子屏幕)动态加载某个子屏幕:

PROCESS BEFORE OUTPUT.
...
CALL SUBSCREEN <area> INCLUDING [<prog>] <dynp>.
...

PROCESS AFTER INPUT.
...
CALL SUBSCREEN <area>.
...

静态Tabstrip示例

image212[4]

image213[4]

image214[4]

REPORT DEMO_DYNPRO_TABSTRIP_LOCAL.
"定义TABSTRIP,名称需也Screen Painter中的名相同
CONTROLS mytabstrip TYPE TABSTRIP.

DATA: ok_code TYPE sy-ucomm,
save_ok TYPE sy-ucomm.
"需要显示(激活)的 Tab Title
mytabstrip-activetab = 'PUSH2'.
CALL SCREEN 100.
MODULE status_0100 OUTPUT.
SET PF-STATUS 'SCREEN_100'.
ENDMODULE.
MODULE cancel INPUT.
LEAVE PROGRAM.
ENDMODULE.
MODULE user_command INPUT.
save_ok = ok_code.
CLEAR ok_code.
IF save_ok = 'OK'.
MESSAGE i888(sabapdocu) WITH 'MYTABSTRIP-ACTIVETAB ='
mytabstrip-activetab.
ENDIF.
ENDMODULE.
image215[4]

动态Tabstrip示例

这个示例是将上面静态的改成了动态的,基本上一样

image216[4]

image217[4]

报表程序:

CONTROLS mytabstrip TYPE TABSTRIP.
DATA: ok_code TYPE sy-ucomm,
save_ok TYPE sy-ucomm.
DATA  number TYPE sy-dynnr.

mytabstrip-activetab = 'PUSH2'.
number = '0120'.

CALL SCREEN 100.
MODULE status_0100 OUTPUT.
SET PF-STATUS 'SCREEN_100'.
ENDMODULE.
MODULE cancel INPUT.
LEAVE PROGRAM.
ENDMODULE.
MODULE user_command INPUT.
save_ok = ok_code.
CLEAR ok_code.
IF save_ok = 'OK'.
MESSAGE i888(sabapdocu) WITH 'MYTABSTRIP-ACTIVETAB =' mytabstrip-activetab.
ELSE.
mytabstrip-activetab = save_ok.
CASE save_ok."点击Tab Title时需要手动切换Tab
WHEN 'PUSH1'.
number = '0110'.
WHEN 'PUSH2'.
number = '0120'.
WHEN 'PUSH3'.
number = '0130'.
ENDCASE.
ENDIF.
ENDMODULE.

选择屏幕中的 Tabstrip

选择屏幕中的 Tabstrip 示例请参考这里

通过向导创建Tabstrip

选择工具栏中的 image218[4] ,绘制控件时,会弹出向导界面:

image219[4]

输入Tabstrip对象名:

image220[4]

输入每个Tab标签的标题,输入的个数也决定了Tab标签的个数:

image221[4]

每个Tab标签页面需要单独分配一个子屏幕,定义好页面后,系统将自动分配功能代码并为每个页面自动分配一个子屏幕:

image222[4]

输入生成的代码所存放的Include文件名:

image223[4]

完成后屏幕绘制上会生成名为TAB01的Tab标签控件:

image224[4]

根据生成的代码来看,向导所采用的是动态Tabstrip方式生成的:

PROCESS BEFORE OUTPUT.
*&SPWIZARD: PBO FLOW LOGIC FOR TABSTRIP 'TAB01'
MODULE TAB01_ACTIVE_TAB_SET.
CALL SUBSCREEN TAB01_SCA
INCLUDING G_TAB01-PROG G_TAB01-SUBSCREEN.
* MODULE STATUS_1001.
*
PROCESS AFTER INPUT.
*&SPWIZARD: PAI FLOW LOGIC FOR TABSTRIP 'TAB01'
CALL SUBSCREEN TAB01_SCA.
MODULE TAB01_ACTIVE_TAB_GET.
* MODULE USER_COMMAND_1001.

 

REPORT  zjzj_tabstripwizard.
CALL SCREEN 1001.

*&SPWIZARD: FUNCTION CODES FOR TABSTRIP 'TAB01'
CONSTANTS: BEGIN OF C_TAB01,
TAB1 LIKE SY-UCOMM VALUE 'TAB01_FC1',
TAB2 LIKE SY-UCOMM VALUE 'TAB01_FC2',
END OF C_TAB01.
*&SPWIZARD: DATA FOR TABSTRIP 'TAB01'
CONTROLS:  TAB01 TYPE TABSTRIP.
DATA:      BEGIN OF G_TAB01,
SUBSCREEN   LIKE SY-DYNNR,
PROG        LIKE SY-REPID VALUE 'ZJZJ_TABSTRIPWIZARD',
PRESSED_TAB LIKE SY-UCOMM VALUE C_TAB01-TAB1,
END OF G_TAB01.
DATA:      OK_CODE LIKE SY-UCOMM.

*&SPWIZARD: OUTPUT MODULE FOR TS 'TAB01'. DO NOT CHANGE THIS LINE!
*&SPWIZARD: SETS ACTIVE TAB
MODULE TAB01_ACTIVE_TAB_SET OUTPUT.
TAB01-ACTIVETAB = G_TAB01-PRESSED_TAB.
CASE G_TAB01-PRESSED_TAB.
WHEN C_TAB01-TAB1.
G_TAB01-SUBSCREEN = '1002'.
WHEN C_TAB01-TAB2.
G_TAB01-SUBSCREEN = '1003'.
WHEN OTHERS.
*&SPWIZARD:      DO NOTHING
ENDCASE.
ENDMODULE.

*&SPWIZARD: INPUT MODULE FOR TS 'TAB01'. DO NOT CHANGE THIS LINE!
*&SPWIZARD: GETS ACTIVE TAB
MODULE TAB01_ACTIVE_TAB_GET INPUT.
OK_CODE = SY-UCOMM.
CASE OK_CODE.
WHEN C_TAB01-TAB1.
G_TAB01-PRESSED_TAB = C_TAB01-TAB1.
WHEN C_TAB01-TAB2.
G_TAB01-PRESSED_TAB = C_TAB01-TAB2.
WHEN OTHERS.
*&SPWIZARD:      DO NOTHING
ENDCASE.
ENDMODULE.

结论:通过上面的实例的实现,发现向导Tab控件比手动写的Tab控件要简单得多,所以尽量采用向导生成的方式来生成Tab控件。

 

 

Custom Controls(自定义控件)

自定义控件是通过Screen Painter的 image225 来创建的区域与放在该区域中的应用控件组成的。通过 image225[1] 只是创建了用来存放具体应用组件的一个区域(即容器),需要放什么则是通过一系列相应类来编程实现的。

 

SAP Control Framework的事件类型
System events:
该事件发生时,不会触发PBO和PAI事件(所以也不会自动发生数据传输)。这类事件的优点是:事件处理方法会第一时间自动调用,不管input checks检测是否通过,因为事件处理方法总是在PAI触发之前执行,而PAI事件触前才进行input checks检测,所以input checks检测是在事件处理方法调用之后才进行;这类事件的缺点是:有可能屏幕字段的内容不会传递到ABAP程序中,这就会造成下一屏幕显示的数据为过时数据,但我们可以在事件处理方法中通过cl_gui_cfw=>set_new_ok_code手动设置Function code ,让PAI事件触发,则屏幕字段内容肯定会被传递到ABAP程序中去,所以这样可以避免这个问题。

image226

Application events:
该事件在PAI处理完后会自动触发(这种情况下屏幕字段已经传输到程序变量),你也可以在PAI事件当中使用CL_GUI_CFW=>DISPATCH来人为触发Application events事件的处理,当事件处理方法调用完后,控制权又会返回到DISPATCH的调用点继续执行PAI事件。

此种类型的事件的优点如下:可以控制事件处理方法在何时调用,并且在调用前数据已经传递到ABAP程序中了;缺点是:如果input checks检测不通过时,事件处理方法是不会被执行的。 image227

(注意:此图有点问题,因为在没有使用DISPATCH的情况下,当PAI事件执行完后,也会自动的调用事件处理方法)

 

CL_GUI_SPLITTER_CONTAINER

主要功能就是container的拆分。

CONSTRUCTOR:构造方法,主要参数:

PARENT -- Parent Container,待拆分的cl_gui_container的引用

ROWS –需要显示多少行,举例,你想把container分成上下两个部分,那么rows = 2

COLUMNS – 需要分成多少列

接下来主要是一些setter和getter方法

SET_BORDER 设置边框的格式,space:不设置 ‘X’:设置

SET_ROW_HEIGHT — GET_ROW_HEIGHT 用来设置行的高度

SET_COLUMN_WIDTH - GET_COLUMN_WIDTH 设置列的宽度

SET_ROW_MODE - GET_ROW_MODE 设置行的模式

SET_COLUMN_MODE - GET_COLUMN_MODE 设置列的模式

下面这四个方法原理一样,主要设置splitter的属性,例如能不能移动等等

SET_ROW_SASH - GET_ROW_SASH

SET_COLUMN_SASH - GET_COLUMN_SASH

get_container方法用来获取指定的拆分格子,参数:

row:      第几行

column:   第几列

container:获取到的格子引用,其类型还是一cl_gui_container类型的Container

 

SAP的GUI的类列表

CLASS NAME Super CLASS NAME DESCRIPTIO
CL_GUI_OBJECT   Proxy Class for a GUI Object
CL_FORMPAINTER_BASEWINDOW CL_GUI_OBJECT SAP Form Painter Window Base Class
CL_FORMPAINTER_BITMAPWINDOW CL_FORMPAINTER_BASEWINDOW SAP Form Painter Bitmap Window Class
CL_FORMPAINTER_TEXTWINDOW CL_FORMPAINTER_BASEWINDOW SAP Form Painter Text Window Class
CL_GUI_CONTROL CL_GUI_OBJECT Proxy Class for Control in GUI
CL_DSVAS_GUI_BUSIGRAPH CL_GUI_CONTROL DSVAS: Proxy for Business Graphic
CL_GFW_GP_PRES_CHART CL_GUI_CONTROL GFW: Product-specific section of CL_GUI_GP_PRES (Chart)
CL_GFW_GP_PRES_PIG CL_GUI_CONTROL GFW: product specific section for web view
CL_GFW_GP_PRES_SAP CL_GUI_CONTROL GFW: product-specific section of CL_GUI_GP_PRES (SAP BUSG)
CL_GUI_ALV_GRID_BASE CL_GUI_CONTROL Basis Class for ALV Grid
CL_CALENDAR_CONTROL_SCHEDULE CL_GUI_ALV_GRID_BASE Calendar View (Day, Week, Month)
CL_GUI_ALV_GRID CL_GUI_ALV_GRID_BASE ALV List Viewer
CL_ALV_DD_LISTBOX CL_GUI_ALV_GRID D&D List Box
CL_BUKF_CAT_GRID CL_GUI_ALV_GRID Key Figures - Grid of categories
CL_BUKF_DSRC_GRID CL_GUI_ALV_GRID Key Figures - Grid for Data sources
CL_BUKF_FILTER_GRID CL_GUI_ALV_GRID Key Figures - Filter for Key Figure
CL_BUKF_KF_GRID CL_GUI_ALV_GRID Key Figures - Grid for Key Figures
CL_BUKF_TERMS_GRID CL_GUI_ALV_GRID Key Figures - Grid for terms
CL_FTR_GUI_ENTRY_ALV CL_GUI_ALV_GRID Class: ALV Grid Control for Initial Screen (Without Toolbar)
CL_GFW_GP_GRID_ALV CL_GUI_ALV_GRID ALV grid proxy
CL_GUI_AQQGRAPHIC_ADAPT CL_GUI_CONTROL Network Adapter
CL_GUI_AQQGRAPHIC_CONTROL CL_GUI_CONTROL BW Basis Class Network Control
CL_GUI_AQQGRAPHIC_NETPLAN CL_GUI_AQQGRAPHIC_CONTROL Network Control
CL_GUI_BARCHART CL_GUI_CONTROL Bar chart wrapper
CL_GUI_BORDERPAINTER CL_GUI_CONTROL SAP Border Painter Control Proxy Class
CL_GUI_BTFEDITOR CL_GUI_CONTROL SAP BTF Editor Control Proxy Class
CL_GUI_CALENDAR CL_GUI_CONTROL Calendar Control Proxy Class
CL_GUI_CHART_ENGINE_WIN CL_GUI_CONTROL Graphics: Presentation Graphics (SAP GUI for Windows)
CL_GUI_CONTAINER CL_GUI_CONTROL Abstract Container for GUI Controls
CL_GUI_CONTAINER_INFO CL_GUI_CONTAINER Information on Container Controls
CL_GUI_CUSTOM_CONTAINER CL_GUI_CONTAINER Container for Custom Controls in the Screen Area
CL_GUI_DIALOGBOX_CONTAINER CL_GUI_CONTAINER Container for Custom Controls in the Screen Area
CL_ECL_VIEWER_FRAME CL_GUI_DIALOGBOX_CONTAINER Manage EAI Control in Own Window
CL_GUI_ECL_VIEWERBOX CL_GUI_DIALOGBOX_CONTAINER ECL Viewer as Dialog Box
CL_GUI_DOCKING_CONTAINER CL_GUI_CONTAINER Docking Control Container
CL_GUI_EASY_SPLITTER_CONTAINER CL_GUI_CONTAINER Reduced Version of Splitter Container Control
CL_EU_EASY_SPLITTER_CONTAINER CL_GUI_EASY_SPLITTER_CONTAINER Internal Test; Do Not Use
CL_GUI_GOS_CONTAINER CL_GUI_CONTAINER Generic Object Services Container
CL_GUI_SIMPLE_CONTAINER CL_GUI_CONTAINER Anonymous Container
CL_GUI_SPLITTER_CONTAINER CL_GUI_CONTAINER Splitter Control
CL_GUI_ECL_2DCOMPARE CL_GUI_CONTROL Compare Module for 2D Viewer
CL_GUI_ECL_3DCOMPARE CL_GUI_CONTROL Compare Module for 3D Viewer
CL_GUI_ECL_3DMEASUREMENT CL_GUI_CONTROL Measurement Module for 3D Viewer
CL_GUI_ECL_3DSECTIONING CL_GUI_CONTROL Sectioning Module for 3D Viewer
CL_GUI_ECL_MARKUP CL_GUI_CONTROL Markup (Redlining) Component
CL_GUI_ECL_PMI CL_GUI_CONTROL PMI Module for the 3D Viewer
CL_GUI_ECL_PRIMARYVIEWER CL_GUI_CONTROL Basis Class for ECL Viewers (2D und 3D)
CL_GUI_ECL_2DVIEWER CL_GUI_ECL_PRIMARYVIEWER Engineering Client 2D Viewer
CL_GUI_ECL_3DVIEWER CL_GUI_ECL_PRIMARYVIEWER Engineering Client 3D Viewer
CL_GUI_ECL_VIEWER CL_GUI_CONTROL Proxy Class for Engineering Client Viewer
CL_GUI_FORMPAINTER CL_GUI_CONTROL SAP Form Painter Control Proxy Class
CL_GUI_GLT CL_GUI_CONTROL Internal; Do Not Use!
CL_GUI_GP CL_GUI_CONTROL GFW: Superclass of all graphics proxies
CL_GUI_GP_GRID CL_GUI_GP GFW: Grid proxy
CL_GUI_GP_HIER CL_GUI_GP GFW: Structure graphics
CL_GUI_GP_PRES CL_GUI_GP GFW: Business graphic
CL_GUI_GRLT CL_GUI_CONTROL Internal; Do Not Use !! ( restricted license - see docu)
CL_GUI_HTML_EDITOR CL_GUI_CONTROL HTML Editor
CL_GUI_ILIDRAGNDROP_CONTROL CL_GUI_CONTROL Interactive List: Drag & Drop
CL_GUI_MOVIE CL_GUI_CONTROL SAP Movie Control
CL_GUI_NETCHART CL_GUI_CONTROL Network wrapper
CL_GFW_GP_HIER_SAP CL_GUI_NETCHART GFW: Product-specific section of CL_GUI_GP_HIER (NETZ)
CL_GUI_PDFVIEWER CL_GUI_CONTROL PDF Viewer
CL_GUI_PICTURE CL_GUI_CONTROL SAP Picture Control
CL_GFW_GP_PRES_WEB CL_GUI_PICTURE GFW: product specific section for web view
CL_GUI_RTF_EDITOR CL_GUI_CONTROL SAP SAPscript Editor Control
CL_GUI_SAPSCRIPT_EDITOR CL_GUI_RTF_EDITOR SAP SAPscript Editor Control
CL_GUI_SELECTOR CL_GUI_CONTROL SAPSelector: Control for selecting colors or bitmaps
CL_GUI_SPH_STATUS_CONTROL CL_GUI_CONTROL SAPphone: Status Event Control
CL_GUI_TABLEPAINTER CL_GUI_CONTROL SAP Table Painter Control Proxy Class
CL_GUI_TIMER CL_GUI_CONTROL SAP Timer Control
CL_GUI_TOOLBAR CL_GUI_CONTROL Toolbar Control
CL_CCMS_AL_GUI_TOOLBAR CL_GUI_TOOLBAR Alerts: GUI Toolbar Used in the Visual Framework
CL_GUI_WCF_WWP CL_GUI_CONTROL Internal Tool - DO NOT USE
CL_KW_AUTOMATION_CONTROL CL_GUI_CONTROL Helper Class for General Automation Objects
CL_LC_EDITOR_CONTROL CL_GUI_CONTROL Lifecycle Editor Control
CL_GCM_LCEDITOR_CONTROL CL_LC_EDITOR_CONTROL Control for the display of definition life cycles
CL_SOTR_SPELLCHECKER CL_GUI_CONTROL Interface with OTR Spellchecker
CL_SRM_BASE_CONTROL CL_GUI_CONTROL SRM Control
CL_SRM_STACKED_CONTROL CL_SRM_BASE_CONTROL RM Control with Stack
CL_TREE_CONTROL_BASE CL_GUI_CONTROL Internal Tree Control Class
CL_GUI_SIMPLE_TREE CL_TREE_CONTROL_BASE Simple Tree Control
CL_ITEM_TREE_CONTROL CL_TREE_CONTROL_BASE Internal Tree Control Class
CL_GUI_COLUMN_TREE CL_ITEM_TREE_CONTROL Column Tree Control
BDMT_CONTROL CL_GUI_COLUMN_TREE Administers Tree Control for Monitoring
CL_BUCC_TREE CL_GUI_COLUMN_TREE Consistency Checks - Library Tree
CL_GFW_COLUMN_TREE CL_GUI_COLUMN_TREE Do not use!!!!!!!!
CL_GFW_GP_HIER_SAPTREE CL_GFW_COLUMN_TREE GFW: Product-specific section of CL_GUI_GP_HIER
CL_HU_COLUMN_TREE CL_GUI_COLUMN_TREE Tree that Displays Handling Units
CL_GUI_LIST_TREE CL_ITEM_TREE_CONTROL List Tree Control
C_OI_CONTAINER_CONTROL_PROXY CL_GUI_CONTROL For Internal Use
SCE_HTML_CONTROL_EVENT_HANDLER CL_GUI_CONTROL Event Handler for SCE HTML Control
CL_ALV_TREE_BASE CL_GUI_CONTROL Basis Class ALV Tree Control
CL_GUI_ALV_TREE CL_ALV_TREE_BASE ALV Tree Control
CL_GCM_WORKLIST_TREE CL_GUI_ALV_TREE CM: Worklist
CL_PT_GUI_TMW_ALV_TREE CL_GUI_ALV_TREE Small Modification to CL_GUI_ALV_TREE
CL_GUI_ALV_TREE_SIMPLE CL_ALV_TREE_BASE Simple ALV Tree
CL_SIMPLE_TREE_VIEW_MM CL_GUI_ALV_TREE_SIMPLE Simplest Kind of Tree
CL_GUI_ECATT_RECORDER CL_GUI_CONTROL SAP eCATT Recorder Control - To be used by eCATT only!
CL_GUI_TEXTEDIT CL_GUI_CONTROL SAP TextEdit Control
CL_GCM_TEXTEDIT CL_GUI_TEXTEDIT CM: Long text control
CL_SOTR_TEXTEDIT CL_GUI_TEXTEDIT Edit Control for the OTR
CL_GUI_HTML_VIEWER CL_GUI_CONTROL HTML Control Proxy Class
CL_BFW_HTML_VIEWER_POC CL_GUI_HTML_VIEWER Browser Framework: Proxy for HTML Control
CL_CCMS_BSP_VIEWER CL_GUI_HTML_VIEWER HTML Control Proxy Class
CL_CCMS_AL_OBJ_DET_HTML_VIEWER CL_CCMS_BSP_VIEWER Alerts: Component That Displays Object Properties with HTML
CL_CCMS_FROG_HTML_VIEWER CL_GUI_HTML_VIEWER CL_GUI_FROG_HTML_VIEWER
CL_SSF_HTML_VIEWER CL_GUI_HTML_VIEWER Smart Forms: Enhanced HTML Viewer
CL_GUI_ECL_MATRIX CL_GUI_OBJECT Represents a Complete Data Type with 13 Floats
CL_GUI_RESOURCES CL_GUI_OBJECT GUI Resources (Fonts, Colors, ...)
CL_WF_GUI_RESOURCES CL_GUI_RESOURCES Getting Front Settings
CL_WF_GUI_RESOURCES_4_HTML CL_GUI_RESOURCES Get Front Settings for HTML Generation
CL_KW_AUTOMATION_OBJECT CL_GUI_OBJECT For Internal Use
CL_TABLEPAINTER_BASETABLE CL_GUI_OBJECT SAP Table Painter Table Base Class
CL_TABLEPAINTER_TABLE CL_TABLEPAINTER_BASETABLE SAP Table Painter Table Class
CL_TABLEPAINTER_TEMPLATE CL_TABLEPAINTER_BASETABLE SAP Table Painter Template Class
C_OI_AUTOMATION_OBJECT CL_GUI_OBJECT For Internal Use
CL_GUI_FRONTEND_SERVICES CL_GUI_OBJECT Frontend services

 

自定义类事件处理示例:

"事件源
CLASS vehicle DEFINITION INHERITING FROM object.
PUBLIC SECTION.
"实例事件
EVENTS: too_fast EXPORTING value(p1) TYPE i.
"静态事件
CLASS-EVENTS: too_fast_static EXPORTING value(p1) TYPE i.
METHODS: accelerate,show_speed IMPORTING msg TYPE string.
CLASS-METHODS: static_meth.
PROTECTED SECTION.
DATA speed TYPE i .
ENDCLASS.

CLASS vehicle IMPLEMENTATION.
METHOD accelerate."非静态方法触发事件
speed = speed + 1.
IF speed > 5.
"触发实例事件
RAISE EVENT too_fast EXPORTING p1 = speed .
"触发静态事件
RAISE EVENT too_fast_static  EXPORTING p1 = speed .
speed = 5.
ENDIF.
ENDMETHOD.
METHOD static_meth."静态方法触发事件
"静态方法不能确发实例事件
"RAISE EVENT too_fast EXPORTING p1 = speed .
"触发静态事件
RAISE EVENT too_fast_static  EXPORTING p1 = 1 .
ENDMETHOD.
METHOD show_speed.
WRITE: /  'show_speed: ' ,msg.
ENDMETHOD.
ENDCLASS.

"监听器
CLASS handler DEFINITION.
PUBLIC SECTION.
METHODS:"实例处理方法
"事件处理方法的定义,参数一定要与事件定义时的参数名一致,sender为事件源
handle_1 FOR EVENT too_fast OF vehicle IMPORTING p1 sender,
"如果省略了sender,则 handle_2 不能访问sender对象
handle_2 FOR EVENT too_fast OF vehicle IMPORTING p1 ,
"注意:静态事件不会传递 sender 参数
handle_11 FOR EVENT too_fast_static OF vehicle IMPORTING p1.
CLASS-METHODS:"静态处理方法
handle_3 FOR EVENT too_fast OF vehicle IMPORTING p1 sender,
handle_4 FOR EVENT too_fast OF vehicle IMPORTING p1,
handle_33 FOR EVENT too_fast_static OF vehicle IMPORTING p1.
ENDCLASS.

CLASS handler IMPLEMENTATION.
METHOD handle_1.
sender->show_speed( msg = 'handle_1' ).
WRITE: / 'handle_1:' , p1.
ENDMETHOD.
METHOD handle_2.
"不能访问sender对象
"sender->show_speed( ).
WRITE: / 'handle_2:' , p1.
ENDMETHOD.
METHOD handle_11.
WRITE: / 'handle_11:' , p1.
ENDMETHOD.
METHOD handle_3.
sender->show_speed( msg = 'handle_3' ).
WRITE: / 'handle_3:' , p1.
ENDMETHOD.
METHOD handle_4.
WRITE: / 'handle_4:' , p1.
ENDMETHOD.
METHOD handle_33.
WRITE: / 'handle_33:' , p1.
ENDMETHOD.
ENDCLASS.

DATA: o_vehicle TYPE REF TO vehicle,
o_handler TYPE REF TO handler.

START-OF-SELECTION.
CREATE OBJECT: o_vehicle,o_handler.
"实例事件处理方法 注册
SET HANDLER o_handler->handle_1 FOR  o_vehicle.
SET HANDLER o_handler->handle_2 FOR ALL INSTANCES.
SET HANDLER handler=>handle_3 FOR ALL INSTANCES.
SET HANDLER handler=>handle_4 FOR ALL INSTANCES.
"静态事件处理方法注册:对事件处理方法所对应的类的所有实例都有效
SET HANDLER o_handler->handle_11.
SET HANDLER handler=>handle_33.
DO 6 TIMES.
CALL METHOD o_vehicle->accelerate.
ENDDO.

自定义控件事件处理示例:

image228 image229

REPORT demo_custom_control.
* Declarations *****************************************************
"事件处理器(即监听器)
CLASS event_handler DEFINITION.
PUBLIC SECTION.
"handle_f1方法用来处理cl_gui_textedit控件的F1事件
"handle_f4方法用来处理cl_gui_textedit控件的F4事件
METHODS: handle_f1 FOR EVENT f1 OF cl_gui_textedit
IMPORTING sender,
handle_f4 FOR EVENT f4 OF cl_gui_textedit
IMPORTING sender.
ENDCLASS.

DATA: ok_code LIKE sy-ucomm,
save_ok LIKE sy-ucomm.
DATA: init,
"自定义容器
container TYPE REF TO cl_gui_custom_container,
"文本编辑器,后面会放到自定义容器中
editor    TYPE REF TO cl_gui_textedit.
"事件列表,存放需要触发的事件
DATA: event_tab TYPE cntl_simple_events,
event     TYPE cntl_simple_event.
DATA: line(256) TYPE c,
text_tab LIKE STANDARD TABLE OF line,
field LIKE line.
DATA handle TYPE REF TO event_handler.
* Reporting Events ***************************************************
START-OF-SELECTION.
line = 'First line in TextEditControl'.
APPEND line TO text_tab.
line = '--------------------------------------------------'.
APPEND line TO text_tab.
line = '...'.
APPEND line TO text_tab.
CALL SCREEN 100.
* Dialog Modules *****************************************************
MODULE status_0100 OUTPUT.
SET PF-STATUS 'SCREEN_100'.
IF init IS INITIAL."只做一次
init = 'X'.
"TEXTEDIT为Screen Painter中自定义容器名
CREATE OBJECT container
EXPORTING
container_name = 'TEXTEDIT'.
CREATE OBJECT editor
EXPORTING
parent = container."将文件编辑器控件放入自定义容器中
CREATE OBJECT handle.

"文本编辑器需要触发(使用RAISE EVENT语句触发)的事件列表,即你要告诉文本编辑控件
"需要使用 RAISE EVENT 语句触发哪些事件(因为只在类中进行定义了的事件是不会自动触
"发的,需要我们使用 RAISE EVENT 这个的语句来进行触发,然后监听这些事件的事件处理
"方法就会被回调),这与自己定义普通类事件原理是一样的(请参见vehicle类中的accelerate
"方法,此方法触发了too_fast与too_fast_static两个事件,只不过文本编辑器中触发事件的
"语句不需要我们写了,我们只需要告诉文本编辑器控件需要触发哪些事件,它就会使用RAISE EVENT
"语句自动帮我们去触发了)
event-eventid = cl_gui_textedit=>event_f1.
event-appl_event = ' '.                     "system event
APPEND event TO event_tab.
event-eventid = cl_gui_textedit=>event_f4.
event-appl_event = 'X'.                     "application event
APPEND event TO event_tab.

"为了将事件从屏幕终端传回到后台服务器程序中处理,需要调以下方法注册需要被传递回的事件
CALL METHOD: editor->set_registered_events
EXPORTING events = event_tab.
"监听方法向文本编辑控件注册(上面event_tab事件列表只管触发哪些事件,
"至于触发后,需回调哪些方法来进行事件处理则要在这里指定)
SET HANDLER handle->handle_f1
handle->handle_f4 FOR editor.
ENDIF.
"向文本编辑中写东西
CALL METHOD editor->set_text_as_stream
EXPORTING
text = text_tab.
ENDMODULE.
MODULE cancel INPUT.
LEAVE PROGRAM.
ENDMODULE.
MODULE user_command_0100 INPUT.
save_ok = ok_code.
CLEAR ok_code.
CASE save_ok.
WHEN 'INSERT'.
CALL METHOD editor->get_text_as_stream
IMPORTING
text = text_tab.
"此Function Code是后面手动设置触发的,并不是某个屏幕元素所
"对应的Function Code
WHEN 'F1'.
"按F1时,由于F1是系统事件,所以不会触发PAI,如果要执行到
"这里,需要在事件处理方法中手动触发,所以按F1时,先执行
"事件处理方法handle_f1,再才去执行PAI事件
MESSAGE 'PAI triggered by handler method' TYPE 'I'.
WHEN OTHERS."
"按F4时,由于F4是应用类事件,所以会触发PAI事件,所以当按
"F4后,先执行这里的PAI事件,再去执行相应事件处理方法:handle_f4
MESSAGE 'PAI triggered by application event' TYPE 'I'.
"调用此句后,会立即去执行后面的事件处理方法handle_f4(执行完后回到这里继续执行),如果将此
"语句注掉,但等PAI事件执行之后,还是会去调用事件处理方法handle_f4
"如果触发的是系统事件,如前面的F1,则不需要调用dispatch
"dispatch:此方法可以触发application event,如果不调用这个方法,application event
"会在PAI处理结束后自动调用事件处理方法。
CALL METHOD cl_gui_cfw=>dispatch.      "for application events
MESSAGE 'PAI after event handling' TYPE 'I'.
ENDCASE.
SET SCREEN 100.
ENDMODULE.

* Class Implementations **********************************************
CLASS event_handler IMPLEMENTATION.
METHOD handle_f1.
DATA row TYPE i.
MESSAGE 'F1 event handling' TYPE 'I'.
"获取光标在文本编辑中的位置
CALL METHOD sender->get_selection_pos
IMPORTING
from_line = row.
"取文本编辑中光标所在行的文本,并放到文本框中
CALL METHOD sender->get_line_text
EXPORTING
line_number = row
IMPORTING
text        = field.
"set_new_ok_code:设置一个新的Function code.该方法只能用于处理system event的
"handler方法中,以此手动触发PAI处理
"系统事件默认是不会触发PAI的,如果需要触发,则需要在事件处理方法中手动触发。注意:当事件处理方法执行完后才触发PAI
CALL METHOD cl_gui_cfw=>set_new_ok_code   "raise PAI for system events
EXPORTING new_code = 'F1'.
"清空缓冲,让服务器端与屏幕终端同步
CALL METHOD cl_gui_cfw=>flush.
ENDMETHOD.
METHOD handle_f4.
DATA row TYPE i.
MESSAGE 'F4 event handling' TYPE 'I'.
CALL METHOD sender->get_selection_pos
IMPORTING
from_line = row.
CALL METHOD sender->get_line_text
EXPORTING
line_number = row
IMPORTING
text        = field.
CALL METHOD cl_gui_cfw=>flush.
ENDMETHOD.
ENDCLASS.

Table Controls表格控件

程序创建

image230   image231

image232   image233

在Screen Painter中,Table的每一列都是一个文本框控件,也需要为每一个列统一命名:

image234

在设计时,如果要给表格增加列头或列体时,在表格控件所在的以外区域先将文本框与标签创建好,再托入到表格控件相应位置。

另外,如果表格中的列都是来自于词典中的透明表时或结构时,可以这样来做:首先画一个表格控件,并将透明表或词典结构作为表格控件的命:

image235

然后从词典中引用字段:

image236

并将选择的字段拖入到表格控件中:

image237

如果需要让表格中的项可以选择的话,需要在设计表格控件时,指定透明表或词典结构中的某个字段作为选择列:

image238

 

PROGRAM  sapmtz60 .
TABLES:sflight.
"注:不要使用如下定义,要使用上面数据词典方式,否则屏幕字段的值与ABAP程序
"无法传递(屏幕字段与ABAP中同名才能进行传递),但屏幕设计中使用的数据词典
"sflight-XXXX的命名方式,所以这里只能使用 TABLES 语句来进行定义
*DATA: sflight TYPE sflight.
DATA: ok_code(4),
rowspage TYPE i,"表格控件可以显示的最大行数,用来实现翻页功能(一屏中可显示的最大行数)
lastrowdisp TYPE i ,"表格控件中显示的最后一行数据的索引
line_count TYPE i."表格中实际显示的行数(一屏可能没有显示满)

==============================

在运行时,flights是一个结构,里面的cols字段是一个包含所有有列信息的内表,可以对这个内表进行处理:如隐藏某列:

data :ls_col like line of flights-cols.

ls_col-invisible = 1.
modify flights-cols index sy-tabix from ls_colWHERE screen-name = 'TZODA_WARNING-BUN'.

==============================

 

CONTROLS: flights TYPE TABLEVIEW USING SCREEN 200."使用CONTROLS定义表格,定义表格后才能使用
DATA int_flights LIKE sflight OCCURS 1 WITH HEADER LINE.

* ====================================PBO
MODULE status_0100 OUTPUT.
SET PF-STATUS 'TD0100'.
SET TITLEBAR '100'.
ENDMODULE.

MODULE status_0200 OUTPUT.
SET PF-STATUS 'TD0200'.
SET TITLEBAR '100'.
ENDMODULE.                 " STATUS_0100  OUTPUT

MODULE display_flights OUTPUT.
"注意,该module当内表为空时,PBO中的Loop循环还是会执行
sflight = int_flights."将程序中的值同步到屏幕字段中
"1、sy-loopc在PBO的LOOP循环中的值默认就是表格控件可以显示的最大数据行数(
"即可用作每页显示多少条数据,而不管内表中的数据是否可以填满表格可显示的最大行)
"2、但在PAI的LOOP循环中的值为当前屏幕表格控件实际显示的数据行数(因为有时表格
"中数据显示不满)
"3、如果退出PBO与PAI的Loop中后,sy-loopc的值恢复为0
rowspage = sy-loopc."记录每页显示满时能显示的行数(每次循环时的值都与第一次相同)
"flights-top_line记录了当前正在循环行的index,一出PAI或PBO中的
"LOOP语句后,flights-top_line的值就会恢复到表格将要显示的第一行
"数据的索引号
"记下表格中显示的最后一行数据的索引(随着循环增加会增加)
lastrowdisp = flights-top_line.
ENDMODULE.
* ====================================PAI
MODULE user_command_0100 INPUT.
CASE ok_code.
"按回车时,Function Code为空
WHEN space.
"获取表格控件中需要显示的所有数据
SELECT  * FROM sflight INTO TABLE int_flights
WHERE  carrid = sflight-carrid
AND    connid = sflight-connid .
"设置表格控件中显示的第一行为int_flights内表中的第1行
flights-top_line = 1.
"设置表格控件数据行总数,一般为所对应内表的总行数,如果
"小于内表总行数,则显示内表中前部分数据
DESCRIBE TABLE int_flights LINES flights-lines.
CLEAR ok_code.
ENDCASE.
ENDMODULE.
MODULE user_command_0200 INPUT.
DATA: ff TYPE i .
ff = flights-top_line.
CASE ok_code.
WHEN 'CANC'.
CLEAR ok_code.
SET SCREEN 100."设置下屏幕为第一屏幕(100)
LEAVE SCREEN."离开当前屏幕(200)后,系统会自动转换到一下屏幕(100)
WHEN 'EXIT'.
CLEAR ok_code.
SET SCREEN 0. "设置下屏幕为0,即当前屏幕结束后,整个屏幕序列会结束
LEAVE SCREEN.
WHEN 'BACK'.
CLEAR ok_code.
SET SCREEN 100. LEAVE SCREEN.
WHEN 'NEW'.
CLEAR ok_code.
SET SCREEN 100. LEAVE SCREEN.
WHEN 'P--'.
CLEAR ok_code.
PERFORM paging USING 'P--'.
WHEN 'P-'.
CLEAR ok_code.
PERFORM paging USING 'P-'.
WHEN 'P+'.
CLEAR ok_code.
PERFORM paging USING 'P+'.
WHEN 'P++'.
CLEAR ok_code.
PERFORM paging USING 'P++'.
ENDCASE.
ENDMODULE.
MODULE exit_0100 INPUT.
CASE ok_code.
"WHEN语句中不能使用OR,否则只有第一个有效
*    WHEN 'CANC' OR 'EXIT' OR 'BACK'.
WHEN 'CANC'.
CLEAR ok_code.
"设置当前屏幕的下一屏幕为0,如果跳转的下一屏幕为0,则
"会自动结束当前屏幕序列,所以此时后面的 leave screen 可以去掉
SET SCREEN 0.
"但加上以下语句后,可以立马结束当前屏幕,如果没有,则
"下一步会去执行屏幕的PAI事件,所以最好是加上
LEAVE SCREEN.
WHEN 'EXIT' .
CLEAR ok_code.
SET SCREEN 0.LEAVE SCREEN.
WHEN 'BACK'.
CLEAR ok_code.
SET SCREEN 0.LEAVE SCREEN.
ENDCASE.
ENDMODULE.
MODULE modi_flights INPUT.
int_flights = sflight."将屏幕字段中的内容同步到程序中
"flights-top_line记录了当前正在循环行的index,一出PAI或PBO中的
"LOOP语句后,flights-top_line的值就会恢复到表格将要显示的第一行
"数据的索引号
MODIFY int_flights INDEX flights-top_line.
"sy-loopc为当前表格中显示的行数
line_count = sy-loopc.
ENDMODULE.

* ====================================FORM
FORM paging USING code."翻页功能,实质就是对flights-top_line的设置
DATA: i TYPE i.

CASE code.
WHEN 'P--'."首页
flights-top_line = 1.
WHEN 'P-'."上一页
"点击翻页按钮与滚动鼠标时,在PAI的module中,flights-top_line的值还是
"当前表格显示的第一行数据的索引,当PAI执行完后,鼠标滚动后在随后
"的PBO中flights-top_line会自动调节(所以鼠标滚动时不需要处理),而点翻页按钮时则需要在这里手动
"调节,然后才能在下一屏幕的PBO中正确使用
flights-top_line = flights-top_line - rowspage.
IF flights-top_line <= 0.
flights-top_line = 1.
ENDIF.
WHEN 'P+'."下一页
IF line_count = rowspage AND lastrowdisp < flights-lines .
flights-top_line = lastrowdisp + 1.
ENDIF.
WHEN 'P++'."最后一页
"表格控件满显示时可能才有下一页
IF line_count = rowspage AND lastrowdisp < flights-lines .
i =  flights-lines - lastrowdisp .
IF i <= rowspage. "如果剩下的不足一页或刚好一页时
flights-top_line = lastrowdisp + 1.
ELSE."如果剩下的多余一页时,取最后一页
flights-top_line = flights-lines - rowspage + 1.
ENDIF.
ENDIF.
ENDCASE.
ENDFORM.

 

Screen 200 Flow logic:

PROCESS BEFORE OUTPUT.
MODULE status_0200.
"将内表中的数据显示(绑定)到表格控件(可显示区域)中,cursor表示从内表
"中的哪一行开始循环(即哪一行显示在表格控件的第一行),并且随着循环,
"flights-top_line会自动加1(即flights-top_line记录了当前循环到了内表中
"的哪一行数据了,因此flights-top_line可以在PAI的Loop循环调用的Module
"中使用。所以flights-top_line除了用来设置将内表中的哪一行显示为表格中
"的第一行外,还记录了当前循环到了内表中的哪一行了,但退出循环立即恢复
"成循环时的初始值)
"循环次数:当内表中的数据行足够时,循环次数为表格能显示的最大数据行数;
"当内表中的数据行不足填满表格时,循环次数由内表行决定(一定要注意的是:
"如果当内表为空时,此时也表现为内表行不足,但此时的循环次数又由表格能
"显示的最大行数决定了,所以当内表为空时小心)
LOOP AT int_flights WITH CONTROL flights CURSOR flights-top_line.
"将内表中的数据行显示到表格控件的相对应的行中
MODULE display_flights.
ENDLOOP.

"表格控件在鼠标滚动与点翻页按钮时会触发,但下面代码只是为了点击翻页按钮实现的,因为没有
"下面这些代码,表格控件的滚动功能也会存在
PROCESS AFTER INPUT.
"1、在PAI中还必须再循环一次(哪怕是空循环,否则编译不能通过),这是为了将
"表格中当前显示的数据行再次传递到ABAP程序中去。在这循环期间,程序可以使
"用SY-LOOPC系统变量来获知表格中当前显示的有多少行数据将被传递到ABAP程序
"中(因为只有显示出的数据才有可能发生了变化)
"2、在循环的过程中,flights-top_line的初始值为当前表格中显示的第一行记录
"的索引号(对应绑定内表中的索引),并且每循环一次也会自动加1(这里的
"flights-top_line与PAI中的作用是一样的,但退出循环时会恢复到当前表格显示
"的第一行记录索引)
"3、可循环次数为系统变量sy-loopc的值
"4、在PAI中,此类循环不能像上面PBO那样附加选项
LOOP AT int_flights .
MODULE modi_flights.
ENDLOOP.
MODULE user_command_0200.

通过向导创建

通过向导创建出来的表格可以同时实现数据的批量输入、输出及维护

本例通过表SPFLI为数据源来演示

由于数据表格可能实现批量维护操作,在ABAP报表主程序中定义两个内表:XSPFLI与YSPFLI,XSPFLI用于保存更新前的数据,YSPFLI用于保存修改后的数据,参照内表YSPFLI对物理表SPFLI进行修改操作,便能将中新增或修改的数据更新到数据库中;而对于在表格中被删除的数据,可以在数据更新前通过两张对比来获取:

REPORT  zjzj_hello.
TABLES:spfli.
DATA:xspfli LIKE STANDARD TABLE OF spfli WITH HEADER LINE,
yspfli LIKE STANDARD TABLE OF spfli WITH HEADER LINE.
CALL SCREEN 1001.

双击程序中的 1001,创建1001对话屏幕,并且为该屏幕创建名为T001的GUI:

image239

绘制1001屏幕:

选择工具条中的Table Control(with Wizard) image240 ,并在屏幕上拖动,系统将会弹出表控件向导初始页面:

image241

为表格控制命名:

image242

表格控件的数据源可以是物理表,也可以是程序中的内表(但只能为标准内表),这里我们引用程序中的标准内表YSPFLI作为数据源:

image243

选择表格控件中需要展示的表字段:

image244

表格控制属性设置:

image245

image246
image247

表格控件附带按钮:

image248

image249

指定用来存放表格向导产生的代码的INCLUDE文件名:

image250

image251

image252

双击表格控件,在表格控件属性框中还可以进一步设置表格属性(从弹出的表格控件属性框可以看到,与非向导的表格控件是一样的)

 

完成后可以看到生成的代码,代码与上面程序创建表格控件

image253 image254

但向导创建完后,但运行是没有数据的:

image255

下面将数据从SPFLI表中查出来,并将所获取的数据存放到YSPFLI中,且查询代码需要放在CALL SCREEN之前:

image256

再次运行,结果如下:

image257

从上图可以看出,“保存”与“退出”按钮都还未激活,原因就是还没有给对话屏幕程序加上GUI,下面给设置:

image258

运行结果:

image259

下面来实现这两个按钮的点击触发功能代码:

在1001屏幕逻辑流的PAI块里,加上如下调用Module代码:MODULE USER_COMMAND_1001.,然后再在ZSCREEN04_TAB Include文件里加上与数据库表同步的代码:

MODULE user_command_1001 INPUT.
ok_code = sy-ucomm.
IF ok_code = 'BREAK'.
LEAVE TO SCREEN 0.
ELSEIF ok_code = 'SAVE'.
LOOP AT yspfli."未被删除的数据就是要被从数据库中删除的数据
DELETE TABLE xspfli FROM yspfli.
ENDLOOP.

DELETE spfli FROM TABLE xspfli.
MODIFY spfli FROM TABLE yspfli.
COMMIT WORK.

ENDIF.
CLEAR:ok_code.
ENDMODULE.

 

 

结论:通过上面的实例的实现,发现向导表格比手动写的表格控件要简单得多,而且还具有很好的维护功能与翻页功能,所以尽量采用向导生成的方式来生成表格控件。

Tree Control 树

image260 image261 image262

 

PARAMETERS carrid TYPE scarr-carrid DEFAULT'AA'.

DATA:ok_code LIKE sy-ucomm,
save_code LIKE ok_code,
container_name1 TYPE scrfname VALUE'MYTREE',"Tree容器名称
tree_r TYPEREFTOcl_gui_simple_tree,"Tree控件引用
container_r TYPEREFTO cl_gui_custom_container."Tree容器引用
DATA: node_table LIKETABLEOF mtreesnode,"用于构造Tree数据内表:节点内表
node1 TYPE mtreesnode."节点
DATA:nodekey(100),nodedisp(200)."节点名与显示的文本名

CLASS cl_event_handle DEFINITION."定义事件处理类
PUBLICSECTION.
"用于处理事件的方法:双击节点时会触发
METHODS handle_dbclick FOREVENT node_double_click OF cl_gui_simple_tree
IMPORTING node_key.
ENDCLASS.

CLASS cl_event_handle IMPLEMENTATION."实现
METHOD handle_dbclick."
nodekey = node_key.
READTABLE node_table WITHKEY node_key = node_key INTO node1.
nodedisp = node1-text.
ENDMETHOD.
ENDCLASS.

"声明对象引用
DATA event_handler TYPEREFTO cl_event_handle.

START-OF-SELECTION.
CREATE OBJECT event_handler."创建事件处理对象
CALLSCREEN100.

MODULE status_0100 OUTPUT.
IF container_r ISINITIAL.
PERFORM create_tree.
ENDIF.
ENDMODULE.

MODULE user_command_0100 INPUT.
save_code = ok_code.
CLEAR ok_code.
CASE save_code.
WHEN'BACK'.
LEAVEPROGRAM.
ENDCASE.
ENDMODULE.

FORM create_tree.
"创建树所在父容器对象
CREATE OBJECT container_r EXPORTING container_name = container_name1.
"创建树对象.创建时需要传入父容器
CREATE OBJECT tree_r EXPORTING parent = container_r
"节点选择模式:单选
node_selection_mode = cl_gui_simple_tree=>node_sel_mode_single.
"子程序 make_data 负责从数据库中获取数据到树内表中
PERFORM get_data.
"将子程序 make_data 构造好的内表传递给树对象的 add_nodes 方式,构造节点
CALLMETHOD tree_r->add_nodes
EXPORTING
table_structure_name = 'MTREESNODE'
node_table           = node_table.
PERFORM register_event."注册节点双击事件
ENDFORM.

FORM make_node USINGvalue(nkey) TYPE string"节点名
value(nname) TYPE string"节点文本
value(pnode) TYPE string"父节点名,如果为空则为根节点
value(is_leaf) TYPE string."是否叶节点
CLEAR node1.
node1-node_key = nkey.
node1-text = nname.
node1-relatkey = pnode.

IF is_leaf = 'X'.
node1-relatship = cl_gui_simple_tree=>relat_last_child.
node1-isfolder = ''.
ELSE.
CLEAR node1-relatship.
node1-isfolder = 'X'.
ENDIF.
node1-hidden = ''.
node1-disabled = ''.
CLEAR: node1-exp_image,node1-expander,node1-n_image.
APPEND node1 TO node_table.
ENDFORM.

FORM register_event.
DATA: double_event TYPE cntl_simple_event,
eventlist TYPE cntl_simple_events.
double_event-eventid = cl_gui_simple_tree=>eventid_node_double_click.
double_event-appl_event = 'X'.
APPEND double_event TO eventlist.
"树所需要监听的事件列表
CALLMETHOD tree_r->set_registered_events
EXPORTING
events = eventlist.
"指定tree_r的事件处理方式为event_handler的handle_dbclick方法
SETHANDLER event_handler->handle_dbclick FOR tree_r.
ENDFORM.

FORM get_data.
DATA: s1 TYPE string,s2 TYPE string,s3 TYPE string.
DATA:wa1 TYPE scarr,wa2 TYPE spfli,wa3 TYPE sflight.
SELECT * FROM scarr INTO wa1 WHERE carrid = carrid.
s1 = wa1-carrname.
PERFORM  make_node USING s1 s1 ''''.
SELECT * FROM spfli INTO wa2 WHERE carrid = carrid.
CHECK wa2-cityfrom ISNOTINITIAL.
CHECK wa2-cityto ISNOTINITIAL.
CONCATENATE wa2-cityfrom '------' wa2-cityto INTO s2.
PERFORM make_node USING s2 s2 s1 ''.
SELECT * FROM sflight INTO wa3 WHERE carrid = carrid AND connid = wa2-connid.
s3 = wa3-fldate.
PERFORM make_node USING s3 s3 s2 'X'.
ENDSELECT.
ENDSELECT.
ENDSELECT.
ENDFORM.

ALV Grid Control

请参考《ALV.docx》文档中的“OO ALV->创建ALV”章节

多行文本编辑器

先创屏幕100,然后放一个名为EDITOR的自定义控件容器,用来存放多行文本编辑器:

image263

工具栏中新增保存按钮:

image264

 

在开发前,先讲讲长文本:

在以前,使用过READ_TEXT函数读取过各种各样的长文本,比如物料长文本、销售长文本等,在读取里时,一般需要传递长文本对象名、长文本ID、及长文本名称,READ_TEXT函数参数如下:

image265

与读取函数READ_TEXT对应的是写函数SAVE_TEXT,它是将长文本以固有的格式存储到相应的物理表中,其中有一个重要的入参HEADER,其类型参照了THEAD结构(实质上为STXH表结构),用于存储长文本相关的各种属性:

image266

image267

长文本最后都存储到了STXH、STXL两个物理表中。在使用READ_TEXT与SAVE_TEXT时,都需要指定长文本对象以及文本ID,这些是通过SE75来维护的:

image268

上面看到的物料长文本对象及文本ID,在物料维护时可以看到:

image269

SAP中的长文本都是以文本对象和文本ID分类别类存储到STXH、STXL两个物理表中,所以想将长字符串,比如这里编辑器中的文本以长文本形式存储到表中,则需要先定义对应的文本对象及文本ID,并且在定义文本对象时,可以指定长文本的格式:如每行的字符个数,这里我们创建需要在下面程序使用的文本对象Z001:

image270

image271

当运行后面程序后,文本编辑器中的文本会以长文本的形式存储到STXH与STXL两个物理表中,STXH表存储的为长文本相关头信息,STXL存储的是真正的数据,但长文本是以LRAW类型存储的,在屏幕上不能直接显示出来:

image272

image273

image274

最后程序运行结果如下:

image275

image276

REPORT  zjzj_text_editor.
DATA: editor_container TYPE REF TO cl_gui_custom_container,"容器
editor TYPE REF TO cl_gui_textedit."文本编辑器
CONSTANTS: c_line_length TYPE i VALUE 72."文本编辑器中每行最多允许的字符个数
TYPES: BEGIN OF st_text,"存放行文本类型
line TYPE c LENGTH c_line_length,
END OF st_text.
TYPES: tt_text TYPE STANDARD TABLE OF st_text."存放编辑器长文本表类型
DATA: it_lines TYPE STANDARD TABLE OF tline,
wa_thead LIKE thead."SAVE_TEXT函数的header参数所需表头数据
""""以下这些参数就是以前读取长文本函数 READ_TEXT 中常使用的参数
wa_thead-tdobject = 'Z001'."长文本对象
wa_thead-tdid = 'ZT01'."长文本ID
wa_thead-tdname = sy-repid."长文本名称:SAP里很多的业务长文本名所采用的拼接规则都不太一样,需要仔细分析,这里直接使用程序名
wa_thead-tdspras = sy-langu."长文本语言

CALL SCREEN 100.

MODULE status_0100 OUTPUT.
SET PF-STATUS 'T001'.
CREATE  OBJECT editor_container "创建容器对象
EXPORTING
container_name =  'EDITOR'.
CREATE OBJECT editor"创建文本编辑器
EXPORTING
parent = editor_container
"换行模式——0: OFF; 1: wrap a window border;窗口边缘换行 2: wrap at fixed position
wordwrap_mode = 2
"从第几个字符开始换行——position of wordwrap, only makes sense with wordwrap_mode=2
wordwrap_position = c_line_length
"eq 1: change wordwrap to linebreak; 0: preserve wordwraps
wordwrap_to_linebreak_mode = 1
max_number_chars = 720.

DATA texttable TYPE tt_text.
DATA ls_header TYPE thead."用来存储读取到的长文本相关属性信息
REFRESH it_lines.
"读取长文本
CALL FUNCTION 'READ_TEXT'
EXPORTING
client                  = sy-mandt
id                      = wa_thead-tdid
language                = sy-langu
name                    = wa_thead-tdname
object                  = wa_thead-tdobject
IMPORTING
header                  = ls_header
TABLES
lines                   = it_lines
EXCEPTIONS
id                      = 1
language                = 2
name                    = 3
not_found               = 4
object                  = 5
reference_check         = 6
wrong_access_to_archive = 7.
"如果是第一次运行本程序,则数据库表 STXH、STXL两个表中不会有数据
",此时READ_TEXT会抛出异常
IF sy-subrc <> 0.
MESSAGE '无此文本' TYPE 'I'.
ENDIF.
MESSAGE ls_header TYPE 'I'."将长文本相关属性信息打印到状态栏

"将从表中读取到的长文本转换为能在文本编辑器显示的文本
CALL FUNCTION 'CONVERT_ITF_TO_STREAM_TEXT'
EXPORTING
language    = sy-langu
TABLES
itf_text    = it_lines
text_stream = texttable.

"将内表数据赋值给文本编辑器
CALL METHOD editor->set_text_as_stream
EXPORTING
text = texttable.
ENDMODULE.

MODULE user_command_0100 INPUT.
CASE sy-ucomm.
WHEN 'SAVE'.
"从文本编辑器中读取文本到内表中
CALL METHOD editor->get_text_as_stream
IMPORTING
text = texttable.
"将文本编辑器中的长文本数据转换为附带信息的可存储到表中的标准格式的长文本
CALL FUNCTION 'CONVERT_STREAM_TO_ITF_TEXT'
EXPORTING
language    = sy-langu
TABLES
text_stream = texttable
itf_text    = it_lines.
"将内表数据通过函数转换为 LRAW 类型并存放到指定的物理表中
CALL FUNCTION 'SAVE_TEXT'
EXPORTING
client          = sy-mandt
header          = wa_thead
savemode_direct = 'X'
TABLES
lines           = it_lines.
ENDCASE.
ENDMODULE.

Call屏幕

image277

call screen 200 starting at 37 5
ending   at 87 22.

在Menu Painter中设计200屏幕时,Screen Type选择的是Modal dialog box,但此选项不是导致屏幕200本身以弹出对话框的形式显示,其意思与Call 选择屏幕时的选项AS WINDOW的使用相同。

屏幕是否以对话框的形式显示,是由starting at选项决定的,这与Calling选择屏幕中的选项是一样的

弹出确认对话框

image278

DATA: l_answer TYPE c.
CALL FUNCTION 'POPUP_TO_CONFIRM'
EXPORTING
*     TITLEBAR                    = ' '
*     DIAGNOSE_OBJECT             = ' '
text_question               = 'Confirm to import?'
text_button_1               = 'Yes'(001)
*     ICON_BUTTON_1               = ' '
text_button_2               = 'No'(002)
*     ICON_BUTTON_2               = ' '
*     DEFAULT_BUTTON              = '1'
*     DISPLAY_CANCEL_BUTTON       = 'X'
*     USERDEFINED_F1_HELP         = ' '
*     START_COLUMN                = 25
*     START_ROW                   = 6
*     POPUP_TYPE                  =
*     IV_QUICKINFO_BUTTON_1       = ' '
*     IV_QUICKINFO_BUTTON_2       = ' '
IMPORTING
answer                      = l_answer
*   TABLES
*     PARAMETER                   =
EXCEPTIONS
text_not_found              = 1
OTHERS                      = 2
.
CHECK l_answer = '1'.

RIPRO主题是一个优秀的主题,极致后台体验,无插件,集成会员系统
SapiBook » ABAP Dynpro-复合屏幕元素

1 评论

  1. Pingback: viagra online

发表评论