Implementing a Spark component

When you create a custom component in ActionScript, you have to override the methods of UIComponent and, if the component is skinnable, of SkinnableComponent.

Basic component structure

The following example shows the basic structure of a skinnable Spark component:

package myComponents 
{ 
    public class MyComponent extends SkinnableComponent 
    {     
        .... 
    } 
}

You must define your ActionScript custom components within a package. The package reflects the directory location of your component within the directory structure of your application.

The class definition of your component must be prefixed by the public keyword. A file that contains a class definition can have one, and only one, public class definition, although it can have additional internal class definitions. Place any internal class definitions at the bottom of your source file below the closing curly brace of the package definition.

Implementing the constructor

Your ActionScript class should define a public constructor method for a class that is a subclass of the UIComponent class, or a subclass of any child of the UIComponent class. The constructor has the following characteristics:

  • No return type

  • Declared public

  • No arguments

  • Calls the super() method to invoke the superclass’ constructor

Each class can contain only one constructor method; ActionScript does not support overloaded constructor methods. For more information, see Defining the constructor.

Use the constructor to set the initial values of class properties. For example, you can set default values for properties and styles, or initialize data structures, such as Arrays. You can also set the skinClass style to the name of your skin class.

Do not create child display objects in the constructor; you should use it only for setting initial properties of the component. If your component creates child components, create them in the skin class.

Implementing the commitProperties() method for Spark components

You use the commitProperties() method to coordinate modifications to component properties. Most often, you use it with properties that affect how a component appears on the screen.

Flex schedules a call to the commitProperties() method when a call to the invalidateProperties() method occurs. The commitProperties() method executes during the next render event after a call to the invalidateProperties() method. When you use the addElement() method to add a component to a container, Flex automatically calls the invalidateProperties() method.

The typical pattern for defining component properties is to define the properties by using getter and setter methods, as the following example shows:

// Define a private variable for the alignText property. 
private var _alignText:String = "right"; 
 
// Define a flag to indicate when the _alignText property changes. 
private var bAlignTextChanged:Boolean = false; 
 
// Define getter and setter methods for the property.         
public function get alignText():String { 
        return _alignText; 
} 
 
public function set alignText(t:String):void { 
    _alignText = t; 
    bAlignTextChanged = true; 
 
    // Trigger the commitProperties(), measure() method. 
    invalidateProperties(); 
    invalidateSize(); 
} 
 
// Implement the commitProperties() method.  
override protected function commitProperties():void { 
    super.commitProperties(); 
     
    // Check whether the flag indicates a change to the alignText property.  
    if (bAlignTextChanged) { 
        // Reset flag. 
        bAlignTextChanged = false; 
 
        // Handle alignment change 
        ...  
 
        // If necessary, call invalidateDisplayList() to update the display. 
        invalidateDisplayList();  
    } 
}

As you can see in this example, the setter method modifies the property, calls the invalidateProperties() method, and then returns. The setter itself does not perform any calculations based on the new property value. This design lets the setter method return quickly, and leaves any processing of the new value to the commitProperties() method.

The commitProperties() method in the previous example process the changes to the property, then calls the invalidateDisplay() method to cause the component to update its display. The call to the invalidateDisplay() method is only necessary if the component has to update its display based on the property change.

The main advantages of using the commitProperties() method are the following:

  • To coordinate the modifications of multiple properties so that the modifications occur synchronously.

    For example, you might define multiple properties that control the text displayed by the component, such as the alignment of the text within the component. A change to either the text or the alignment property requires Flex to update the appearance of the component. However, if you modify both the text and the alignment, you want Flex to perform any calculations for sizing or positioning the component once, when the screen updates.

    Therefore, you use the commitProperties() method to calculate any values based on the relationship of multiple component properties. By coordinating the property changes in the commitProperties() method, you can reduce unnecessary processing overhead.

  • To coordinate the sequence that you set properties.

    Some properties may have to be set in a particular sequence. In the commitProperties() method, determine which properties are being modified, and, if necessary, set them in the proper order.

  • To coordinate multiple modifications to the same property.

    You do not necessarily want to perform a complex calculation every time a user updates a component property. For example, users modify the icon property of the Button control to change the image displayed in the button. Calculating the label position based on the presence or size of an icon can be a computationally expensive operation that you want to perform only when necessary.

    To avoid this behavior, you use the commitProperties() method to perform the calculations. Flex calls the commitProperties() method when it updates the display. That means you perform the calculations once when Flex updates the screen, regardless of the number of times the property changed between screen updates.

The following example shows how you can handle two related properties in the commitProperties() method:

// Define a private variable for the text property. 
private var _text:String = "ModalText"; 
private var bTextChanged:Boolean = false; 
         
// Define the getter method.  
public function get text():String { 
        return _text; 
} 
 
//Define the setter method to call invalidateProperties()  
// when the property changes.  
public function set text(t:String):void { 
    _text = t; 
    bTextChanged = true; 
    invalidateProperties(); 
} 
 
// Define a private variable for the alignText property. 
private var _alignText:String = "right"; 
private var bAlignTextChanged:Boolean = false; 
         
public function get alignText():String { 
        return _alignText; 
} 
     
public function set alignText(t:String):void { 
    _alignText = t; 
    bAlignTextChanged = true; 
    invalidateProperties(); 
    invalidateSize(); 
} 
     
// Implement the commitProperties() method.  
override protected function commitProperties():void { 
    super.commitProperties(); 
 
    // Check whether the flags indicate a change to both properties.  
    if (bTextChanged && bAlignTextChanged) { 
        // Reset flags. 
        bTextChanged = false; 
        bAlignTextChanged = false; 
 
        // If necessary, update the dispaly. 
        invalidateDisplayList(); 
    } 
     
    // Check whether the flag indicates a change to the text property.  
    if (bTextChanged) { 
        // Reset flag. 
        bTextChanged = false; 
 
        // If necessary, update the dispaly. 
        invalidateDisplayList(); 
    } 
 
    // Check whether the flag indicates a change to the alignText property.  
    if (bAlignTextChanged) { 
        // Reset flag. 
        bAlignTextChanged = false; 
 
        // If necessary, update the dispaly. 
        invalidateDisplayList(); 
    } 
}

Implementing the updateDisplayList() method for Spark components

The updateDisplayList() method sizes and positions parts of the component based on all previous property and style settings. The parent container for the component determines the size of the component itself. You rarely have to implement this method for Spark components.

A component does not appear on the screen until its updateDisplayList() method gets called. Flex schedules a call to the updateDisplayList() method when a call to the invalidateDisplayList() method occurs. The updateDisplayList() method executes during the next render event after a call to the invalidateDisplayList() method. When you use the addElement() method to add a component to a container, Flex automatically calls the invalidateDisplayList() method.

In general, all visual aspects of a Spark component are controlled by the skin class. But, there are times when the component must participate in the visual display. The only time a Spark component implements the updateDisplayList() method is when the component has view-specific knowledge that it must control. For example, the slider thumb of the Spark HSlider and VSlider controls is positioned by the updateDisplayList() method.

Make sure to perform as much visual display as possible in the skin. For example, the updateDisplayList() method of the HSlider component only sets the x position of the slider thumb. The skin class sets the y position.

The main uses of the updateDisplayList() method are the following:

  • To set the size and position of the elements of the component for display.

    Many components are made up of one or more child components, or have properties that control the display of information in the component.

    To size components in the updateDisplayList() method, you use the setActualSize() method, not the sizing properties, such as width and height. To position a component, use the move() method, not the x and y properties.

  • To draw any visual elements necessary for the component.

    Components support many types of visual elements such as graphics, styles, and borders. Within the updateDisplayList() method, you can add these visual elements, use the Flash drawing APIs, and perform additional control over the visual display of your component.

The updateDisplayList() method has the following signature:

protected function updateDisplayList(unscaledWidth:Number, 
    unscaledHeight:Number):void

The properties have the following values:

unscaledWidth 
Specifies the width of the component, in pixels, in the component’s coordinates, regardless of the value of the scaleX property of the component. This is the width of the component as determined by its parent container.

unscaledHeight 
Specifies the height of the component, in pixels, in the component’s coordinates, regardless of the value of the scaleY property of the component. This is the height of the component as determined by its parent container.

Scaling occurs in Flash Player or AIR, after updateDisplayList() executes. For example, a component with an unscaledHeight value of 100, and with a scaleY property of 2.0, appears 200 pixels high in Flash Player or AIR.

Implementing the partAdded() and partRemoved() methods for skinnable components for Spark components

Some skinnable components are composed of one or more subcomponents. For example, a NumericStepper component contains a subcomponent for an up button, a down button, and a text area.

The component class is responsible for controlling the behavior of the subcomponents. The skin class is responsible for defining the subcomponents, including the appearance of the component, its subcomponents, and any other visual aspects of the component.

Flex clearly defines the relationship between the component class and the skin class. The component class must do the following:
  • Identify the skin parts that it expects with the [SkinPart] metadata tag. The skin parts are implemented in the skin file. For more information on using the [SkinPart] metadata tag, see SkinPart metadata tag.

  • Identify the view states that the component supports with the [SkinStates] metadata tag. For more information on the [SkinState] metadata tag, see SkinState metadata tag.

  • Use CSS styles to associate the skin class with the component.

The skin class must do the following:
  • Specify the component name with the [HostComponent] metadata tag. While the [HostComponent] metadata tag is not required, it is strongly recommended. For more information on the [HostComponent] metadata tag, see HostComponent metadata tag.

  • Declare the view states, and define their appearance.

  • Define the appearance of the skin parts. The skin parts must use the same name as the corresponding skin-part property in the component.

Flex calls the partAdded() and partRemoved() methods automatically when a skin is created or destroyed. You typically override the partAdded() method to attach event handlers to a skin part, configure a skin part, or perform other actions when a skin part is added. You implement the partRemoved() method to remove the even handlers added in partAdded().

In the component class, define skin parts as properties. In the following example, the component defines two required skin parts:
// Define the skin parts. 
[SkinPart(required="true")] 
public var modeButton:Button; 
 
[SkinPart(required="true")] 
public var textInput:RichEditableText;

The first skin part defines a Button control, and the second defines a RichEditableText control. While the skin parts are defined as properties of the component, component users do not directly modify them. The skin class defines their implementation and appearance. For more information on defining skin parts, see Skin parts.

In your implementation of the partAdded() method, you first use super to invoke the partAdded() method of the base class. Then, determine the skin part that was added, and configure it. Use the property name of the skin part to reference it. In this example, you set properties on the skin part and add event listeners to it:
override protected function partAdded(partName:String, instance:Object):void { 
    super.partAdded(partName, instance); 
 
    if (instance == textInput) { 
        textInput.editable = false; 
        textInput.text= _text; 
        textInput.addEventListener("change", modeButton_clickHandler); 
    } 
 
    if (instance == modeButton) { 
        modeButton.label = "Toggle Editing Mode"; 
        modeButton.addEventListener("click", modeButton_changeHandler); 
    } 
}
In your implementation of the partRemoved() method, use super to invoke the partRemoved() method of the base class. You can then remove the event listeners added by the partAdded() method:
override protected function partRemoved(partName:String, instance:Object):void { 
    super.partRemoved(partName, instance); 
 
    if (instance == textInput) { 
        textInput.removeEventListener("change", modeButton_clickHandler); 
    } 
 
    if (instance == modeButton) { 
        modeButton.removeEventListener("click", modeButton_changeHandler); 
    } 
}

Implementing the getCurrentSkinState() method for skinnable components for Spark components

A component must identify the view states that its skin supports. Use the [SkinState] metadata tag to define the view states in the component class. This tag has the following syntax:
[SkinState("stateName")]

Specify the metadata before the class definition. For more information on the [SkinState] metadata tag, see SkinState metadata tag.

The following example defines two view states for the component named ModalTextStates:
[SkinState("normal")] 
[SkinState("textLeft")] 
public class ModalTextStates extends SkinnableComponent 
{ 
    ...
The component’s skin class then define these view states, as the following example shows:
<?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:mx="library://ns.adobe.com/flex/mx" 
    minWidth="100" minHeight="25"> 
 
    <s:states> 
        <s:State name="normal" /> 
        <s:State name="textLeft"/> 
    </s:states> 
    ...

In your component, implement the getCurrentSkinState() method to set the view state of the skin class. Flex calls the getCurrentSkinState() method automatically from the commitProperties() method.

The getCurrentSkinState() method takes no arguments, and returns a String identifying the view state of the skin. You can use information in the class to determine the new view state. In the following example, you examine the _textPlacement property of the component to determine the view state of the skin:
override protected function getCurrentSkinState():String { 
    var returnState:String = "normal"; 
 
    // Use information in the class to determine the new view state of the skin class. 
    if (_textPlacement == "left")     { 
        returnState = "textLeft"; 
    } 
    return returnState; 
}