SkinnableComponentを拡張したツールチップ #2

2011.12.28

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

前回に引き続き、SkinnableComponentを拡張したツールチップを作成します。今回はツールチップを固定位置に表示するボタンを作成します。

固定位置にツールチップを表示するボタン

ツールチップをボタンの上下左右の固定位置に表示させるボタンコンポーネントを作成します。以下はコンポーネントのソースコードです。

package jp.classmethod.sample.components
{
    import flash.errors.IllegalOperationError;
    import flash.geom.Point;
   
    import mx.core.IFactory;
    import mx.core.IToolTip;
    import mx.events.ToolTipEvent;
   
    import spark.components.Button;
   
    public class FixedPositionToolTipButton extends Button
    {
       
        //--------------------------------------------------------------------------
        //
        //  Constructor
        //
        //--------------------------------------------------------------------------
       
        public function FixedPositionToolTipButton()
        {
            super();
        }
       
        [SkinPart(required="false")]
        public var toolTipComponent:IFactory;
       
        //--------------------------------------------------------------------------
        //
        //  Properties
        //
        //--------------------------------------------------------------------------
       
        //----------------------------------
        //  toolTipDirection
        //----------------------------------
       
        private var _toolTipDirection:String = FixedPositionToolTipDirection.TOP;
       
        [Inspectable(category="General", enumeration="top,bottom,left,right", defaultValue="top")]

        public function get toolTipDirection():String
        {
            return _toolTipDirection;
        }

        public function set toolTipDirection(value:String):void
        {
            switch (value)
            {
                case FixedPositionToolTipDirection.TOP:
                case FixedPositionToolTipDirection.LEFT:
                case FixedPositionToolTipDirection.RIGHT:
                case FixedPositionToolTipDirection.BOTTOM:
                    _toolTipDirection = value;
                    break;
                default:
                    throw new IllegalOperationError("Invalid ToolTipDirection.");
            }
        }
       
        //----------------------------------
        //  toolTipGap
        //----------------------------------

        private var _toolTipGap:Number = 5;

        public function get toolTipGap():Number
        {
            return _toolTipGap;
        }

        public function set toolTipGap(value:Number):void
        {
            _toolTipGap = value;
        }
       
        //--------------------------------------------------------------------------
        //
        //  Overridden methods
        //
        //--------------------------------------------------------------------------
       
        override protected function childrenCreated():void
        {
            super.childrenCreated();
            addEventListener(ToolTipEvent.TOOL_TIP_CREATE, toolTipCreateHandler, false, 0, true);
            addEventListener(ToolTipEvent.TOOL_TIP_SHOW, toolTipShowHandler, false, 0, true);
        }
       
        //--------------------------------------------------------------------------
        //
        //  Methods
        //
        //--------------------------------------------------------------------------
       
        protected function toolTipCreateHandler(event:ToolTipEvent):void
        {
            if (toolTipComponent)
            {
                var toolTip:FixedPositionToolTip = toolTipComponent.newInstance();
                toolTip.toolTipDirection = toolTipDirection;
                event.toolTip = toolTip as IToolTip;
            }
        }
       
        protected function toolTipShowHandler(event:ToolTipEvent):void
        {
            var toolTip:IToolTip = event.toolTip;
            var buttonOriginPoint:Point = localToGlobal(new Point(0, 0));
            switch (toolTipDirection)
            {
                case FixedPositionToolTipDirection.TOP:
                    toolTip.x = buttonOriginPoint.x + width / 2 - toolTip.width / 2;
                    toolTip.y = buttonOriginPoint.y - toolTip.height - toolTipGap;
                    break;
                case FixedPositionToolTipDirection.LEFT:
                    toolTip.x = buttonOriginPoint.x - toolTip.width - toolTipGap;
                    toolTip.y = buttonOriginPoint.y + height / 2 - toolTip.height / 2;
                    break;
                case FixedPositionToolTipDirection.RIGHT:
                    toolTip.x = buttonOriginPoint.x + width + toolTipGap;
                    toolTip.y = buttonOriginPoint.y + height / 2 - toolTip.height / 2;
                    break;
                case FixedPositionToolTipDirection.BOTTOM:
                    toolTip.x = buttonOriginPoint.x + width / 2 - toolTip.width / 2;
                    toolTip.y = buttonOriginPoint.y + height + toolTipGap;
                    break;
            }
        }
       
    }
}

拡張したボタンコンポーネントのスキンクラスのソースコードです。

<?xml version="1.0" encoding="utf-8"?>
<s:SparkSkin xmlns:fx="http://ns.adobe.com/mxml/2009" xmlns:s="library://ns.adobe.com/flex/spark"
             xmlns:fb="http://ns.adobe.com/flashbuilder/2009" minWidth="21" minHeight="21" alpha.disabled="0.5"
             xmlns:mx="library://ns.adobe.com/flex/mx"
             xmlns:components="jp.classmethod.sample.components.*">
   
    <!-- host component -->
    <fx:Metadata>
        <![CDATA[
        /**
         * @copy spark.skins.spark.ApplicationSkin#hostComponent
         */
        [HostComponent("jp.classmethod.sample.components.FixedPositionToolTipButton")]
        ]]>
    </fx:Metadata>
   
    <fx:Declarations>
        <fx:Component id="toolTipComponent">
            <components:FixedPositionToolTip
                skinClass="jp.classmethod.sample.skins.FixedPositionToolTipSkin"/>
        </fx:Component>
    </fx:Declarations>
   
    <!-- states -->
    <s:states>
        <s:State name="up" />
        <s:State name="over" />
        <s:State name="down" />
        <s:State name="disabled" />
    </s:states>
   
    <!-- グラフィックの定義は省略 -->
   
    <s:BitmapImage id="iconDisplay"
              alpha="0.85"
              horizontalCenter="0" verticalCenter="-2">       
    </s:BitmapImage>
   
</s:SparkSkin>

では、ソースコードを見ていきましょう。

ツールチップコンポーネント

コンポーネント側では、固定位置に表示するツールチップをスキンパーツとして宣言します。

[SkinPart(required="false")]
public var toolTipComponent:IFactory;

スキン側では、先ほど作成したツールチップコンポーネントをパーツとして定義します。

<fx:Component id="toolTipComponent">
    <components:FixedPositionToolTip
        skinClass="jp.classmethod.sample.skins.FixedPositionToolTipSkin"/>
</fx:Component>

ツールチップコンポーネントを表示リストに追加するのはToolTipManagerの役割になりますので、スキンクラスでは表示リスト上の子として定義せずに、Componentタグでクラス情報を保持します。コンポーネント側でスキンパーツを宣言している変数の型がIFactory型になっているのはこのためで、コンポーネント側の変数toolTipComponentにはインスタンス化されたツールチップではなく、ツールチップをインスタンス化するための情報を保持しているClassFactory型のオブジェクトがセットされます。後程このオブジェクトを利用してツールチップをインスタンス化します。

ツールチップイベント

ツールチップが生成・破棄される過程で、ツールチップ表示のトリガーとなったコンポーネントで以下のイベントが発生します。

  • ToolTipEvent.TOOL_TIP_START
  • ToolTipEvent.TOOL_TIP_CREATE
  • ToolTipEvent.TOOL_TIP_SHOW
  • ToolTipEvent.TOOL_TIP_SHOWN
  • ToolTipEvent.TOOL_TIP_HIDE
  • ToolTipEvent.TOOL_TIP_END

今回はToolTipEvent.TOOL_TIP_CREATEイベントとToolTipEvent.TOOL_TIP_SHOWイベントを利用して表示するツールチップをカスタマイズします。

まずはToolTipEvent.TOOL_TIP_CREATEイベントのイベントハンドラでの処理を見ていきましょう。

if (toolTipComponent)
{
    var toolTip:FixedPositionToolTip = toolTipComponent.newInstance();
    toolTip.toolTipDirection = toolTipDirection;
    event.toolTip = toolTip as IToolTip;
}

ToolTipEvent.TOOL_TIP_CREATEイベントはツールチップを生成するタイミングで発生します。このイベントハンドラ内でイベントのtoolTipプロパティにツールチップコンポーネントをセットすることで、表示するツールチップを任意のIToolTip実装コンポーネントに差し替えることができます。ここでは、スキンに定義したツールチップコンポーネントのファクトリオブジェクトからコンポーネントをインスタンス化して、ToolTipイベントのtoolTipプロパティにセットしています。

次はToolTipEvent.TOOL_TIP_SHOWイベントのイベントハンドラでの処理を見ていきましょう。

var toolTip:IToolTip = event.toolTip;
var buttonOriginPoint:Point = localToGlobal(new Point(0, 0));
switch (toolTipDirection)
{
    case FixedPositionToolTipDirection.TOP:
        toolTip.x = buttonOriginPoint.x + width / 2 - toolTip.width / 2;
        toolTip.y = buttonOriginPoint.y - toolTip.height - toolTipGap;
        break;
    case FixedPositionToolTipDirection.LEFT:
        toolTip.x = buttonOriginPoint.x - toolTip.width - toolTipGap;
        toolTip.y = buttonOriginPoint.y + height / 2 - toolTip.height / 2;
        break;
    case FixedPositionToolTipDirection.RIGHT:
        toolTip.x = buttonOriginPoint.x + width + toolTipGap;
        toolTip.y = buttonOriginPoint.y + height / 2 - toolTip.height / 2;
        break;
    case FixedPositionToolTipDirection.BOTTOM:
        toolTip.x = buttonOriginPoint.x + width / 2 - toolTip.width / 2;
        toolTip.y = buttonOriginPoint.y + height + toolTipGap;
        break;
}

ToolTipEvent.TOOL_TIP_SHOWイベントはツールチップを表示する直前に発生します。イベントのtoolTipプロパティにはツールチップコンポーネントのインスタンスがセットされているので、このインスタンスの座標を変更することでツールチップの表示位置を変更することができます。ここでは、自コンポーネントの位置を基準にtoolTipDirectionとtoolTipGapの値に応じてツールチップを表示する位置を設定しています。なお、ツールチップが表示リストに追加される際にはツールヒントレイヤーに追加されますので、ツールチップの位置はグローバル座標で設定します。

これで固定位置にツールチップを表示するボタンが完成しました。

動作確認

では、完成したコンポーネントの動作を見てみましょう。以下はサンプルアプリケーションのソースコードです。

<s:Application xmlns:fx="http://ns.adobe.com/mxml/2009" 
               xmlns:s="library://ns.adobe.com/flex/spark" 
               xmlns:mx="library://ns.adobe.com/flex/mx"
               xmlns:components="jp.classmethod.sample.components.*"
               applicationComplete="application1_applicationCompleteHandler(event)"
               width="400" height="300">
    
    <fx:Style>
        @namespace s "library://ns.adobe.com/flex/spark";
        @namespace mx "library://ns.adobe.com/flex/mx";
        @namespace components "jp.classmethod.sample.components.*";
        
        global {
            font-family : "メイリオ";
        }
        
        components|FixedPositionToolTip {
            font-size : 16;
        }
    </fx:Style>
    
    <fx:Script>
        <![CDATA[
            import jp.classmethod.sample.skins.FixedPositionToolTipButtonSkin;
            
            import mx.events.FlexEvent;
            import mx.managers.ToolTipManager;

            protected function application1_applicationCompleteHandler(event:FlexEvent):void
            {
                ToolTipManager.showEffect = fadeIn;
                ToolTipManager.hideEffect = fadeOut;
                ToolTipManager.showDelay = 100;
                ToolTipManager.scrubDelay = 400;
            }
        ]]>
    </fx:Script>
    
    <fx:Declarations>
        <s:Fade id="fadeIn" alphaFrom="0" alphaTo="1" duration="300"/>
        <s:Fade id="fadeOut" alphaFrom="1" alphaTo="0" duration="300"/>
    </fx:Declarations>
    
    <components:FixedPositionToolTipButton id="button" 
                                           bottom="5" horizontalCenter="0"
                                           toolTip="ホーム" icon="@Embed('assets/HomeIcon.png')" 
                                           toolTipDirection="top"
                                           skinClass="jp.classmethod.sample.skins.FixedPositionToolTipButtonSkin"/>
    <components:FixedPositionToolTipButton id="button2"
                                           right="5" verticalCenter="0"
                                           toolTip="ホーム" icon="@Embed('assets/HomeIcon.png')"
                                           toolTipDirection="left"
                                           skinClass="jp.classmethod.sample.skins.FixedPositionToolTipButtonSkin"/>
    <components:FixedPositionToolTipButton id="button3"
                                           left="5" verticalCenter="0"
                                           toolTip="ホーム" icon="@Embed('assets/HomeIcon.png')"
                                           toolTipDirection="right"
                                           skinClass="jp.classmethod.sample.skins.FixedPositionToolTipButtonSkin"/>
    <components:FixedPositionToolTipButton id="button4"
                                           top="5" horizontalCenter="0"
                                           toolTip="ホーム" icon="@Embed('assets/HomeIcon.png')"
                                           toolTipDirection="bottom"
                                           skinClass="jp.classmethod.sample.skins.FixedPositionToolTipButtonSkin"/>
</s:Application>

サンプルアプリケーションです。

[SWF]http://public-blog-dev.s3.amazonaws.com/wp-content/uploads/2011/12/ComponentTest.swf, 400, 300[/SWF]

固定位置にカスタマイズされたツールチップが表示されているのが確認できるかと思います。

まとめ

今回作成したツールチップコンポーネントはスキン可能ですので、もちろん別のスキンクラスを作成すればまた違う外観にすることができます。こういった細かいカスタマイズが比較的簡単にできるのがSparkコンポーネントのいいところですね。スキン可能コンポーネントを作成したことがなかった方も、ぜひこれを機に挑戦してみてください。