Building Custom Components in Flex 4: SkinParts

July 22nd, 2009 by ryanstewart

I built my first Flex 4 custom component today, a basic sortable list component that extends List. The only modification I added was a button that you could click to resort your list in alphanumeric order. I mostly wanted to get a feel for building a custom component and get a sense for how to use SkinParts. Hopefully this will be useful to others getting their feet wet in Flex 4 custom components.

The concept of a SkinPart in Flex 4 is one that gives the developer and the designer a lot of power. By listing something as a SkinPart, it means that you can put that particular item anywhere in your custom component using a skin file. SkinParts are also just other components so you can get the built-in functionality as well as any skinning that can take place by using them. In my SortableList, I decided to use a basic Button as the header. Defining a SkinPart is very straightforward, here’s the code for my custom component with the SkinPart definition.

public class SortableList extends List
{
     public function SortableList()
     {
          super();
     }
 
     [SkinPart(required="true")]
     public var header:Button;
}

All you have to do is create a metadata tag with SkinPart and the required attribute set to true or false. Right below that define what the id if your skin part has to be and then what kind of component it is.

To show and use the SkinPart you need to add it to the skin file and make sure the component type and id match. In my case, I’ve got a component with an id of header.

<s:Button id="header" width="100" height="20" x="0" y="0" />

Presumably, you want your new SkinPart to do something and this is where I ran into trouble. At first inside of my SortableList constructor I added an event listener on my header component. The problem is that when the constructor is called it doesn’t create the SkinPart. That happens during a separate operation. The secret then is to override the partAdded function, which is a method in the SkinnableComponent class, the basis for every new spark component.

The partAdded function is automatically called and will add all of the SkinParts you specify in your custom component. It takes two parameters, the partName and the instance. Make sure to call super.partAdded so that your SkinPart gets added to the component and then you can use the instance variable to check when your SkinPart is being created. That lets you add the event listeners that will fire when a user interacts with your skin part. In my case I just added a simple click handler:

override protected function partAdded(partName:String, instance:Object):void
{
     super.partAdded(partName, instance);
 
     if( instance == header)
     {
          header.addEventListener(MouseEvent.CLICK,header_clickHandler);
     }
}

If you just have one SkinPart, there doesn’t seem to be any need for the if statement because it will only run once. I have it there just to be safe. You also need to add code so that if the SkinPart is removed you also remove the event listener. That’s done by overriding the partRemoved function.

override protected function partRemoved(partName:String, instance:Object) : void
{
     super.partRemoved(partName, instance);
 
     if( instance == header )
     {
          header.removeEventListener(MouseEvent.CLICK, header_clickHandler);
     }
}

And that’s pretty much all there is to it. This is just a very basic implementation but it should get you started. The design/development flexibility you get by using SkinParts in custom components makes it a great feature. When I finish with my SortableList component I’ll blog it and talk more about things I run into.

Posted in Flex

6 Responses

  1. Lee Probert

    Hey Ryan. I want to build a ‘PagedList’ component but am unsure whether to control the paging aspect of it within the skin in a custom Layout class. What do you think?

  2. thivy

    yeh i made a skin for the same reason.. let me know what u think
    http://www.thivy.com/blog/index.php/archive/flash-builder-iphone-skin/

  3. Lee Probert

    Hi again Ryan. I’ve been trying to make a custom HSlider for a volume control and hacking into the Spark VideoScrubBar component gave me the code I need to add the extra Skin part for displaying the volume bar in the same way as the Scrub displays the played area. Unfortunately when I add the metadata … [SkinPart(required="false")] … to allow the box to be rendered in the skin and override all the methods it suddenly stops my thumb and track from being clickable/draggable. If I comment out the metadata it’s fine.

    Here’s the class …

    package com.gower.amway.components
    {
    import mx.core.IVisualElement;
    import flash.display.DisplayObject;
    import flash.display.DisplayObjectContainer;
    import flash.display.InteractiveObject;
    import flash.events.Event;

    import spark.components.HSlider;

    public class VolumeHSlider extends HSlider
    {
    [SkinPart(required="false")]
    public var volumeArea:IVisualElement;

    private var tempTrackSize:Number = NaN;

    public function VolumeHSlider()
    {
    super();
    }

    override protected function partAdded(partName:String, instance:Object):void
    {
    super.partAdded(partName, instance);

    if (instance == volumeArea)
    {
    if (volumeArea is InteractiveObject)
    InteractiveObject(volumeArea).mouseEnabled = false;
    if (volumeArea is DisplayObjectContainer)
    DisplayObjectContainer(volumeArea).mouseChildren = false;

    invalidateDisplayList();
    }
    else if (instance == track)
    {
    tempTrackSize = NaN;
    }
    }

    override protected function updateDisplayList(unscaledWidth:Number,
    unscaledHeight:Number):void
    {
    super.updateDisplayList(unscaledWidth, unscaledHeight);

    sizeVolumeArea(valueToPosition(value) + thumbSize);
    }

    override protected function track_updateCompleteHandler(event:Event):void
    {
    super.track_updateCompleteHandler(event);

    if (trackSize != tempTrackSize)
    {
    sizeVolumeArea(valueToPosition(value) + thumbSize);
    tempTrackSize = trackSize;
    }
    }

    protected function sizeVolumeArea(volumeAreaSize:Number):void
    {
    if (volumeArea)
    DisplayObject(volumeArea).width = Math.round(volumeAreaSize);
    }
    }
    }

  4. Ryan Graff

    Getter/Setters instead of partAdded/Removed??? Why or Why not???

  5. Joshua White

    Hi Ryan,

    I’ve been working with F4 skins a while and I have an opinion question. In your example above you add the event listener in your skin class to re-order the DATA in your list. So about the function :

    header_clickHandler(event:MouseEvent)

    Do you feel the best practice to implement the list sort logic would be to run the function from the skin like: hostComponent.sortAlpha()

    Or would it better to dispatch another bubbles event for the host component to catch and then run sortAlpha()

    I’m of the opinion that we should certainly not implement sortAlpha function within the skin. And in MVC patterns it is not good for the view to directly call the controller. So then the best way to decouple the pair is to have the skin bubble up the event. This seems a bit over the top. (and repeat!)

    Another thought is to add the event to the button from the SkinnableListComponent itself. That could be considered to be bad practice by some, but keeps the skin completely clean of AS3 and the catalyst skin designer happier. Probably on creation complete?

    I debate this a lot and thought to see if you or the community might have an opinion.

  6. ma83

    I’m an RTL (Right-To-Left) developer. What if I wanted to create a DropDownList (or any other similar component) where the drop-down arrow is on the left and not on the right? How can I do that through a custom component? Or is there another way?

Leave a Comment

Please note: Comment moderation is enabled and may delay your comment. There is no need to resubmit your comment.

About Ryan Stewart – Rich Internet Application Mountaineer

A blog by a Platform Evangelist at Adobe covering Adobe's RIA platform. Includes posts about Adobe Flex, Adobe AIR, ColdFusion, LiveCycle, Thermo, and everything in between.