Flex 的事件机制和绑定机制(转)

1. 最简单的事件

以下代码:

   1: xml version="1.0" encoding="utf-8"?>

   2: <mx:Application xmlns:mx="http://www.adobe.com/2006/mxml"

   3:                 layout="absolute">

   4:     <mx:Script>

   5:         [CDATA[

   6:             import flash.events.MouseEvent;

   7:             import mx.controls.Alert;

   8: 

   9:             private function btnClick(event:MouseEvent):void {

  10:                 Alert.show("Hello Event");

  11:             }

  12:         ]]>

  13:     mx:Script>

  14:     <mx:Button x="311.5"

  15:                y="215"

  16:                label="Button"

  17:                click="btnClick(event)"/>

  18: mx:Application>

效果很简单,当单击鼠标时弹出一个提示窗口,内容“Hello Event”。代码中,buttonclick设定了事件触发的时候执行btnClick方法中的代码。

其中,event对象就是这个组件分发的事件对象,即type“click”MouseEvent的一个实例。这个event对象包含了触发该事件时的各种信息,比如触发事件对象是哪个,监听对象是哪个,触发时鼠标点在哪里等等,不同的event类会包含不同的属性,比如KeyboardEvent包含了键盘点击了哪个键。

下面将会解释到,这个事件会在事件流的target阶段被触发。

2. 事件的传播

在官方的帮助手册里有提到,当事件触发之后,FLEX3个检测事件监听器的阶段, 3个阶段的发生的顺序如下:

1. 捕获 
2.
目标 
3.
上浮

在任意一个阶段, 节点们都有机会操作事件。

关于这三个阶段的描述,发现很多网上的版本说不清楚。帮助手册里原文是这样的:

l The first part of the event flow is called the capturing phase. This phase comprises all of the nodes from the root node to the parent of the target node. During this phase, Flash Player examines each node, starting with the root, to see if it has a listener registered to handle the event. If it does, Flash Player sets the appropriate values of the Event object and then calls that listener. Flash Player stops after it reaches the target node's parent and calls any listeners registered on the parent.

l The second part of the event flow, the targeting phase, consists solely of the target node. Flash Player sets the appropriate values on the Event object, checks the target node for registered event listeners, and then calls those listeners. For more information.

l The third part of the event flow, the bubbling phase, comprises all of the nodes from the target node's parent to the root node. Starting with the target node's parent, Flash Player sets the appropriate values on the Event object and then calls event listeners on each of these nodes. Flash Player stops after calling any listeners on the root node.

总结了一下,如下解释:

l 捕获阶段(capture) 
在这个阶段,Flash Player会去查找事件的触发源,它是通过根显示元素(root display object)逐层向下寻踪,直到找到事件的发起源头的父节点,如你在buttonclick后,Flash Player找到的事该button对象。在这个阶段监视器(listener)默认是不会收到任何消息的(默认只在targetbubble阶段收到消息)

l 目标执行阶段(target) 
这个阶段会调用事件发起者中的事件监听器。

l 冒泡阶段(bubble) 
最后的这个阶段其实就是第一个阶段的逆向过程。它就是在事件代码执行完毕后,从事件发起源头的父节点,通过子元素逐级向父元素发出该事件,一直到根元素。过程中,在节点处添加的listener会收到消息并且触发。前提是事件支持bubble阶段。

设置listener的方法为使用下面的function

   1: addEventListener(type:String, listener:Function,useCapture:Boolean = false, priority:int = 0, useWeakReference:Boolean = false)

l type:Event的类型,例如click事件type就是click等。

l Listener:响应事件的方法,即当捕获到事件时你要程序做什么。

l useCapture:默认为false。当设置为true时,事件会在捕获阶段就触发;设置为false时,事件则在冒泡阶段触发。因为默认是false,所以之前提到在捕获阶段,listener默认是不会收到任何消息的。如果想让事件既在捕获阶段触发又在上浮阶段触发,可以用两次addEventListener,一次把这个参数设成false,一次设成true,分别在这两个阶段触发。

l priority:优先级。

l useWeakReference:是否设置为弱引用。如果被设置为true,则表示告诉Flash Player,当直接引用这个监听器的对象数量为0时,就可以马上回收它。默认是false的话,还需要遍历这个引用可以到达的其它引用,确保不会发生循环引用时才回收资源。

拿本文第一章中的例子改造一下说明这三个阶段以及addEventListener的使用:

   1: xml version="1.0" encoding="utf-8"?>

   2: <mx:Application xmlns:mx="http://www.adobe.com/2006/mxml"

   3:                 layout="absolute"

   4:                 creationComplete="creationComplete()">

   5:     <mx:Script>

   6:         [CDATA[

   7:             import flash.events.MouseEvent;

   8:             import mx.controls.Alert;

   9: 

  10:             private function btnClick(event:MouseEvent):void {

  11:                 trace(event.eventPhase + ":Button:" + event.currentTarget.id + ":" + event.target.id);

  12:             }

  13: 

  14:             private function btnClick2(event:MouseEvent):void {

  15:                 trace(event.eventPhase + ":Application:" + event.currentTarget.id + ":" + event.target.id);

  16:             }

  17: 

  18:             private function btnClick3(event:MouseEvent):void {

  19:                 trace(event.eventPhase + ":VBox:" + event.currentTarget.id + ":" + event.target.id);

  20:             }

  21: 

  22:             private function btnClick4(event:MouseEvent):void {

  23:                 trace(event.eventPhase + ":AddButton:" + event.currentTarget.id + ":" + event.target.id);

  24:             }

  25: 

  26:             private function creationComplete():void {

  27:                 addEventListener(MouseEvent.CLICK, btnClick2, false);

  28:                 addEventListener(MouseEvent.CLICK, btnClick2, true);

  29:                 vb.addEventListener(MouseEvent.CLICK, btnClick3);

  30:                 tb.addEventListener(MouseEvent.CLICK, btnClick4);

  31:             }

  32:         ]]>

  33:     mx:Script>

  34:     <mx:VBox x="200"

  35:              y="147"

  36:              id="vb"

  37:              height="100%">

  38:         <mx:Button id="tb"

  39:                    label="Button"

  40:                    click="btnClick(event)"/>

  41:     mx:VBox>

  42: mx:Application>

为了解释事件的三个阶段,我把button再放到一个vbox里面。在Application加入两个listeneruserCapture分别设置为falsetrue,这样就可以在Application捕获到捕获阶段和上浮阶段的事件。然后在VBox设置一个listeneruserCapture属性使用默认的false,用于证明上浮阶段的顺序。再在Button上设置一个listener,证明Button上的listener没有参与捕获阶段和上浮阶段。

代码中用于检测事件阶段的EventeventPhase属性。这个属性分别用123表示Capture PhaseTarget PhaseBubble Phase

还有就是eventcurrentTargettarget属性。因为在事件流的过程中,event对象会被动态的在各个层次节点传递。currentTarget就是当前的节点对象,target则是事件分配源对象,这个例子里面target就是button

Debug跑一下以上代码,然后点击Button,控制台输出如下:

   1: 1:Application:HelloEvent:tb

   2: 2:Button:tb:tb

   3: 2:AddButton:tb:tb

   4: 3:VBox:vb:tb

   5: 3:Application:HelloEvent:tb

结果中可以看到上浮的过程是由内而外的,以及可以看到事件发出对象的监听只在target阶段被执行。

在事件的传播过程中,任何阶段中都可以使用以下两个函数停止事件传播:

   1: stopPropagation()

   2: stopImmediatePropagation()

这两个函数的共同特征就是,可以阻止事件继续传播到别的节点,而且都不影响对象和事件的关联关系。不同的地方是,stopPropagation() 要等到当前节点所有事件都完成了以后才生效,而stopImmediatePropagation()会马上生效。

3. 事件的分派

最终继承自EventDispatcher的对象都会含有dispatchEvent这个方法,他有一个参数,事件对象。事实上所有的事件都是通过这个函数分派出去的.dispathEvent(Event),我们当然也可以自己定义这个Event,然后用第二章中的各种方式去捕获自定义的Event,这个呆会会说到。

先看下面的代码:

   1: xml version="1.0" encoding="utf-8"?>

   2: <mx:Application xmlns:mx="http://www.adobe.com/2006/mxml"

   3:                 layout="absolute"

   4:                 creationComplete="creationComplete()">

   5:     <mx:Script>

   6:         [CDATA[

   7:             import flash.events.MouseEvent;

   8:             import mx.controls.Alert;

   9:             private function btnClick(event:MouseEvent):void {

  10:                 trace("Click:Button:" + event.currentTarget.id + ":" + event.target.id + ":" + event.eventPhase);

  11:                 tb.dispatchEvent(new MouseEvent(flash.events.MouseEvent.DOUBLE_CLICK, true));

  12:             }

  13:             private function btnDoubleClick(event:MouseEvent):void {

  14:                 trace("DoubleClick:Button:" + event.currentTarget.id + ":" + event.target.id + ":" + event.eventPhase);

  15:             }

  16:             private function doubleClickHandler(event:MouseEvent):void {

  17:                 trace("Application:doubleClick");

  18:             }

  19:             private function creationComplete():void {

  20:                 addEventListener(flash.events.MouseEvent.DOUBLE_CLICK, doubleClickHandler);

  21:             }

  22:         ]]>

  23:     mx:Script>

  24:     <mx:Button x="311"

  25:                y="180"

  26:                id="tb"

  27:                label="Button"

  28:                click="btnClick(event)"

  29:                doubleClick="btnDoubleClick(event)"/>

  30: mx:Application>

上面的程序,现在Application添加一个鼠标双击事件的监听,在Button注册一个click事件。在click响应的函数里面使用dispatchEvent,将一个鼠标双击事件由Button发出。控制台结果如下:

   1: Click:Button:tb:tb:2

   2: DoubleClick:Button:tb:tb:2

   3: Application:doubleClick

可以看到,单击这个Button的时候Application捕获到了双击事件。使用这个函数,我们就可以在自定义的控件中分派自定义的事件了。

4. Bindable绑定

看下面的一段代码:

   1: xml version="1.0" encoding="utf-8"?>

   2: <mx:Application xmlns:mx="http://www.adobe.com/2006/mxml"

   3:                 layout="absolute">

   4:     <mx:Script>

   5:         [CDATA[

   6:             [Bindable]

   7:             private var bgColor:uint = 0x000000;

   8: 

   9:             private function btnClick(event:MouseEvent):void {

  10:                 if (bgColor == 0x000000)

  11:                     bgColor = 0xFFFFFF;

  12:                 else

  13:                     bgColor = 0x000000;

  14:             }

  15:         ]]>

  16:     mx:Script>

  17:     <mx:Button x="311"

  18:                y="180"

  19:                id="tb"

  20:                label="Button"

  21:                click="btnClick(event)"/>

  22:     <mx:Panel x="219"

  23:               y="210"

  24:               width="250"

  25:               height="152"

  26:               id="pa"

  27:               borderColor="{bgColor}"

  28:               layout="absolute">

  29:     mx:Panel>

  30: mx:Application>

效果是点击一个按钮,就可以看到一个Panel的背景色不断的变换。手段仅仅是在按钮的单击事件中设置了一个变量的值。

不过能这样做的前提是gbColor这个变量声明了[Bindable]。这个声明是给编译器看的,告诉编译器这个变量是可绑定的,当这个变量的值发生变化的时候,会去更新与它绑定的属性并且更新组件。

绑定的实质就是事件。在被绑定的对象上增加了改变事件的监听,一旦某个被绑定对象改变后,就会分发一个“propertyChange”事件(默认的,可以改变成其它事件)。而在其它组件中,默认加入了对PropertyChangeEvent.PROPERTY_CHANGE事件的监听,当扑捉到该事件之后,则会去更新组件的属性并显示。

[Bindable]可用的地方有三处,类、变量、getter/setter

[Bindable]使用在类上的时候,相当于在这个类中的所有public属性(包括变量,getter/setter,普通方法)加上[Bindable]。由于方法上不能用[Bindable],这时候编译器会有警告。

setter/getter上面设置[Bindable]会比较匪夷所思一些。一对setter/getter只要在其中一个上面标上[Bindable]就可以,两个都标上的话编译器会警告重复。据说在复杂情况下getter的绑定会被编译器忽略:

Now keep in mind that there’s no way for the compiler to actually tell if the value of a property get function would be different if called, short of doing an extensive code flow analysis of the get function, identifying all the inputs that might be affecting the value of the get function (i.e., member fields, statics, globals that are used in the get function and in any methods, global functions, closures, etc) it might call, and setting up watchers on every one of those to trigger the binding when any of them change. That’s prohibitively difficult, and expensive to do. So the compiler doesn’t try. 
    Instead when you put [Bindable] on a get/set property, the compiler makes it bindable with a little creative rewriting that allows the framework to watch the get function, and dispatch a change event when the get function is triggered. This means that automatic bindable properties don’t work when the get function is computed from multiple values, or when you change its value by setting a backing field, rather than using the set function.
 
    It _also_ means that if you have no set function, we can pretty much guarantee that there’s no way automatically bindable get properties will be triggered. a read only propeerty is, to the compiler, completely opaque…at the moment, it has no idea where that value is coming from, and hence will never be able to ‘automatically’ trigger the binding.

不过我没有尝试成功。据说是编译器因为考虑到性能原因会把复杂的getter上的绑定去掉,这时候可以自己加上绑定关系:

   1: xml version="1.0" encoding="utf-8"?>

   2: <mx:Application xmlns:mx="http://www.adobe.com/2006/mxml"

   3:                 layout="absolute">

   4:     <mx:Script>

   5:         [CDATA[

   6:             import mx.controls.Alert;

   7:             [Bindable]

   8:             private var _bgColor:uint = 0xFFFFFF;

   9: 

  10:             [Bindable]

  11:             public function set bgColor(val:uint):void {

  12:                 _bgColor = val;

  13:                 this.dispatchEvent(new Event("event"));

  14:             }

  15: 

  16:             [Bindable("event")]

  17:             public function get bgColor():uint {

  18:                 return _bgColor;

  19:             }

  20: 

  21:             private function btnClick(event:MouseEvent):void {

  22:                 bgColor = bgColor + 10;

  23:             }

  24:         ]]>

  25:     mx:Script>

  26:     <mx:Button x="311"

  27:                y="180"

  28:                id="tb"

  29:                label="Button"

  30:                click="btnClick(event)"/>

  31:     <mx:Panel x="219"

  32:               y="210"

  33:               width="250"

  34:               height="152"

  35:               id="pa"

  36:               backgroundColor="{bgColor}"

  37:               layout="absolute">

  38:     mx:Panel>

  39: mx:Application>

像上面那样,在setter中抛出事件,让被绑定的getter执行一遍,效果是一样的。

5. 自定义事件

AS3中的自定义事件继承自flash.events.Event

   1: public class DrawingToolEvent extends Event {

   2:     public static const SELECTED:String = 'selected';

   3: 

   4:     public var drawingTool:DrawingTool;

   5:     public var mouseEvent:MouseEvent;

   6: 

   7:     public function DrawingToolEvent(drawingTool:DrawingTool, mouseEvent:MouseEvent, type:String, bubbles:Boolean = false) {

   8:         super(type, bubbles);

   9: 

  10:         this.drawingTool = drawingTool;

  11:         this.mouseEvent = mouseEvent;

  12:     }

  13: }

构造函数中执行一下父类的构造函数,传入必要的typebubbles参数。在使用这个自定义的Event的时候:

   1: public function clickHandler(event:MouseEvent):void {

   2:             isSelected = !isSelected;

   3:             var drawingToolEvent:DrawingToolEvent = new DrawingToolEvent(this, event, DrawingToolEvent.SELECTED);

   4:             dispatchEvent(drawingToolEvent);

   5:             invalidateDisplayList();

   6:         }

实例化一个自定义的Event,然后使用dispatchEvent分发。这个Event事件就会像其它普通的Event一样经历Flex事件的三个阶段被分发出去。可以使用

   1: addEventListener(DrawingToolEvent.SELECTED, drawingToolClickHandler)

在节点上捕获这个事件,也可以用第一章中的方法注册这个事件。不过对于这个自定义Event要使用第一章的方法,则要扩展一下组件,加入对这个事件的声明:

   1: [Event(name="selected", type = "com.hy.paperClasses.events.DrawingToolEvent")]

这样可以就像第一章一样,在mxml定义控件的时候注册事件了。

 

 

本文转载自http://blog.csdn.net/ivanmarkliu/archive/2009/07/08/4327570.aspx

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值