当事件被触发时,Flex会有三个时期来检查事件监听器,这些阶段是按照下面的顺序发生的。
Capturing
Targeting
Bubbling
在每一个阶段,这些节点都有机会来对该事件做出反映,例如,假设用户点击了一个在VBox容器中的Button控件,在capturing阶段,Flex检查Application对象和VBox对象来处理该事件,然后进入targeting阶段,Flex会触发Button上的监听器,最后在bubbling阶段,VBox对象和Application对象又一次会被检查,但是检查的顺序和capturing阶段的顺序正好相反,MouseEvent事件沿着父链向上冒泡,任何祖先都有机会来处理它,当事件冒泡时,event.target属性不变,一直是UItextField,而event.currentTarge属性的值在每一个level都会被赋值为当前处理该事件的对象,最终,当Button控件上的事件监听器来处理该事件时,currentTarget属性值会是Button,所以,你应该使用event.currentTarget属性,
<mx:Button label="OK" click="trace(event.currentTarget.label)"/>
在这个情况下,在button监听click的监听器中, event.currentTarget属性总是指向Button,而 event.target可能是Button也可能是Button中的UITextField,这取决于用户点击Button的什么地方。
在AS3中,可以在目标节点或处于事件流中的任何节点注册事件监听器,可是不是所有的事件都参与事件流中的这三个时期,有些个类型的事件直接被派发给目标节点而没有参与capturing阶段和bubbling阶段,所有的事件理论都可以被捕获到除非是从最上层的节点派发出去的,有一些其他的事件可能被派发给处在display list中的对象,比如一个事件被派发给了Scocket类,这些事件直接流向了目标节点,而没有参与capturing时期和bubbling时期,你也可以取消一个事件沿着事件流传递,即使这个事件原本可以被传播到其他的阶段,你也可以阻止它,通过把它的cancelable属性设置为true,当一个事件对象沿着display list移动的时候,捕获是从父节向子节点方向,而冒泡是从子节点向父节点方向,这个过程和继承层次无关,只有DisplayObject(可视的对象比如容器和控件)对象才能有这三个阶段。
鼠标和键盘事件都可以冒泡,任何事件都可以被捕获到,但是除非你显示的告诉DisplayObject对象来捕获事件,否则它们在捕获期是不会监听事件的,换句话说,捕获默认是disabled。当一个faceless(匿名的,无个性的)的事件派发者例如Validator,派发出一个事件,该事件只有目标期,因为没有一个可视的display list能够捕获到它或者冒泡通过。
About the target and currentTarget properties
target和currentTarget属性
每一个事件对象都有 target和currentTarget属性 ,这两个属性帮助你跟踪某个事件在事件传播过程的位置,target属性指代派发事件的事件源,currentTarget属性指代当前正在被检查事件监听函数的节点,当你处理一个鼠标事件时,比如,在某个组件中写了一个事件监听器来处监听鼠标单击事件,event.target属性不一定指那个组件,它经常是它的一个子组件,比如Button控件的UITextField子组件,UITextField它定义了button的label,当Flash Playe派发一个事件时,它是从最靠近鼠标的对象派发事件(好好体会一下这句话的意思),因为孩子控件在父控件之前,,这意味着Flash Player是从一个内部的子组件中派发出来事件,例如Button控件中的UITextField子控件,event.target属性指代派发出事件的对象,在本例中,event.target指代UITextField,而不是正在被监听的对象(大多数时候都在Button控件上监听click事件)。
Capturing phase
捕获时期
在捕获时期,Flex检查事件源的祖宗看哪个节点注册了该事件的事件监听器,Flex从祖宗的根节点开始沿着display list链向下寻找一直到事件源的直接祖宗,也就是事件源的父亲,多数情况,根祖宗是Stage对象,然后是SystemManager对象,然后是Application对象。
For example, if you have an application with a Panel container that contains a TitleWindow container, which in
turn contains a Button control, the structure appears as follows:
比如应用中的层次如下:
Application
Panel
TitleWindow
Button
If your listener is on the click event of the Button control, the following steps occur during the capturing phase
if capturing is enabled:
1 Check the Application container for click event listeners.
2 Check the Panel container for click event listeners.
3 Check the TitleWindow container for click event listeners.
During the capturing phase, Flex changes the value of the currentTarget property on the Event object to match
the current node whose listener is being called. The target property continues to refer to the dispatcher of the
event.
默认情况下,所有的container在捕获期是不监听事件的,因为usr_capture属性值默认为false,唯一能够让一个组件在捕获时期监听事件的方式就是调用addEventListener()方法,传递给use_caputure一个true的值。如下:
myPanel.addEventListener(MouseEvent.MOUSE_DOWN, clickHandler, true);
如果你在MXML中添加一个监听器的话,是不能给use_caputure这个属性传递true这个值过去
如果你把use_caputure属性值设为true的话,也就是事件通过捕获期间传播的话,该事件仍然可以冒泡,但是已经在捕获期间执行了的监听器将不会在冒泡期间对该事件做出反映,如果你想让该事件在捕获期和冒泡期都能行的通的话,你必须调用addEventListener()两次,一次是设置use_capture为true,一次是设置use_capture属性为false。
The capturing phase is very rarely used, and it can also be computationally intensive. By contrast, bubbling is
much more common.
Targeting phase
在目标时期, Flex只会调用事件派发者的本身的事件监听函数,处在display list中的其他节点的事件监听函数将不会被检查,event对象的currentTarget和target属性的值在这个时期是相同的。
Bubbling phase
冒泡时期
在冒泡时期,Flex为事件监听函数检查事件的祖先,Flex从事件派发者的父节点开始沿着display list向上找一直到根节点,这和捕获时期恰恰相反,
例如,如果有下面的结构的一个应用,
Application
Panel
TitleWindow
Button
如果监听器监听的是Button控件的click事件,bubbling可用的情况下会出现一下几步,
1、检查TitleWindow容器
2、检查Panel容器
3、检查Application容器。
一个事件只有当它的bubbles属性设置为true时才能冒泡,鼠标和键盘事件都可以冒泡,对于Flex派发的高级的事件很少冒泡,可以冒泡的事件包括change, click, doubleClick, keyDown, keyUp, mouseDown, 和mouseUp.检验一个事件是否可以冒泡,可以看Flex文档。
在冒泡阶段,Flex会改变event对象的currenTarget属性的值来匹配那些监听器将要被调用的节点,而target属性一直指向事件源,当Flex呼叫一个监听函数时,其实event对象实际上早已经被处在display list树中的深处的对象派发出来了,这个派发事件的原始对象就是target属性,而正在冒泡的对象是currentTarget属性,所以当你在你的事件监听函数中想引用current object的时候,应更多的使用currentTarget属性。你只可以为那些能派发出这个事件的对象注册该事件的事件监听函数,例如,
<?xml version="1.0" encoding="utf-8"?> <s:Application xmlns:fx="http://ns.adobe.com/mxml/2009" xmlns:s="library://ns.adobe.com/flex/spark" xmlns:mx="library://ns.adobe.com/flex/mx" minWidth="955" minHeight="600" initialize="initial()"> <fx:Script> <!--[CDATA[ import mx.controls.Alert; import mx.core.UITextField; public function initial():void{ // button.addEventListener(MouseEvent.CLICK,form1_mouseDownHandler,false); form.addEventListener(MouseEvent.CLICK,form1_mouseDownHandler); } protected function form1_mouseDownHandler(event:MouseEvent):void { Alert.show("haha"); } ]]--> </fx:Script> <fx:Declarations> <!-- 将非可视元素(例如服务、值对象)放在此处 --> </fx:Declarations> <mx:Panel id="panel"> <mx:Form id="form"> <!--<mx:Button id="button" label="click"/>--> </mx:Form> </mx:Panel> </s:Application>
你不能在Form容器注册一个事件监听器来监听单击事件,除非form中包含一个Button控件,因为form不能派发单击事件,form可以派发mouseDown事件,所以你可以在form标签中注册mouseDown事件监听器,然后这个button控件或者form接收到mouseDown事件,这个事件监听函数都会被触发,如果你把useCapture属性设置为true,也就是事件通过捕获方式传播,那么它将忽略冒泡的默认行为,不会冒泡,如果你想让事件在捕获期和冒泡都传递,你必须调用addEventListener()方法两次,一次是设置useCapture属性为true,一次是设置useCapture属性为false,
一个事件只会沿着display list树的父链向上冒泡,兄弟节点,比如同一个容器中的两个Button控件,不会干扰彼此的事件。
Detecting the event phase
You can determine what phase you are in by using the Event objectseventPhase property. This property contains
an integer that represents one of the following constants:
1 “ Capturing phase CAPTURING_PHASE)
2 “ Targeting phase AT_TARGET)
3 “ Bubbling phase BUBBLING_PHASE)
The following example displays the current phase and current targets ID
<?xml version="1.0"?> <!-- events/DisplayCurrentTargetInfo.mxml --> <mx:Application xmlns:mx="http://www.adobe.com/2006/mxml"> <mx:Script><!--[CDATA[ import mx.controls.Alert; private function showInfo(e:MouseEvent):void { Alert.show("Phase: " + e.eventPhase + "/n" + "Current Target: " + e.currentTarget.id); } ]]--></mx:Script> <mx:Button id="b1" label="Click Me" click="showInfo(event)" /> </mx:Application>
Stopping propagation
停止事件传播
在任何时期,都可以通过Event对象的 stopPropagation() 和stopImmediatePropagation()这两个方法来停止遍历display list,
你可以调用event的stopPropagation()方法或者stopImmediatePropagation()方法来停止事件在事件流中的移动,这两个方法的唯一区别就在于拥有事件监听函数的当前的节点是否执行该函数,stopPropagation()方法阻止事件对象移到到另一个节点上,但是允许当前节点的其他事件监听函数执行,而stopImmediatePropagation()方法不仅阻止事件从当前节点移动到另一个节点上,它还不允许当前节点的其他事件监听函数执行,下面的例子创建了一个TitleWindow容器和一个Panel容器,Panel容器包括TitleWindow,这两个容器都监听mouseDown事件。结果如果如果你不调用stopImmediatePropagation()方法的话,你在TitleWindow中单击一下的话,showAlert()方法将被执行两次,
<?xml version="1.0"?> <!-- events/StoppingPropagation.mxml --> <mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" initialize="init(event);"> <mx:Script><!--[CDATA[ import mx.controls.Alert; import flash.events.MouseEvent; import flash.events.Event; public function init(e:Event):void { p1.addEventListener(MouseEvent.MOUSE_DOWN, showAlert); tw1.addEventListener(MouseEvent.MOUSE_DOWN, showAlert); tw1.addEventListener(Event.CLOSE, closeWindow); p2.addEventListener(MouseEvent.MOUSE_DOWN, showAlertWithoutStoppingPropagation); tw2.addEventListener(MouseEvent.MOUSE_DOWN, showAlertWithoutStoppingPropagation); tw2.addEventListener(Event.CLOSE, closeWindow); } public function showAlert(e:Event):void { Alert.show("Alert!/n" + "Current Target: " + e.currentTarget + "/n" + "Phase: " + e.eventPhase); e.stopImmediatePropagation(); } public function showAlertWithoutStoppingPropagation(e:Event):void { Alert.show("Alert!/n" + "Current Target: " + e.currentTarget + "/n" + "Phase: " + e.eventPhase); } public function closeWindow(e:Event):void { p1.removeChild(tw1); } ]]--></mx:Script> <mx:Panel id="p1" title="Stops Propagation"> <mx:TitleWindow id="tw1" width="300" height="100" showCloseButton="true" title="Title Window 1" > <mx:Button label="Click Me"/> <mx:TextArea id="ta1"/> </mx:TitleWindow> </mx:Panel> <mx:Panel id="p2" title="Does Not Stop Propagation"> <mx:TitleWindow id="tw2" width="300" height="100" showCloseButton="true" title="Title Window 2" > <mx:Button label="Click Me"/> <mx:TextArea id="ta2"/> </mx:TitleWindow> </mx:Panel> </mx:Application>
Examples
In the following example, the parent containers click handler disables the target control after the target handles
the event. It shows that you can reuse the logic of a single listener (click the HBox container) for multiple events
(all the clicks).
<?xml version="1.0"?> <!-- events/NestedHandlers.mxml --> <mx:Application xmlns:mx="http://www.adobe.com/2006/mxml"> <mx:Script><!--[CDATA[ public function disableControl(event:MouseEvent):void { // Use this same logic for all events. event.currentTarget.enabled = false; } public function doSomething(event:MouseEvent):void { b1.label = "clicked"; ta1.text += "Something happened."; } public function doSomethingElse(event:MouseEvent):void { b2.label = "clicked"; ta1.text += "Something happened again."; Adobe Flex 3 Developer Guide } ]]--></mx:Script> <mx:HBox id="hb1" height="50" click="disableControl(event)"> <mx:Button id='b1' label="Click Me" click="doSomething(event)"/> <mx:Button id='b2' label="Click Me" click="doSomethingElse(event)"/> <mx:TextArea id="ta1"/> </mx:HBox> <mx:Button id="resetButton" label="Reset" click="hb1.enabled=true;b1.enabled=true;b2.enabled=true;b1.label='Click Me';b2.label='Click Me';" /> </mx:Application>
By having a single listener on a parent control instead of many listeners (one on each child control), you can reduce
your code size and make your applications more efficient. Reducing the number of calls to the
addEventListener() method potentially reduces application startup time and memory usage.
The following example registers an event handler for the Panel container, rather than registering a listener for each
link. All children of the Panel container inherit this event handler. Since Flex invokes the handler on a bubbled
event, you use the
target property rather than the currentTarget property. In this handler, the currentTarget
property would refer to the Panel control, whereas the
target property refers to the LinkButton control, which
has the label that you want.
<?xml version="1.0"?> <!-- events/SingleRegisterHandler.mxml --> <mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" creationComplete="createLinkHandler();"> <mx:Script><!--[CDATA[ private function linkHandler(event:MouseEvent):void { var url:URLRequest = new URLRequest("http://finance.google.com/finance?q=" + event.target.label); navigateToURL(url); } private function createLinkHandler():void { p1.addEventListener(MouseEvent.CLICK,linkHandler); } ]]--></mx:Script> <mx:Panel id="p1" title="Click on a stock ticker symbol"> <mx:LinkButton label="ADBE"/> <mx:LinkButton label="GE"/> <mx:LinkButton label="IBM"/> <mx:LinkButton label="INTC"/> </mx:Panel> </mx:Application>
Event priorities
You can register any number of event listeners with a single event. Flex registers event listeners in the order in
which the
addEventListener() methods are called. Flex then calls the listener functions when the event occurs
in the order in which they were registered. However, if you register some event listeners inline and some with the
addEventListener() method, the order in which the listeners are called for a single event can be unpredictable.
You can change the order in which Flex calls event listeners by using the
priority parameter of the
addEventListener() method. It is the fourth argument of the addEventListener() method.
Flex calls event listeners in priority order, from highest to lowest. The highest priority event is called first. In the
following example, Flex calls the
verifyInputData() method before the saveInputData() function. The
verifyInputData() method has the highest priority. The last method to be called is returnResult() because
the value of its
priority parameter is lowest.
<?xml version="1.0"?>
<!-- events/ShowEventPriorities.mxml -->
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" creationComplete="initApp()">
<mx:Script><![CDATA[
private function returnResult(e:Event):void {
ta1.text += "returnResult() method called last (priority 1)/n";
}
private function verifyInputData(e:Event):void {
ta1.text += "verifyInputData() method called first (priority 3)/n";
}
private function saveInputData(e:Event):void {
ta1.text += "saveInputData() method called second (priority 2)/n";
}
private function initApp():void {
b1.addEventListener(MouseEvent.CLICK, returnResult, false, 1);
b1.addEventListener(MouseEvent.CLICK, saveInputData, false, 2);
b1.addEventListener(MouseEvent.CLICK, verifyInputData, false, 3);
}
]]></mx:Script>
<mx:Button id="b1" label="Click Me"/>
<mx:TextArea id="ta1" height="200" width="300"/>
</mx:Application>
You can set the event priority to any valid integer, positive or negative. The default value is 0. If multiple listeners
have the same priority, Flex calls them in the order in which they were registered.
If you want to change the priority of an event listener once the event listener has already been defined, you must
remove the listener by calling the
removeEventListener() method. You add the event again with the new
priority.The priority parameter of the addEventListener() method is not an official part of the DOM Level 3 events
model. ActionScript 3.0 provides it so that programmers can be more flexible when organizing their event
listeners.
Even if you give a listener a higher priority than other listeners, there is no way to guarantee that the listener will
finish executing before the next listener is called. You should ensure that listeners do not rely on other listeners
completing execution before calling the next listener. It is important to understand that Flash Player does not
necessarily wait until the first event listener finishes processing before proceeding with the next one.
If your listeners do rely on each other, you can call one listener function from within another, or dispatch a new
event from within the first event listener. For more information on manually dispatching events, see Manually
dispatching events– on page 8.