(1/7)背景
由于AT NEW field会判断field和它前面的所有字段,而ON CHANGE OF没有这个要求,所以后者就有了登上舞台的空间。
(2/7)正常情况
现在有一个内表GT_TAB,包括一个字段FIELD1,内容如下:
行FIELD1的值112132
LOOP GT_TAB时,ON CHANGE OF GT_TAB-FIELD1会在第一和第三行被触发。
(3/7)非正常情况
GT_TAB内容如下:
行FIELD1的值1121
没有疑问,第一次LOOP GT_TAB时,ON CHANGE OF GT_TAB-FIELD1会在第一行被触发。
可是,如果第二次LOOP呢?
答案是:不会触发ON CHANGE OF。
而且,即便你清空GT_TAB的内表或工作区再重新赋值,都没有用。
(4/7)示例说明
为此我做了一个示例,包含的功能如下:
(连续的两次循环时,内表的值都是无重复的,我们重点关注第二次循环是否触发ON CHANGE OF的情况)
操作内表是否触发构建新内表,首次循环全局内表
触发
第二次循环全局内表不触发清空内表后重新赋值并循环全局内表不触发清空工作区后重新循环全局内表不触发清空内表+工作区后重新赋值并循环全局内表不触发调用FORM循环局部内表局部内表触发
再次调用FORM循环局部内表局部内表不触发
换一个FORM2,同名局部内表局部内表触发
再次调用FORM2循环局部内表局部内表不触发
这是一个比较隐蔽的坑,因为在某些情况下,我们不能预知GT_TAB-FIELD1会不会一定都是多个值的。如果某一次循环的时候,我们判断的字段没有重复值,那么下次循环的时候,如果内表中的首条记录恰巧与我们上一次循环的值相同,ON CHANGE OF就不会被触发。
而且,不管是全局变量还是局部变量,都有这样的情况。
(5/7)示例代码
有兴趣的同学,可以自己修改代码以测试一下这样的情况:
1、首次循环,内表中是1 1两条数据;
2、再次循环,内表中是1 2 两条数据
REPORT ztest_abap_ll.
SELECTION-SCREEN PUSHBUTTON /10(40) clear1 USER-COMMAND clear1.
SELECTION-SCREEN SKIP 1.
SELECTION-SCREEN PUSHBUTTON /10(40) clear2 USER-COMMAND clear2.
SELECTION-SCREEN SKIP 1.
SELECTION-SCREEN PUSHBUTTON /10(40) create USER-COMMAND create.
SELECTION-SCREEN SKIP 1.
SELECTION-SCREEN PUSHBUTTON /10(40) loop1 USER-COMMAND loop1.
SELECTION-SCREEN SKIP 1.
SELECTION-SCREEN PUSHBUTTON /10(40) loop2 USER-COMMAND loop2.
SELECTION-SCREEN SKIP 1.
SELECTION-SCREEN PUSHBUTTON /10(40) loop3 USER-COMMAND loop3.
TYPES:BEGIN OF ty_tab,
field1 TYPE string,
END OF ty_tab,
tyt_tab TYPE TABLE OF ty_tab.
DATA: gt_tab TYPE tyt_tab WITH HEADER LINE,
g_msg TYPE string,
g_msgty TYPE c.
INITIALIZATION.
clear1 = '清空内表'.
clear2 = '清空工作区'.
create = '向内表中插入两条数据'.
loop1 = '循环全局内表'.
loop2 = '全局内表放入局部内表后再循环'.
loop3 = '全局内表放入另一个FORM的同名局部内表后再循环'.
AT SELECTION-SCREEN.
CASE sy-ucomm.
WHEN 'CLEAR1'.
CLEAR: gt_tab[].
WHEN 'CLEAR2'.
CLEAR: gt_tab.
WHEN 'CREATE'.
"插入两条数据
gt_tab-field1 = '1'.
APPEND gt_tab.
APPEND gt_tab.
WHEN 'LOOP1'.
PERFORM loop1.
WHEN 'LOOP2'.
PERFORM loop2.
WHEN 'LOOP3'.
PERFORM loop3.
ENDCASE.
FORM loop1.
LOOP AT gt_tab.
ON CHANGE OF gt_tab-field1.
g_msg = '当前索引' && sy-tabix && ',触发了ON CHANGE OF'.
g_msgty = 'S'.
ELSE.
g_msg = '当前索引' && sy-tabix && ',没有触发ON CHANGE OF'.
g_msgty = 'E'.
ENDON.
MESSAGE g_msg TYPE 'I' DISPLAY LIKE g_msgty.
ENDLOOP.
ENDFORM.
FORM loop2.
DATA: lt_tab TYPE tyt_tab WITH HEADER LINE.
lt_tab[] = gt_tab[].
LOOP AT lt_tab.
ON CHANGE OF lt_tab-field1.
g_msg = '当前索引' && sy-tabix && ',触发了ON CHANGE OF'.
g_msgty = 'S'.
ELSE.
g_msg = '当前索引' && sy-tabix && ',没有触发ON CHANGE OF'.
g_msgty = 'E'.
ENDON.
MESSAGE g_msg TYPE 'I' DISPLAY LIKE g_msgty.
ENDLOOP.
ENDFORM.
FORM loop3.
DATA: lt_tab TYPE tyt_tab WITH HEADER LINE.
lt_tab[] = gt_tab[].
LOOP AT lt_tab.
ON CHANGE OF lt_tab-field1.
g_msg = '当前索引' && sy-tabix && ',触发了ON CHANGE OF'.
g_msgty = 'S'.
ELSE.
g_msg = '当前索引' && sy-tabix && ',没有触发ON CHANGE OF'.
g_msgty = 'E'.
ENDON.
MESSAGE g_msg TYPE 'I' DISPLAY LIKE g_msgty.
ENDLOOP.
ENDFORM.
(6/7)原因分析
根据F1里的说明,可以看到对于ON CHANGE OF的事件,在系统里是有一个类似于帮助变量的东西在支撑的,而且这个帮助变量的生命周期是比我们的FORM、FUNCTION等代码块的周期更长的。
F1里面没有提到的是,它的生命周期与全局变量的生命周期谁更长。不过实践证明了,除非程序退出,否则这个帮助变量是不会消失的。
(7/7)如何规避
个人觉得,用ON CHANGE OF,不如自己定义一个变量,记录一下上次循环的值,当本次的值与上次不一致时,即形同ON CHANGE OF的事件。
如下:
DATA: l_field1 type string.
loop at gt_tab.
if l_field1 <> gt_tab-field1.
"形同ON CHANGE OF
else.
l_field1 = gt_tab-field1.
endif.
endloop.