| Flex 2 開発ガイド > Flex プログラミング言語の使用 > イベントの使用 > イベントの伝播 | |||
イベントがトリガされると、Flex は 3 つの段階でイベントリスナーが存在するかどうかを確認します。これらの段階の順序は次のようになっています。
各段階において、ノードはイベントに応答する機会を得ます。たとえば、ユーザーが、VBox コンテナ内の Button コントロールをクリックしたとします。キャプチャ段階では、ルートノードである Application オブジェクトと VBox で、イベントを処理するリスナーがあるかどうかが確認されます。次のターゲット段階では、Button のリスナーがトリガされます。バブリング段階では、キャプチャ段階とは逆の VBox が先、Application オブジェクトが後という順序で、イベントを処理する機会が再び与えられます。
ActionScript 3.0 では、ターゲットノードまたはイベントフローの任意のノードで、イベントリスナーを登録することができます。ただし、すべてのイベントが、イベントフローの 3 つの段階すべてで処理されるわけではありません。一部のタイプのイベントはターゲットノードに直接送出されます。この場合、キャプチャ段階とバブリング段階はスキップされます。それが最上位ノードから送出されたものでない限り、どのイベントもキャプチャできます。
他のイベントでは、表示リストにないオブジェクトが対象になることがあります。たとえば、Socket クラスのインスタンスに送出されたイベントなどです。これらのイベントオブジェクトは、キャプチャ段階またはバブリング段階を経ずに、ターゲットノードに直接フローします。また、イベントがイベントモデルにフローする際に、イベントをキャンセルすることもできるので、他の段階に進む予定のイベントを停止することができます。これは cancelable プロパティが true に設定されている場合のみ可能です。
キャプチャおよびバブリングは、Event オブジェクトが表示リストのノード間を移動する際に実行されます。親から子に移動する場合にはキャプチャが、子から親に移動する場合にはバブリングが実行されます。この処理は継承階層とは関係ありません。ターゲット段階に加えて、キャプチャ段階とバブリング段階も経ることができるのは、DisplayObject オブジェクト (コンテナやコントロールなどのビジュアルオブジェクト) のみです。
マウスイベントおよびキーボードイベントは、バブリング段階でのみ処理されます。キャプチャはどのイベントでも可能ですが、明示的に指定しない限り、DisplayObject オブジェクトはキャプチャ段階ではイベントをリッスンしません。つまり、デフォルトではキャプチャは無効になっています。
Validator のようなフェイスレスイベントディスパッチャによってイベントが送出されると、ターゲット段階の処理のみが実行されます。これは、Event オブジェクトにキャプチャおよびバブリング対象のビジュアル表示リストがないためです。
どの Event オブジェクトにも、Event オブジェクトが伝播プロセスのどの段階にあるかを追跡するための target プロパティと currentTarget プロパティがあります。target プロパティは、イベントのディスパッチャを参照します。currentTarget プロパティは、イベントリスナーが存在しているかどうかが現在検査されているノードを参照します。
あるコンポーネントでリスナーを記述し、MouseEvent.CLICK などのマウスイベントを処理する場合、event.target プロパティは必ずしもそのコンポーネントを参照しません。ほとんどの場合は、ラベルを定義するサブコンポーネント (Button コントロールの UITextField など) を参照します。
Flash Player は、マウスポインタの下にあるオブジェクトのうち、一番上にあるものからイベントを送出します。子は親の前面にあるため、Button の UITextField など、内部サブコンポーネントからイベントを送出することもあります。
event.target プロパティは、監視されているオブジェクト (ほとんどのアプリケーションでは Button コントロールの click イベントを監視しています) ではなく、イベントを送出したオブジェクト (この場合は UITextField) に設定されます。
MouseEvent イベントは親チェーンをバブルアップし、任意の祖先で処理できます。イベントがバブルしても、event.target プロパティの値は変わりません (UITextField のまま)。ただし、event.currentTarget プロパティの値は、各レベルで、イベントを処理する祖先に設定されます。最終的には、currentTarget が Button になり、Button コントロールのイベントリスナーがイベントを処理します。このため、event.target プロパティではなく、次のように event.currentTarget プロパティを使用する必要があります。
<mx:Button label="OK" click="trace(event.currentTarget.label)"/>
この場合、Button イベントの click イベントリスナーでは、event.currentTarget プロパティは常に Button を参照します。それに対して event.target は、ユーザーが Button コントロールのどこをクリックしたかによって、Button またはその UITextField になります。
キャプチャ段階では、表示リスト内のイベントの祖先を検査し、イベントのリスナーとして登録されているかどうかを確認します。検査は表示リスト内のルート祖先から始まり、ターゲットの直接の祖先まで続きます。ほとんどの場合、ルート祖先はステージで、その下が SystemManager、さらにその下が Application オブジェクトです。
たとえば、Panel コンテナを持つアプリケーションがあり、Panel コンテナの中に TitleWindow コンテナ、さらに TitleWindow コンテナの中に Button コントロールが含まれている場合、その構造は次のようになります。
Application
Panel
TitleWindow
Button
Button コントロールの click イベントに対するリスナーが存在し、キャプチャが有効な場合、キャプチャ段階では次の手順が実行されます。
キャプチャ段階では、現在リスナーが呼び出されているノードに合わせて Event オブジェクトの currentTarget プロパティの値が変更されます。target プロパティは、引き続きイベントのディスパッチャを参照します。
デフォルトでは、コンテナはキャプチャ段階で受け取りません。use_capture パラメータのデフォルト値は false です。キャプチャ段階でリスナーを追加するには、addEventListener() メソッドを呼び出す際に、use_capture パラメータを true に設定します。次に例を示します。
myPanel.addEventListener(MouseEvent.MOUSE_DOWN, clickHandler, true);
MXML タグを使用してインラインでイベントリスナーを追加すると、このパラメータは false に設定されます。この値はオーバーライドできません。
use_capture パラメータを true に設定した場合、つまりイベントがキャプチャ段階で伝播する場合でも、そのイベントはバブル可能ですが、キャプチャ段階のリスナーはそれに応答しません。キャプチャ段階とバブリング段階の両方でイベントを処理する場合は、addEventListener() を 2 回、つまり use_capture を true に設定して 1 回と、false に設定して 1 回呼び出す必要があります。
キャプチャ段階が使用されることはほとんどありません。またキャプチャ段階を使用すると、計算処理の負荷が大きくなります。キャプチャとは逆に、バブリングは頻繁に実行されます。
ターゲット段階では、イベントディスパッチャのリスナーが呼び出されます。表示リスト上の他のノードでは、イベントリスナーの有無が確認されません。ターゲット段階では、Event オブジェクトの currentTarget プロパティと target プロパティの値は同じです。
バブリング段階では、イベントの祖先を検査し、イベントリスナーが存在するかどうかを確認します。検査はディスパッチャの直接の祖先から始まり、表示リストをルート祖先までさかのぼります。これはキャプチャ段階の逆です。
たとえば、Panel コンテナを持つアプリケーションがあり、Panel コンテナの中に TitleWindow コンテナ、さらに TitleWindow コンテナの中に Button コントロールが含まれている場合、その構造は次のようになります。
Application
Panel
TitleWindow
Button
Button コントロールの click イベントに対するリスナーが存在し、バブリングが有効な場合、バブリング段階では次の手順が実行されます。
イベントがバブルするのは、イベントの bubbles プロパティが true に設定されている場合のみです。バブルするイベントとしては、マウスイベントとキーボードイベントがあります。一般に、Flex によって送出される高レベルのイベントはバブルしません。バブルできるイベントには change、click、doubleClick、keyDown、keyUp、mouseDown、mouseUp などがあります。特定のイベントがバブルするかどうかを確認するには、『Adobe Flex 2 リファレンスガイド』のイベントの項目を参照してください。
バブリング段階では、現在リスナーが呼び出されているノードに合わせて Event オブジェクトの currentTarget プロパティの値が変更されます。target プロパティは、引き続きイベントのディスパッチャを参照します。
イベントリスナーが呼び出されるとき、実際には表示リストの下位のオブジェクトによって Event オブジェクトが送出される場合があります。イベントを送出したオブジェクトそのものが target となります。イベントが現在バブルしているオブジェクトが currentTarget になります。そのため、イベントリスナーで現在のオブジェクトを参照するときは、一般に、target プロパティではなく currentTarget プロパティを使用します。
イベントリスナーは、イベントを送出するオブジェクトにのみ登録できます。たとえば、Button コントロールがその中に含まれていたとしても、click イベントをリッスンするために Form コンテナを登録することはできません。Form コンテナは click イベントを送出しないためです。ただし、Form コンテナは mouseDown イベントを送出するため、Form コンテナタグで mouseDown イベントリスナーを追加することは可能です。こうすると、Button コントロールまたは Form コンテナが mouseDown イベントを受け取るたびにイベントリスナーがトリガされます。
useCapture プロパティを true に設定した場合、つまりイベントがキャプチャ段階で伝播する場合は、バブリングのデフォルト設定に関係なく、そのイベントはバブルしません。キャプチャ段階とバブリング段階の両方でイベントを処理する場合は、addEventListener() を 2 回、つまり useCapture を true に設定して 1 回と、false に設定して 1 回呼び出す必要があります。
イベントは、表示リストの祖先の親チェーンのみをバブルアップします。同じコンテナ内に含まれる 2 つの Button コントロールなどの兄弟は、互いのイベントに影響しません。
Event オブジェクトの eventPhase プロパティを使用して、イベントがどの段階にあるかを確認できます。このプロパティには、次の定数のいずれかを表す整数が格納されています。
CAPTURING_PHASE)AT_TARGET)BUBBLING_PHASE)次の例では、現在の段階と現在のターゲット ID が表示されます。
<?xml version="1.0"?>
<!-- events/DisplayCurrentTargetInfo.mxml -->
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml">
<mx:Script><![CDATA[
private function showInfo(e:MouseEvent):void {
trace(e.eventPhase + ":" + e.currentTarget.id);
}
]]></mx:Script>
<mx:Button id="b1" label="Click Me" click="showInfo(event)"/>
</mx:Application>
それぞれの段階で表示リスト内の移動を停止するには、Event オブジェクトで次のいずれかのメソッドを呼び出します。
Event オブジェクトのイベントフロー処理を停止するには、stopPropagation() メソッドまたは stopImmediatePropagation() メソッドを呼び出します。これら 2 つのメソッドはほとんど同じですが、現在のノードの残りのイベントリスナーを実行できるかどうかという点のみが異なります。stopPropagation() メソッドを呼び出すと、Event オブジェクトは次のノードに移動しなくなりますが、現在のノードの他のイベントリスナーはすべて実行されます。
stopImmediatePropagation() メソッドを呼び出しても Event オブジェクトは次のノードに移動しなくなりますが、この場合は現在のノードの他のイベントリスナーは実行されません。
次の例では、Panel コンテナの中に TitleWindow コンテナを作成し、両方のコンテナを mouseDown イベントのリスナーとして登録しています。その結果、TitleWindow コンテナをクリックした場合、stopImmediatePropagation() メソッドの呼び出しを追加しない限り、showAlert() メソッドが 2 回呼び出されます。
<?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);
}
public function showAlert(e:Event):void {
Alert.show("Alert!\n" + e.currentTarget + "\n" + e.eventPhase);
e.stopImmediatePropagation();
}
public function closeWindow(e:Event):void {
p1.removeChild(tw1);
}
]]></mx:Script>
<mx:Panel id="p1" title="Panel 1">
<mx:TitleWindow id="tw1" width="300" height="300" showCloseButton="true" title="Title Window 1">
<mx:Button label="Enter name"/>
<mx:TextArea id="ta1"/>
</mx:TitleWindow>
</mx:Panel>
</mx:Application>
次の例では、ターゲットがイベントを処理した後、親コンテナの click ハンドラによって、ターゲットコントロールが無効に設定されます。この例は、複数のイベント (すべてのクリック) で 1 つのリスナー (HBox コンテナのクリック) のロジックを再利用できることを示しています。
<?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 {
event.currentTarget.enabled = false;
}
public function doSomething(event:MouseEvent):void {
b1.label = "clicked";
ta1.text += "something wonderful happened";
}
public function doSomethingElse(event:MouseEvent):void {
b2.label = "clicked";
ta1.text += "something wonderful happened again";
}
]]></mx:Script>
<mx:HBox 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:Application>
複数のリスナー (子コントロールごとに 1 つずつ) を指定する代わりに親コントロールで 1 つのリスナーを指定すると、コードの行数が減り、アプリケーションの効率が向上します。addEventListener() メソッドの呼び出し回数が減ると、アプリケーションの起動時間が短くなり、メモリの消費量も少なくなります。
次の例では、リンクごとにリスナーを登録する代わりに、Panel コンテナに対して 1 つのイベントハンドラを登録しています。Panel コンテナの子はすべてこのイベントハンドラを継承します。Flex はバブルイベントのハンドラを呼び出すため、currentTarget プロパティではなく target プロパティを使用します。このハンドラでは、currentTarget プロパティは Panel コントロールを参照し、target プロパティは必要なラベルを持つ LinkButton コントロールを参照します。
<?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>
Flex 2.01