[Flex][Flex2開発ガイド] 4章 イベントの使用

Flexにおけるイベントの概念を学びます。
多分Flexで業務アプリを作っていっていろいろイベントを張り巡らしていくと、
結局このあたりをちゃんと勉強していかないと手が出なくなるような気もします。

イベントについて

この章では後半の章を間単に説明したような感じですね。

ただEventDispatcherクラス(IEventDispatcher)については確認しておくべきですね。
このクラスを継承しているクラスはイベントリスナー(イベントハンドラー)を登録、確認、削除などができるようになります。
もちろんIEventDispatcherインターフェースを自分で実装する場合は、そのようなことができるようにする必要があるんですがね。
イベントリスナーはイベントの発生を監視しているという意味で覚えておけばよいのでしょうか、
イベントハンドラーはイベントの処理を実際に行うような意味で覚えておけばよいのかな?
ただ、あんまり気にしてても仕方ないので、両方とも同じように読み進めればいいみたいです。

イベントの使用

まずはイベントリスナーに目を向けてみます。
イベントリスナーは下記のような形をとる必要があるみたいです。

function myEventHandler(e:Event):void{
  ...
}

この中で指定しているEventクラスはそのサブクラスでも問題ありません。

function myEventHandler(e:ToolTipEvent):void{
  ...
}

ただし、登録時のリスニングしているイベントとイベントリスナーで指定したEventクラスが
正しくない(指定したEventクラスにキャストできない)状態だとコンパイルは通るけど実行時エラーが出ます。

  • CastErrorEventListener.mxml
<?xml version="1.0" encoding="UTF-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" width="100%"
    height="100%" creationComplete="init()">

    <mx:Script>
        <![CDATA[
        import mx.events.ToolTipEvent;
        
        private function init():void{
            // addEventListenerでイベントリスナーを登録
            btn1.addEventListener(MouseEvent.CLICK, clickBtn1);
        }
        
        private function clickBtn1(e:ToolTipEvent):void{
            trace("clickBtn1");
        }
        ]]>
    </mx:Script>
    <mx:Button id="btn1" label="ボタン1"/>
</mx:Application>

この場合、ボタンをクリックすると下記メッセージが表示されます。

TypeError: Error #1034: 強制型変換に失敗しました。flash.events::MouseEvent@11f6191 を mx.events.ToolTipEvent に変換できません。
	at [mouseEvent]


次に、Eventオブジェクトには何が含まれているかを見ていきます。
Eventクラスの公開されているプロパティには次のようなものがあります。

  • bubbles:Boolean
  • cancelable:Boolean
  • currentTarget:Object
  • eventPhase:uint
  • target:Object
  • type:String

詳しくはasdocを見てもらった方がいいのですが、
特に重要なものはcurrentTargetとtargetでしょうか・・・。
targetはイベントを送出したオブジェクトが入ってます。
currentTargetはイベントを捕捉したオブジェクトが入ってます。
このイベントを捕捉についてはイベントの伝播で詳しく見ていくことにしますが、
ここではそういうものがあるんだね、ってくらいでいいかもしれません。

このtarget、currentTargetなどを利用してイベントハンドラー内で情報を取得したり、
変更したりすればいいということです。


次にイベントリスナーの登録。登録方法は大きく2種類あるらしい。

  • タグによる方法
  • addEventListenerを使う方法
<?xml version="1.0" encoding="UTF-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" width="100%"
    height="100%" creationComplete="init()">

    <mx:Script>
        <![CDATA[
        import flash.events.Event;
        
        private function init():void{
            // addEventListenerでイベントリスナーを登録
            btn2.addEventListener(MouseEvent.CLICK, clickBtn2);
        }
        
        private function clickBtn1(e:Event):void{
            trace("clickBtn1");
        }
        private function clickBtn2(e:Event):void{
            trace("clickBtn2");
        }
        ]]>
    </mx:Script>

    <!-- タグでイベントリスナーを登録-->
    <mx:Button id="btn1" label="ボタン1" click="clickBtn1(event)" />
    <mx:Button id="btn2" label="ボタン2" />
</mx:Application>

ただ、タグの中で指定する方法は、細かな指定ができない、削除できないといったデメリットがあるので
addEventListenerで指定する方がお勧めだそうだ。

この細かな指定って言うのは、イベントの伝播で利用するパラメータであったり、優先度であったり、
タグにかけないようなイベントの指定であったりするわけですが、伝播、優先度については後述するのでそちらを見てください。

あと、イベントハンドラーの作り方や単一イベントに複数のリスナーを追加したり、
複数のコンポーネントに対して単一のリスナーを追加したり・・・想像通りに普通にできるので割愛します。

手動によるイベントの送出

EventDispatcher.dispatchEvent()メソッドを利用してEventオブジェクトを渡してあげれば、
イベントを発生させることができるらしい。ということでサンプルです。

<?xml version="1.0" encoding="UTF-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" width="100%"
    height="100%" creationComplete="init()">

    <mx:Script>
        <![CDATA[
        import flash.events.Event;
        import flash.events.MouseEvent;
        import mx.controls.Alert;
        
        private function mouseOver(e:Event):void{
            var me:MouseEvent = new MouseEvent(MouseEvent.CLICK);
            btn1.dispatchEvent(me);
        }
        private function click(e:Event):void{
            Alert.show("クリックしました");
        }
        ]]>
    </mx:Script>

    <mx:Button id="btn1" label="ボタン1" mouseOver="mouseOver(event)" click="click(event)" />
</mx:Application>

この例ではボタンにマウスを当てるとmouseOverイベントが発生し、そのイベントハンドラーの中で、
クリックイベントを発生させています。
なので、クリックもしていないのにクリックイベント処理が行われるって感じになっています。
こんな感じで、イベントをどんどん発生させることもできるのです。

イベントの伝播

コンテナコンポーネントの中にボタンが配置され、コンテナコンポーネント、ボタンともにクリックイベントに対するイベントリスナーが登録されていた場合どうなるんでしょうか?
ここでは、そのようなときにどのように動作するのかを見ていくことにします。

イベントの発生時には、3つの段階があるようです。

  • キャプチャ
  • ターゲット
  • バブリング

例えば、HBoxの中にButtonが存在したとしましょう。
HBoxにはクリックイベントのキャプチャ段階で処理されるイベントリスナーAと
ターゲット、バブリング状態で処理されるイベントリスナーB(通常はこちら)が登録されています。
またButtonにはクリックイベントで処理されるイベントリスナーCが登録されています。

この状態でButtonをクリックしたとするとどうなるでしょうか?

  • イベントリスナーAが処理される(キャプチャ段階)
  • イベントリスナーCが処理される(ターゲット段階)
  • イベントリスナーBが処理される(バブリング段階)

まず簡単なターゲット段階とは、イベントの発生元に対するものです。
次にバブリング段階とは、ターゲット段階が終了し、そのコンポーネントの上位にあるコンポーネントでイベントが捕捉される状態のときのことを言います。
逆に、ターゲット段階に向かう時のことをキャプチャ段階といいます。

また、ターゲット段階はバブリング、キャプチャと一緒には定義できません。
例えば、Buttonに対してキャプチャ時にクリックイベントを拾うように定義しても、
Buttonをクリックした時には、そのイベントは実行されません。
あくまでターゲットとして登録する必要があるのです。

ま、実行してみましょう。

  • EventPropagation.mxml
<?xml version="1.0" encoding="UTF-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" width="100%"
    height="100%" creationComplete="init()">

    <mx:Script source="EventPropagation.as">
    </mx:Script>

    <mx:HBox id="hbox1">
        <mx:VBox id="vbox1">
            <mx:Button id="button1" label="button1"/>
        </mx:VBox>
        <mx:VBox id="vbox2">
            <mx:Button id="button2" label="button2"/>
        </mx:VBox>
    </mx:HBox>

    <mx:TextArea id="logArea" text="" width="500" height="500"></mx:TextArea>

</mx:Application>
  • EventPropagation.as
&#65279;import flash.events.Event;
import mx.containers.HBox;
import flash.events.MouseEvent;
import mx.controls.TextArea;

//private var logArea:TextArea;

private function log(mesg:String, event:Event):void {
	var currentTarget:Object = event.currentTarget;
	var target:Object = event.target;	
    logArea.text = logArea.text + mesg + "\n==>target=" + target + "\n==>currentTarget" + currentTarget + "\n";
	//logArea.data
}
        
private function captureHbox1(event:Event):void {
    log("Hbox1のキャプチャ段階:", event );
}
        
private function captureVbox1(event:Event):void {
    log("Vbox1のキャプチャ段階:", event );
}

private function captureVbox2(event:Event):void {
    log("Vbox2のキャプチャ段階:", event );
}

private function captureButton1(event:Event):void {
    log("Button1のキャプチャ段階:", event );
}

private function captureButton2(event:Event):void {
    log("Button2のキャプチャ段階:", event );
}

private function bubbleHbox1(event:Event):void {
    log("Hbox1のバブリング段階:", event );
}
        
private function bubbleVbox1(event:Event):void {
    log("Vbox1のバブリング段階:", event );
}

private function bubbleVbox2(event:Event):void {
    log("Vbox2のバブリング段階:", event );
}

private function bubbleButton1(event:Event):void {
    log("Button1のバブリング段階:", event );
}

private function bubbleButton2(event:Event):void {
    log("Button2のバブリング段階:", event );
}

private function init():void {
	hbox1.addEventListener(MouseEvent.CLICK, captureHbox1,true );
	hbox1.addEventListener(MouseEvent.CLICK, bubbleHbox1, false );
	vbox1.addEventListener(MouseEvent.CLICK, captureVbox1, true );
	vbox1.addEventListener(MouseEvent.CLICK, bubbleVbox1, false);
	vbox2.addEventListener(MouseEvent.CLICK, captureVbox2, true );
	vbox2.addEventListener(MouseEvent.CLICK, bubbleVbox2, false);
	button1.addEventListener(MouseEvent.CLICK, captureButton1, true );
	button1.addEventListener(MouseEvent.CLICK, bubbleButton1, false);
	button2.addEventListener(MouseEvent.CLICK, captureButton2, true );
	button2.addEventListener(MouseEvent.CLICK, bubbleButton2, false);
}

イベントの優先度

同じターゲットに複数のイベントをつける時、大抵実行される順番が気になります。
そんなときは、優先度をつけて登録しましょう。
大きい方が優先度:高です。
デフォルト=0です。負の整数もOKです。

じゃ、同一だった場合どうなるの?ってことで試してみましょう。

<?xml version="1.0" encoding="UTF-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" width="100%"
    height="100%" creationComplete="init()">

    <mx:Script>
        <![CDATA[
        import flash.events.Event;
        import flash.events.MouseEvent;
        import mx.controls.Alert;
        
        private function init():void{
            btn1.addEventListener(MouseEvent.CLICK, click1, false, 1);
            btn1.addEventListener(MouseEvent.CLICK, click2, false, 1);
        }
        private function click1(e:Event):void{
            Alert.show("1");
        }
        private function click2(e:Event):void{
            Alert.show("2");
        }
        ]]>
    </mx:Script>
    <mx:Button id="btn1" label="ボタン1"/>
</mx:Application>

うん、この場合はclick2が先に処理されますね。
ただ、どこにもそんなことは書かれていないので、順序が重要ならちゃんと定義しましょうってことでしょうかね。

イベントのサブクラスの使用

ここは、イベントオブジェクトのキャストがどうとか、そういう話をしています。
ちょっと前にかじったので、スルーです。

キーボードイベントについて

これも結構重要です。キーボードの操作でなんかしたいって要求っていつでもついて回るので・・・。

気になるのは、どうやってキーボートイベントのイベントハンドラを登録するのか?
何が押されたのか?といったものでしょうか?

まずは一般的な例ですね。

<?xml version="1.0" encoding="UTF-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" width="100%"
    height="100%" creationComplete="init()">

    <mx:Script>
        <![CDATA[
        import flash.events.Event;
        import flash.events.KeyboardEvent;
        import mx.controls.Alert;
        
        private function init():void{
            application.addEventListener(KeyboardEvent.KEY_DOWN, keyDown);
            // フォーカスを合わせる必要があるので・・・
            c.setFocus();
        }
        private function keyDown(e:Event):void{
            Alert.show("keyDown");
        }
        ]]>
    </mx:Script>
    <mx:Canvas id="c"/>
</mx:Application>

起動して、何かキーを押すとイベントが捕捉されます。

UIComponentを拡張しているクラスでは、KeyUpとKeyDownのイベントを送出するため、
これらのどこかにフォーカスがあってさえいればキーを押した、離したのイベントは
捕捉できるみたいですね。

次に、何が押されたかです。これにはKeyboadEventオブジェクトのkeyCodeとcharCodeプロパティを覗く必要があります。
keyCodeとはキーボード上のキーを数値化したコードで、charCodeはそれが押されたことによる文字種別を表すコードのようです。

またMouseEventもキーを押してイベントが発生したときなど色々処理ができるみたいです。
必用になったときにこのあたりを読み返せばいいのかなと思えてきたので割愛します。

一点、気になることとしては、ブラウザが捕捉するキーイベントはFlexでは捕捉できない!ってことです。
例えばCtrl-wは捕捉できません。ま、当然でしょうね・・・。

所感

イベントハンドラーをクラスにするか、MXMLスクリプトタグに書くとかという話題もありましたが、
個人的にはMXMLスクリプトタグ(外に出すのもOK)に書くのが一番かなと・・・。
そこから、共通的なクラスに処理を委譲するのはOK。
イベント処理って画面独自のものが多いので、すぐ近くに記述があった方が分かりやすいと思うんですよね。

ただ、このドキュメントにはスタティックメソッドがお勧めとかあったのですが、どうなんでしょう?!
たぶん私はそんなことはしないと思うのですが、Flexでは当たり前なのかな〜。