It’s an Interface; It’s a Code-Behind; It’s a … Skin?

I managed to avoid skinning components in Flex 3 (I called it “dancing kitties”). I just made the components work. I tried to ignore the changes in Flex 4 skins, but Spark components are very different than MX components, although Adobe has succeeded in making the changes apparently minor.

In Flex 2 and 3, “skins” were CSS styles and images and, rarely, bits of code that created graphics.

What Flex 4 Skins Are

Flex 4 skins are ActionScript classes that are completely responsible for presenting the data from another ActionScript class.  Custom components can inherent behavior from their class ancestors (UIComponent, Group, etc.), but builds their appearance by choosing or creating a custom skin class. The component has the properties and methods to manage the component’s behavior and data, and the skin class presents it to the user. A skin class usually descends from the ancestor class Skin (itself a child of Group) and contains other components (e.g. Labels, Images, RichTextEdit) to display and gather data, (somewhat like the way a custom item-renderer contains other components). The nesting can seem a little recursive, but it has the same advantages as any other object oriented protocol: each object does its thing with loose linkages to other objects.

It’s Code-Behind

The relationship between a component and its skin is like a code-behind pattern: specified component class variables refer to the skin class’ sub-components by name. As the skin class constructs, it passes each of its sub-compents (e.g. Buttons) to the host component. The host component does two things: first, if it finds a variable annotated with a [SkinPart], it assigns the instance to the value from the skin. Second, it calls the addPart() method so the component can add event listeners, etc. For example, if a skin class used with a Button has a sub-component with the id “labelDisplay”, that sub-component’s “text” property will get the Button.label value via the ButtonBase.setContent() method:

The Component The Skin
[SkinPart(required="false")]
public var labelDisplay : IDisplayText;   // must have a .text property
<s:Label id="labelDisplay" />
protected function setContent(value : String) : void
{
    _content = value;  //    this stores the value in the component

    if (labelDisplay != null)  // it's not required, so it could be null
        labelDisplay.text = value;
}
The Label component displays the new value

It’s an interface

And I mean with a lower-case “i”: it’s is not an ActionScript Interface It is an agreement between the component and the skin so they can work together (actually, the component requires and the skin fulfills):

  • [SkinPart(required="true")] or [SkinPart]on the component requires the skin to have a sub-component with a specific name; the class will throw an exception during creation without it
  • [SkinPart(required="false")] on the component will connect to a skin’s sub-component with a specific name if it’s present

It’s a View

So skins have evolved from being styles and bitmaps to more of a MVC-type view, and the component acts as a controller and model. The component keeps a copy of all the visible data (like label texts) so it can change and initialize skins at any time. When a skin initializes, the component gets a notification for each sub-component of the skin object, and the component can add an event listener or set its value. While the component can query the skin about constraint-based position and sizes, the less the component knows about the view, the better.

Likewise, the less the skin knows about the component, the better. The component handles all the interaction with the outside world: methods to set and filter the data, validation of property values, etc. The skin gets data values and occasionally styles and top-level dimensions. The component uses its code-behind references directly to the skin’s sub-components to set property values and get events.

Binding Optional

While binding is very cool, this protocol does not usually use events and watchers and binding stuff: the code in the component explicitly assigns values to its references of the skin’s sub-components (e.g. SkinnableTextBase.setPrompt()). The skin can bind to the component’s properties (e.g. hostComponent.title), but it’s unusual.

The Component’s View of the Skin

These are the states, properties, and methods of the component that give it access to and control over the skin.

skin class
(will inherit from ancestor classes)
.skinClass
required skin states [SkinState("xxx")]
skin sub-components
[SkinPart(required="true")]
public var closeIcon : Button;

[SkinPart(required="false")]
public var labelDisplay : IDisplayText;   // must have a .text property
add/remove event listeners to skin objects
override protected function partAdded(partName : String, instance : Object) : void
{
    super.partAdded(partName,instance);
    if (instance == closeIcon)
    {
        closeIcon.addEventListener(MouseEvent.CLICK, onCloseIcon);
        closeIcon.source = getStyle(“icon”);
    }
}

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

    if (instance == closeIcon)
        closeIcon.removeEventListener(MouseEvent.CLICK, onCloseIcon);
}
skin instance
(rarely used)
.skin
supporting methods
SkinnableComponent.invalidateSkinState();
// this forces a call to getCurrentSkinState()
other miscellaneous overrides
override protected function getCurrentSkinState() : String { 

//    translate component states into skin states
//    note: the base class' method, 
//            SkinnableComponent.getCurrentSkinState(), returns null
}

override public function styleChanged(styleProp : String) : void
{
    super.styleChanged(styleProp);

    if (styleProp == “icon”)
        closeIcon.source = getStyle(“icon”);
}
data values push values into the skin using code-behind
currentState the component sets the skin’s state to reflect some aspect (e.g. disabled)

The Skin’s View of the Component

These are the properties the skin used to receive and communicate with the host. In general, the skin assumes that the synchronization with the component will push values into the sub-components and change the skin’s state; it does not reference the .hostComponent directly.

host class [HostComponent("spark.components.Button")]
host instance
(rarely used)
.hostComponent
data values pushed into values by the component
currentState the component sets the skin’s state to reflect some aspect (e.g. disabled)

Skin State

The component can require specific skin states (e.g. “normal”, “disabled”). It uses those states to instruct the skin to change appearance, but leaves the specifics up to the skin. Adobe could have used an event, an expected method, or some other way to signal the skin; the advantage of using states is that it’s easy to set up an MXML component to respond to states. The base component’s invalidateSkinState() method (called when the component’s state change, for example) triggers a call to getCurrentSkinState(). That method examines the component’s properties and returns the skin state that fits.

For example, SkinnableTextBase requires the skin to have states “normalWithPrompt” and “disabledWithPrompt”, and uses them as appropriate in getCurrentSkinState() to have the skin show the .prompt text (using whatever sub-component the skin chooses to show it).

Object-Oriented Skins … Or Not

Adobe has been uncharacteristically forceful in their declaration that  we should not create inheritance hierarchies of skin classes; Adobe wants us to copy and paste when creating new skin classes. Part of that is political: they have lots of tools for designers to work with developers, and designers do not sub-class anything. Although I am a OOP-freak, I see their point: skins are so thin (i.e. they have very little behavior implemented in code) that the logistical overhead of having some parts in the ancestor and some in a descendant does not have much benefit to balance it. A well-designed skin class can use CSS as a replaceable properties file, and that can bundle a lot of behavior in a thin class without sub-classes.

 

2 thoughts on “It’s an Interface; It’s a Code-Behind; It’s a … Skin?

  1. Unrelated question, but this is the only way I could find to contact you.

    You had posted a question to stack overflow about testing a double-metaphone algorithm written AS3; and your question implied that you’d be releasing that once it was done.

    So naturally,I was wondering if you’d completed and published the AS3 double-metaphone algorithm?

    Thank you,

    Marc

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>