« AS3 についていろいろメモ | メイン

2008年11月17日

[Progression][as3] SceneObject のイベントフロー周りを少し細かく実装してみました

現在の Progression にはシーンオブジェクトにイベントフローが実装されていますが、 表示オブジェクトとは少し挙動が違っています。(Progression 3.0.7 現在)

useCapture や eventPhase, bubbles あたりの値が有効化されていませんので、これを実装してみました。

変更を加えたファイルを以下に置いておきます。
jp/progression/events/progression_event
jp/progression/events/SceneEvent
jp/progression/scenes/SceneObject

未だ as ファイルだと表示できなくて、拡張子を .txt に変えてますが。
リンク先を trac.progression.jp の方に変更しました。

まとめてダウンロードされる場合はこちら。
eventflow.zip

以下、解説を少し。
var sceneA:SceneObject = createScene("sceneA");
var sceneB:SceneObject = createScene("sceneB");
var sceneC:SceneObject = createScene("sceneC");

sceneA.addScene(sceneB);
trace("----");
sceneB.addScene(sceneC);
trace("----");
sceneA.removeScene(sceneB);

function createScene(name:String):SceneObject
{
    var scene:SceneObject = new SceneObject(name);
    scene.addEventListener(SceneEvent.SCENE_ADDED, eventHandler, false);
    scene.addEventListener(SceneEvent.SCENE_REMOVED, eventHandler, false);
    scene.addEventListener(SceneEvent.SCENE_ADDED, eventHandler, true);
    scene.addEventListener(SceneEvent.SCENE_REMOVED, eventHandler, true);
    return scene;
}

function eventHandler(event:Event):void 
{
    trace(event.target.name, event.currentTarget.name, event.type, event.eventPhase);
}

出力結果
sceneB sceneA sceneAdded 1
sceneB sceneB sceneAdded 2
sceneB sceneA sceneAdded 3
----
sceneC sceneA sceneAdded 1
sceneC sceneB sceneAdded 1
sceneC sceneC sceneAdded 2
sceneC sceneB sceneAdded 3
sceneC sceneA sceneAdded 3
----
sceneB sceneA sceneRemoved 1
sceneB sceneB sceneRemoved 2
sceneB sceneA sceneRemoved 3


変更したのは SceneEvent クラスと SceneObject クラスの 2 つ。

まず SceneEvent クラスに以下を追記。
override public function get target():Object 
{
    return super.target
        && progression_event::$target 
        || super.target;
}

override public function get currentTarget():Object 
{
    return super.currentTarget 
        && progression_event::$currentTarget
        || super.currentTarget;
}

override public function get eventPhase():uint 
{
    return progression_event::$eventPhase || super.eventPhase;
}

progression_event var $target:Object = null;
progression_event var $currentTarget:Object = null;
progression_event var $eventPhase:uint = 0;
ここで SceneObject から値に手を加えられるようにするために、 progression_event という namespace ファイルを jp/progression/events/ 内に追加しています。

package jp.progression.events 
{
    public namespace progression_event 
        = "http://www.progression.jp/events/progression_event";
}


dispatchEvent() にイベントオブジェクトを渡したときに target に値が設定されていると、 dispatchEvent 内で Event.clone() が呼び出されるので、 super.target が null のときは null を返すようにしています。
もし $target が設定されていて super.target も設定されている場合は $target の値を、 $target が null でも super.target が設定されている場合は super.target の値を返します。
これは、 dispatchEvent() の内部以外での clone() メソッドの呼び出しへの配慮でもあります。

次はちょっと長いですが、 SceneObject に追加した内容。
/**
 * イベントフローのための内部参照
 */
private var $parent:SceneObject;

/**
 * キャプチャ段階のイベントに登録されたリスナーを保持しておくための EventIntegrator オブジェクト
 * 
 * コンストラクタ内に以下を追記
 * $captureEvent = new EventIntegrator(this);
 */
private var $captureEvent:EventIntegrator;

override public function dispatchEvent(event:Event):Boolean 
{
    if (event is SceneEvent)
    {
        return $dispatchEvent(event as SceneEvent);
    }
    return super.dispatchEvent(event);
}

private function $dispatchEvent(event:SceneEvent):Boolean
{
    var bool:Boolean = true;
    
    if (event.eventPhase == EventPhase.AT_TARGET)
        event.progression_event::$target = this;
    
    event.progression_event::$currentTarget = this;
    
    if (event.bubbles 
        && $parent 
        && event.eventPhase <= EventPhase.AT_TARGET)
    {
        var phase1:SceneEvent = event.clone() as SceneEvent;
        phase1.progression_event::$eventPhase = EventPhase.CAPTURING_PHASE;
        phase1.progression_event::$target
            = event.progression_event::$target;
        bool = $parent.dispatchEvent(phase1);
    }
    
    if (event.eventPhase == EventPhase.CAPTURING_PHASE)
    {
        bool = $captureEvent.dispatchEvent(event);
    }
    else
    {
        bool = super.dispatchEvent(event);
    }
    
    if (event.bubbles 
        && $parent 
        && event.eventPhase >= EventPhase.AT_TARGET)
    {
        var phase3:SceneEvent = event.clone() as SceneEvent;
        phase3.progression_event::$eventPhase = EventPhase.BUBBLING_PHASE;
        phase3.progression_event::$target = event.target;
        bool = $parent.dispatchEvent(phase3);
    }
    return bool;
}

override public function addEventListener(
    type:String, listener:Function, useCapture:Boolean = false,
    priority:int = 0, useWeakReference:Boolean = false):void 
{
    if (useCapture)
    {
        $captureEvent.addEventListener(
            type, listener, false, priority, useWeakReference);
    }
    else
    {
        super.addEventListener(
            type, listener, false, priority, useWeakReference);
    }
}

override public function removeEventListener(
    type:String, listener:Function, useCapture:Boolean = false):void 
{
    if (useCapture)
    {
        $captureEvent.removeEventListener(type, listener, false);
    }
    else
    {
        super.removeEventListener(type, listener, false);
    }
}

override public function hasEventListener(type:String):Boolean 
{
    return super.hasEventListener(type)
        || $captureEvent.hasEventListener(type);
}

override public function willTrigger(type:String):Boolean 
{
    var bool:Boolean = hasEventListener(type);
    if ($parent) bool = bool || $parent.hasEventListener(type);
    return bool;
}

override public function removeAllListeners(
    completely:Boolean = false):void 
{
    super.removeAllListeners(completely);
    $captureEvent.removeAllListeners(completely);
}

override public function restoreRemovedListeners():void 
{
    super.restoreRemovedListeners();
    $captureEvent.restoreRemovedListeners();
}
ここでは EventDispatcher のメソッドをそれぞれオーバーライドして処理内容に手を加えています。
具体的には、 addEventListener() と removeEventListener() で useCapture の値で 登録するオブジェクトを分けています。これはイベント呼び出し時にイベントフェーズによる振り分けができないためです。

dispatchEvent() 内は、親シーンへ遡ってイベントを呼び出すように変更しています。

更に、 _registerScene() と _unregisterScene() にも少し修正を加えます。

_registerScene の変更箇所
// イベントフロー時の参照先を登録する
scene.$parent = scene._parent;

// イベントリスナーを登録する
//scene.addExclusivelyEventListener( SceneEvent.SCENE_ADDED, 
    dispatchEvent, false, int.MAX_VALUE, true );
//scene.addExclusivelyEventListener( SceneEvent.SCENE_REMOVED, 
    dispatchEvent, false, int.MAX_VALUE, true );
//addExclusivelyEventListener( SceneEvent.SCENE_ADDED_TO_ROOT, 
    scene.dispatchEvent, false, int.MAX_VALUE, true );
//addExclusivelyEventListener( SceneEvent.SCENE_REMOVED_FROM_ROOT, 
    scene.dispatchEvent, false, int.MAX_VALUE, true );

// イベントを送出する
scene.dispatchEvent( new SceneEvent(
    SceneEvent.SCENE_ADDED, true, false, scene ) );

// ルートシーンが存在し、変更されていれば
if ( root && previousRoot != root ) {
    // イベントを送出する
    scene.dispatchEvent( new SceneEvent(
        SceneEvent.SCENE_ADDED_TO_ROOT, true, false, scene ) );
}

_unregisterScene の変更箇所
// イベントを送出する
scene.dispatchEvent( new SceneEvent( SceneEvent.SCENE_REMOVED, true, false, scene ) );

// ルートシーンが存在せず、変更されていれば
if ( !scene._root && previousRoot != scene._root ) {
    // イベントを送出する
    scene.dispatchEvent( new SceneEvent( SceneEvent.SCENE_REMOVED_FROM_ROOT, true, false, scene ) );
}

// イベントフローのための参照を削除
scene.$parent = null;

// イベントリスナーを解除する
//scene.completelyRemoveEventListener( 
    SceneEvent.SCENE_ADDED, dispatchEvent );
//scene.completelyRemoveEventListener( 
    SceneEvent.SCENE_REMOVED, dispatchEvent );
//completelyRemoveEventListener( 
    SceneEvent.SCENE_ADDED_TO_ROOT, scene.dispatchEvent );
//completelyRemoveEventListener( 
    SceneEvent.SCENE_REMOVED_FROM_ROOT, scene.dispatchEvent );
追加・削除された子シーンにイベントリスナーの登録を行っていた処理をコメントアウトしています。
また、イベントフローのための親シーンの参照を登録・解除しています。

あとは、 dispatchEvent に渡されるイベントオブジェクトの bubbles の値を true に変更しました。
bubbles が false に設定された場合はイベントフローを発生させないようにしているためです。

以上で、表示オブジェクトのようなイベントフローの実装ができます。
ただ、 EventIntegrator での実装を、あまり理解せずに手を加えているので、どこかに不都合はあるかもしれないです。
すこし使い倒してみる必要はあるかと思います。

トラックバック(0)

このブログ記事を参照しているブログ一覧: SceneObject のイベントフロー周りを少し細かく実装してみました

このブログ記事に対するトラックバックURL: http://system.seyself.com/mt-tb.cgi/520

コメントする


画像の中に見える文字を入力してください。

しばらく時間が経過すると、システム内部と表示されている文字内容に食い違いが発生するようなので、
投稿する前にページをリロードすることをお勧めします。
リロードしてもフォームの内容は維持されます。