« Visiting Google, and how we behave if something is free and plentiful | Garbage Collection »

Accessing the component instance from a Flex skin

8th August 2008

Another little post about skinning Flex components...

If you need to access the component that a skin belongs to, you need to know that the skin's parent is the component that you're skinning. Which means you can access properties of the component through the skin's parent (N.B. Don't do this in the constructor – the skin isn't added to the component's display list until after the constructor has run).

For example, you can find a Button's label as the label property of the button skin's parent. Using this, you can create skins that vary based on the parent's label. For example, a set of buttons for controlling video playback would all have different icons on them. Rather than creating different skins for each button, you can create one skin and draw a different icon based on the skin's label (play, pause, etc).

Hiding a Button's label

If, as above, you use a button's label as an indicator for modifying a button skin, you may also want to hide the label graphic. The label graphic is the only child of the Button that is a UITextField. And the Button is the skin's parent. So, to hide the label you look through the parent's children, find the UITextField, and set it's visible property to false.

Example

Putting these two ideas together produces something like this

package
{
  import mx.controls.Button;
  import mx.core.UITextField;
  import mx.skins.ProgrammaticSkin;
  
  public class MediaButtonSkin extends ProgrammaticSkin
  {
    public function MediaButtonSkin()
    {
      super();
    }

    override protected function updateDisplayList(
                                         w:Number, h:Number ):void
    {
      // check we're on a Button
      if( ! parent ) return;
      if( ! parent is Button )
        throw( new Error( "MediaButtonSkin may only be 
                                         used on Button objects" ) );

      super.updateDisplayList( w, h );
      
      // find the label display UITextField and hide it
      for( var i:int = 0; i < parent.numChildren; i++ )
      {
        if( parent.getChildAt( i ) is UITextField )
        {
          parent.getChildAt( i ).visible = false;
        }
      }
      
      // choose a fill color based on the skin state
      var fillColor:uint;
      switch( name )
      {
        case "upSkin":
        case "selectedUpSkin":
          fillColor = 0xCC0000;
          break;
        case "overSkin":
        case "selectedOverSkin":
          fillColor = 0xFF3300;
          break;
        case "downSkin":
        case "selectedDownSkin":
          fillColor = 0xFF9900;
          break;
        case "disabledSkin":
        case "selectedDisabledSkin":
          fillColor = 0x7F0000;
          break;
      }
      
      // set the circle properties
      var radius:Number = Math.min( w, h ) / 2;
      var centreX:Number = w / 2;
      var centreY:Number = h / 2;
      
      // draw the circle
      graphics.clear();
      graphics.beginFill( fillColor, 1 );
      graphics.drawCircle( centreX, centreY, radius );
      graphics.drawCircle( centreX, centreY, radius-3 );
      graphics.endFill();
      
      graphics.beginFill( fillColor, 0 );
      graphics.drawCircle( centreX, centreY, radius-3 );
      graphics.endFill();
      
      // get the button's label text
      var label:String = Button( parent ).label.toLowerCase();
      
      // draw the graphics based on the label text
      switch( label )
      {
        case "play":
          var tipX:Number = centreX + radius * 0.5;
          var backX:Number = centreX - radius * 0.3;
          var tipYOffset:Number = radius * 0.45;
    
          graphics.beginFill( fillColor, alpha );
          graphics.moveTo( tipX, centreY );
          graphics.lineTo( backX, centreY + tipYOffset );
          graphics.lineTo( backX, centreY - tipYOffset );
          graphics.lineTo( tipX, centreY );
          graphics.endFill();
          break;
          
        case "pause":
          var outX:Number = radius * 0.4;
          var inX:Number = radius * 0.15;
          var tipY:Number = radius * 0.45;
    
          graphics.beginFill( fillColor, alpha );
          graphics.drawRect( centreX + inX, centreY - tipY, 
                                         outX - inX, tipY * 2 );
          graphics.drawRect( centreX - outX, centreY - tipY, 
                                         outX - inX, tipY * 2 );
          graphics.endFill();
          break;
      }
    }
  }
}

Add this little bit of MXML

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" 
  layout="absolute" width="160" height="40" backgroundColor="0">
  <mx:HBox x="45" y="5">
    <mx:Button skin="MediaButtonSkin" 
                             width="30" height="30" label="play"/>
    <mx:Button skin="MediaButtonSkin" 
                             width="30" height="30" label="pause"/>
  </mx:HBox>
</mx:Application>

And the result looks like this

Flash required: You need version 9 or later of the free Flash player from Adobe to use this content. To download and install the free player from Adobe's web site click here.

Read more articles about Flex, Actionscript 3 

2 Comments add your own

  • Thanks! This was very helpful!

    Erno  |  8th August 2008 at 1:31 pm

  • “Which means you can access properties of the component through the skin’s parent”

    This is usually the case but isn’t guarenteed. It’s feasible that the skin could be inside another container, inside the component. Always worth bearing in mind.

    Tink  |  9th August 2008 at 11:54 pm

Leave a Comment

required

required, hidden

XHTML: you can use these tags - <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <code> <em> <i> <strike> <strong>

Subscribe to the comments via RSS Feed