一.引
很多新人對Flex的事件機制都不太熟悉,在使用過(guò)程中難免會(huì )出現各種問(wèn)題,這是一個(gè)非常普遍的問(wèn)題,為了更快更好的幫助大家,將介紹一下Flex中事件的各種機制和用法。
Flex的精髓之一就是事件和綁定機制,了解之后,能幫助大家更靈活的設計程序,也對新手上路有一定的幫助。
講解可能不太系統,也不全面,有很多沒(méi)有深入。如果高手看到后有疑問(wèn),歡迎指正。當然各位也可以提出自己的看法,或者經(jīng)驗分享,謝謝。
二.事件機制介紹
1. 什么是事件機制
事件可以看作是一種觸發(fā)機制,當滿(mǎn)足了一定的條件后,會(huì )觸發(fā)這個(gè)事件。比如MouseEvent就是指的當鼠標進(jìn)行操作之后觸發(fā)的一系列的事件。很多控件中都有click事件,這個(gè)事件就是一個(gè)MouseEvent的實(shí)例,當點(diǎn)擊鼠標后,系統會(huì )自動(dòng)拋出一個(gè)名稱(chēng)為click的MouseEvent事件(這種方法我們將在后面介紹到)。如果此時(shí)在click上注冊一個(gè)方法,那么觸發(fā)該事件時(shí)就會(huì )執行這個(gè)方法。
大致示意圖
該示意圖對應的Flex主應用的mxml代碼
<mx:Script>
<![CDATA[
import mx.controls.Alert;
private function clickHandler(e:MouseEvent){
Alert.show(e.currentTarget.toString());
}
]]>
</mx:Script>
<mx:Button id="testBtn" click="clickHandler(event)" label="測試">
</mx:Button> 在我們寫(xiě)代碼時(shí),編輯器的代碼補全提示列表中,有很多不同的圖標,如圖
那些帶有閃電的就是事件,三個(gè)小塊的就是樣式,空心圓圈的是屬性,實(shí)心圓點(diǎn)的是公有方法,還有一個(gè)是效果。
我們能在這個(gè)列表中看到的事件,我把它稱(chēng)之為事件注冊通道。(官方仍然稱(chēng)它為事件,但是它又和普通的事件含義不同。關(guān)于事件注冊通道會(huì )再下面講述到)
2. 事件注冊通道
上面說(shuō)到了,這些通道是只能在mxml的代碼提示中可以看到的,他的作用就是給mxml組件提供 事件觸發(fā)時(shí)所執行的方法的注冊通道,而且能在代碼提示中可見(jiàn),這樣給組件提供了很大的抽象的好處,我們可以很清楚的告訴組件的使用者,組件里包含哪些事件給你調用。
為什么把他區別對待?除了代碼提示外,他還有一些實(shí)現上的不同。
Button的click事件是繼承自核心類(lèi)InteractiveObject,遺憾我們看不到他的源碼,但是說(shuō)明了“事件注冊通道”是可以繼承的。
我們會(huì )在自定義事件中講述到如何聲明“事件注冊通道”。
3. 事件觸發(fā)方法
注冊通道中如果填入了函數,那么就代表觸發(fā)該事件時(shí),會(huì )執行這個(gè)方法。
click="clickHandler(event)"
我們看到這個(gè)方法有一個(gè)event對象作為參數傳入,新人可能會(huì )問(wèn)到,這個(gè)event對象哪里來(lái)的?我也沒(méi)聲明這個(gè)變量啊。他實(shí)際上是注冊通道傳給他的,默認變量名就是event。我們如果想在事件觸發(fā)時(shí)傳其他的參數,可以通過(guò)自定的事件對象來(lái)實(shí)現。
這個(gè)對象就是這個(gè)組件分發(fā)的事件對象,即type為“click”的MouseEvent的一個(gè)實(shí)例。
這個(gè)event對象包含了觸發(fā)該事件時(shí)的各種信息,比如觸發(fā)事件對象是哪個(gè),監聽(tīng)對象是哪個(gè),觸發(fā)時(shí)鼠標點(diǎn)在哪里等等,不同的event類(lèi)會(huì )包含不同的屬性,比如KeyboardEvent包含了鍵盤(pán)點(diǎn)擊了哪個(gè)鍵。
我們也可以通過(guò)自定義一個(gè)事件類(lèi),來(lái)傳遞我們自己想要的各種信息。(這在后面將介紹到)
4. 事件分發(fā)(重點(diǎn)了)最終繼承自EventDispatcher的對象都會(huì )含有dispatchEvent這個(gè)方法,他有一個(gè)參數,事件對象。
之前說(shuō)到的事件注冊通道,他只是一個(gè)通道,實(shí)際上事件是由這個(gè)方法來(lái)分發(fā)出去的,通道只是一個(gè)管道而已。
他的作用就是分發(fā)一個(gè)事件對象,他的分發(fā)是沒(méi)有目的的,一種廣播形式的,Flex的事件監聽(tīng)線(xiàn)程會(huì )接收到各種各樣的事件(我們稱(chēng)之為捕獲事件,這在后面會(huì )介紹到),那么哪種才是你要的事件,標識就通過(guò)事件的type屬性來(lái)區分。
1)事件對象
在分發(fā)事件時(shí),將會(huì )分發(fā)一個(gè)事件對象出去。不管是那個(gè)事件類(lèi),都是繼承自flash.events.Event對象的,他包含一些比較重要的屬性,type和bubbles。
type是事件的類(lèi)型,事件監聽(tīng)通過(guò)這個(gè)參數來(lái)識別是否是自己所監聽(tīng)的事件。
bubbles是個(gè)布爾值,決定了該對象是否會(huì )向上傳遞。默認是false。什么意思呢?畫(huà)個(gè)圖就明白了。
比如說(shuō),當button組件分發(fā)click事件對象時(shí),設置的bubbles為false,那么他的分發(fā)是這樣的
示意代碼
dispatchEvent(new MouseEvent( “click” , false ));
事件對象無(wú)法跨越組件本身,當然,除了之前講到的注冊通道(這樣就很形象了吧)
因此,如果沒(méi)有注冊通道,在Flex主應用中,就無(wú)法捕獲到這個(gè)button組件分發(fā)出的事件。
如果我們將Bubbles設為true,他看起來(lái)就是這樣
dispatchEvent(new MouseEvent( “click” , true ));
可以看到,這個(gè)事件可以跨過(guò)組件本身,到達Flex主應用里。不止這樣,在幫助手冊中明確說(shuō)到,如果在傳遞過(guò)程中間一直沒(méi)有被捕獲的話(huà),這個(gè)事件會(huì )逐層上傳,直到最終的stage,那時(shí)如果還沒(méi)被捕獲,這個(gè)事件就會(huì )被銷(xiāo)毀掉。
這樣一來(lái),即使我們沒(méi)有click的事件通道,只要我們在Flex主應用中添加事件監聽(tīng)器(addEventListener)那么我們就可以獲得到這個(gè)分發(fā)出的click事件了。
那么,注冊通道不是沒(méi)用了嗎?不是,之前說(shuō)到過(guò),注冊通道是現式的,可見(jiàn)的,因此如果你的組件要給其他人使用,那么就非常一目了然,而不必知道你源碼中究竟分發(fā)了什么事件。但是,不要監聽(tīng)和注冊同一個(gè)事件,這樣會(huì )重復執行的。(后面將講到)
5. 事件監聽(tīng)
在分發(fā)中,我們講到,如果不是通過(guò)注冊通道來(lái)調用觸發(fā)事件,那么我們是需要一個(gè)監聽(tīng)來(lái)捕捉的。如何捕捉到分發(fā)出的事件,就是通過(guò)事件的type值。
比如:
<mx:Application xmlns:mx=http://www.adobe.com/2006/mxml layout="absolute" xmlns:comp
creationComplete='init()'
>
<mx:Script>
<![CDATA[
private function init(){
testBtn.addEventListener(“click”, clickHandler);
}
Flex的事件中都提供了一些靜態(tài)常量,讓我們調用,避免我們打錯了。因此這句話(huà)可以這么寫(xiě)
testBtn.addEventListener(MouseEvent.CLICK,clickHandler);
我們看到,監聽(tīng)的回調方法中沒(méi)有傳遞參數,是的,這和通道的寫(xiě)法有些不同,這里的回調方法(即clickHandler)只是個(gè)引用,并不是代表方法的執行,他的含義是,告訴eventLinstener,如果捕捉到click事件,那么就去找clickHandler,并執行它,event對象參數在執行時(shí)動(dòng)態(tài)的傳遞。(如果熟悉ajax的朋友這里應該很容易懂了)
他作用起來(lái)就是這樣
如果你又注冊了click的事件通道,那么這兩個(gè)都會(huì )生效,顯然這是多余的。
6. 關(guān)于異步和執行順序
以前的說(shuō)法有誤,as里是不存在線(xiàn)程概念的,在遠程請求時(shí),結果事件、錯誤事件都是異步的。如果你需要處理結果,需要利用監聽(tīng),并在回調中獲取你的遠程數據。
而在處理本地事件時(shí),他們仍然是同步的。(謝謝ltian 的指正)異步示意圖
上圖可以看出,回調方法執行的順序甚至還不如dispatchEvent之后的方法。如果接下來(lái)的方法依賴(lài)于事件回調,那么把接下來(lái)的方法寫(xiě)到回調方法中去
三.綁定機制
在我們了解了事件機制后,那么理解綁定就不難了。綁定其實(shí)也是事件機制的運用
1. 什么是綁定
綁定的原理就是事件,在被綁定的對象上增加了改變事件的監聽(tīng),一旦某個(gè)被綁定對象改變后,就會(huì )分發(fā)一個(gè)“propertyChange”事件(默認的,也可以改變成自己定義的事件),在其他組件中,會(huì )有propertyChange的事件監聽(tīng),當捕捉到該事件后,則會(huì )去更新組件的屬性并顯示。
綁定的作用在于,將Flex中的變量、類(lèi)、方法等與組件的值進(jìn)行綁定。例如,一個(gè)變量如果被綁定后,那么引用該變量的組件的相關(guān)屬性也會(huì )發(fā)生改變。我們用一個(gè)實(shí)例來(lái)表示
<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx=http://www.adobe.com/2006/mxml layout="absolute" xmlns:comp
>
<mx:Script>
<![CDATA[
import mx.controls.Alert;
[Bindable]
private var isSelected:Boolean;
private function clickHandler(e:MouseEvent){
//Alert.show(e.currentTarget.toString());
isSelected=isSelected?false:true; //這句話(huà)的意思是如果isSelected為true,改變它為false,如果它為false,改變它為true;
Alert.show(isSelected.toString());
}
]]>
</mx:Script>
<mx:Button id="testBtn" click="clickHandler(event)" label="測試" />
<mx:CheckBox x="60" selected="{isSelected}" />
</mx:Application>
上述程序的效果就是,當點(diǎn)擊button時(shí),button不是直接改變checkbox的選中狀態(tài),而是改變isSelected這個(gè)變量,由于isSelected是被綁定了的,那么會(huì )關(guān)聯(lián)的改變CheckBox的選中狀態(tài)。
這樣看起來(lái)有些多此一舉,完全可以直接改變checkbox的selected屬性,我只是為了演示一下效果。如果說(shuō)你的checkbox是動(dòng)態(tài)構造的上百個(gè),你不會(huì )去一個(gè)個(gè)的改變他吧。
因此,我們多數會(huì )將一個(gè)數據源進(jìn)行綁定聲明,這樣引用了這個(gè)數據源的控件,比如datagrid,在數據源發(fā)生了改變時(shí),即使你不重新設置dataProvider,列表的數據也會(huì )刷新。當然,還有很多應用等待你去嘗試。
如果這個(gè)代碼中取消了[Bindable]的聲明,會(huì )怎么樣?isSelected不會(huì )改變了嗎?
isSelected會(huì )改變,我們alert出來(lái)的結果也會(huì )顯示結果改變了,但是checkbox的選擇狀態(tài)不會(huì )改變,因為當一個(gè)組件由創(chuàng )建到最終顯示出來(lái)時(shí)是經(jīng)過(guò)很多方法的,比如addChild,commitProperties,updateDisplayList等,updataDisplayList則是類(lèi)似刷新顯示效果一樣的方法。
僅僅改變屬性,而不去更新顯示效果那么組件不會(huì )因為屬性的改變而發(fā)生任何變化。
綁定的原理也是利用的事件分發(fā)。更復雜的綁定有待你去自己發(fā)現了
四. 自定義事件的分發(fā)
這部分就不長(cháng)篇大論了,因為各位應該已經(jīng)掌握了事件的原理,因此貼出演示源碼,并進(jìn)行些簡(jiǎn)單的解釋。
1. 自定義事件 components/MyEventTest.as
package components
{
import mx.events.FlexEvent;
public class MyEventTest extends FlexEvent
{
public static const ONCHANGE:String = "onChange";
public var eventInfo:String; //自定義的事件信息
public function MyEventTest(s:String){
super(s); //如果在構造時(shí)不設bubbles,默認是false,也就是不能傳遞的。
eventInfo="這個(gè)事件是:"+s;
}
}
}
2. 自定義組件 components/ComponentForEvent.as
package components
{
import flash.events.EventDispatcher;
//這個(gè)就是聲明事件注冊通道的方法了。name是事件對應的名稱(chēng),也就是之前提到的type。Type是該事件的類(lèi)
[Event(name="onChange", type="components.MyEventTest")]
public class ComponentForEvent extends EventDispatcher
{
private var name:String;
public function changeName(newName:String){
this.name=newName;
dispatchEvent(new MyEventTest(MyEventTest.ONCHANGE) );
}
}
}
3. App.mxml
<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute" xmlns:comp
>
<mx:Script>
<![CDATA[
import mx.controls.Alert;
private function changeName(){
cfe.changeName("新名稱(chēng)");
}
]]>
</mx:Script>
<mx:Button id="testBtn" click=" changeName ()" label="測試" />
<components:ComponentForEvent
id="cfe" />
</mx:Application>