下面是在自己在运用DropDownList时,DropDownList.set selectedItem()方法触发事件的问题
需求很简单,初始化UI时,从数据库查出“生词本”放入DropDownList;选择不同的“生词本”,则再查询出这个生词本里的生词。
但我有个想法:如果用户选择了某个生词本后,查询过程中出了意外未能成功返回所需数据,则DropDownList的选项不改变。结果我在实现这个想法的过程中,遇到了麻烦。
代码如下:
在mxml文件中,我有一个DropDownList,表示“生词本”:
上边的controller是这个UI的控制类,其 handleDdlChange方法如下:
我的想法是,在DropDownList changing 时,先prevent掉他的默认改变,并往后台递交一个查询,这个查询在dataList中封装好了,是一个RemoteObject call,然后根据查询的结果,用编程方式设置DropDownList的selectedItem(见函数①、②)。
本来觉得应该挺好,但是一运行,发生了问题.... 函数①中的最后一句代码"view.ddlBook.selectedItem=selObj"竟然触发了changing事件,于是,handleDdlChange方法被调用,于是...我的程序死循环了,1秒之内我的后台收到了几百次查询。
我原本以为编程方式设置DropDownList的selectedItem时,是不会触发changing 事件的,不过它确实发生了。跟踪set selectedItem()方法,发现它直接把逻辑代理给了下面这个函数,我们称之为函数③:
首先,函数③的最后一句,会开始这样的后续流程:invalidateProperties() --> commitProperties --> commitSelection()
通过在函数③设置断点可以发现,当用户在UI中点击选项时,List.as的默认鼠标事件处理函数item_mouseDownHandler也会调用函数③,跟set selectedItem()所不同的是,List.as里边调用时,第二个参数dispatchChangeEvent传入的是true,set selectedItem里边调用时,是false。
从函数③的第二个if里看到,传入的dispatchChangeEvent参数会影响"dispatchChangeAfterSelection",这是DropDownList基类ListBase中的一个boolean状态,此状态将会影响commitSelection()的行为
从目前所得到的意思来看:Flex源代码默认DropDownList只有通过UI选择Item时才会触发IndexChangeEvent事件,而编程设置Item时则不会触发。然而仔细看代码后续并继续跟踪,会有新的发现。
commitSelection方法里的注释很详细,把这个方法分为了4个step,来到step2有:
那么可以看到,这就是派发IndexChangeEvent的地方。在"dispatchChangeAfterSelection"为true时,也就是用户用鼠标点击选项后,commitSelection()会dispatch出IndexChangeEvent()。
从以下代码:
if (!dispatchEvent(e))
{
// The event was cancelled. Cancel the selection change and return.
_proposedSelectedIndex = NO_PROPOSED_SELECTION;
return false;
}
可以看出:
如果我们在changing事件的处理函数中调用了event.preventDefault()(这正是我在自己的handleDdlChange()方法中所做的事情),if判断会生效,if中的语句会执行,commitSelection()方法直接返回,
但此时"dispatchChangeAfterSelection"仍为true,后续代码也没有任何地方修改了"dispatchChangeAfterSelection"的值。
这之后,我通过编码的方式设置了set selectedItem(),又来到了函数③,很不幸,这时候,根据它的第二个if,"dispatchChangeAfterSelection"为true,那么,随后在commitSelection的Step2,IndexChangeEvent会再次触发,程序开始死循环。
那是否可以认为:如果在DropDownList的changing事件的handler里调用了event.preventDefault(),再调用DropDownList.set selectedItem()会导致changing事件被dispatch?答案是Yes。
但是,是不是我在changing事件的handler里调用了event.preventDefault(),之后又编程调用了set selectedIndex/Item就会死循环呢?答案是No!
我们做个简单的例子:
打几个断点可以看到,testddl.selectedIndex/Item时,invalidateProperties()会直接返回,ListBase的commitSelection()方法根本没被调用。又来看看这个方法invalidateProperties方法:
由于invalidatePropertiesFlag的控制,这个invalidateProperties()方法在整个事件处理流程中只会被调用一次。并且是在最外层。这应该是Flex的一种防止死循环的机制。
那为何我的方法就死循环了呢?我想原因是我在事件处理流程中使用了远程方法,方法①/②的运行是在远程方法的回调流程中,而不是与方法③在同一个事件处理流程中。所以,现在死循环的原因就清楚了,它有两个前提:
1.我在changing处理函数中调用了event.preventDefault(),间接将ListBase的dispatchChangeAfterSelection设为了true,使得当commitSelection()方法被调用时,会dispatch changing Event。
2.我在changing处理函数中使用了远程调用,在其回调函数中set selectedIndex,这样使得set selectedIndex的这个在changing事件处理流程完全结束后才发生,而不是和它在同一个流程中,使得commitSelection()必然被触发。
于是,
橘黄色表示事件处理
蓝色表示远程查询与回调
用户UI选择Item --> myChangingHandler [ event.preventDefault()启动前提1, 使用remoteCall启动前提2] --> 远程调用... --> 回调 函数①/② --> setSelectedIndex() --> invalidateProperties() -- 前提2 --> commitSelection() -- 前提1 --> dispatch Changing Event --> myChangingHandler --> 重复的过程...