Why use partAdded rather than HostComponent to call a function? (from MXML) What are the disadvantage and disadvantage.
Note. The major project in Flex 4 was one that required no skinning. A research tool for academic clients. Complex functionalities but no design. The default Spark look and feel was just fine. Other projects in Flex 4 were about taking over from other developers, getting the app to work. Skinnable components were mostly simple (pretty buttons), not composite.
When no Skin is defined, then event listeners can be attached to the host component.
Provided the visual component is written using a separation of Display Object from skin, then the button gets declared in a skin, not in the component. Behaviour/EventListeners should never be declared in the skin. (That I would not consider doing.)
“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.” (source: http://help.adobe.com/en_US/flex/using/WS460ee381960520ad-2811830c121e9107ecb-7ff9.html#WS03d33b8076db57b9-a120b14121ef5f63a3-8000).
If the component is composite, then events cannot be attached when the host component is being initialized. Children have not been created yet. PartAdded and PartRemoved are the best efficient point in the lifecycle to attach these events.
If the component is not composite, like for a skinnable button, then a priori, a click be attached directly to the host component. This is the way it is done in code I had to work in “get this to work” scenarios.
<s:Button skinClass=“path.to.skins.BackButtonSkin” click=“backlickHandler(event)” />
A problem, though, with this type of approach is that a strong reference to the object is created, outside of the object itself, causing the garbage collector to bypass the object.
partAdded, partRemoved offer a cleaner way to manage events.
If the skin is dynamically swapped at runtime (via some “select a theme menu”), then partRemoved will clean up the events from one skin before loading the next one. (The skin has a lifecycle of its own.)
When is partRemoved actually called? What components that have state changes, like an accordion view with non visible panels and subpanels re-created or “destroyed” when creationChildren is false? Not too clear ⇒ Spark Skinnning Specification
The one from a one minute google search is : The flex coder mailing list tells me that Flex 4 skin can't bind to host component property. Alex Harui himself says that “the whole partAdded protocol is there to: 1) avoid the cost of binding, 2) support changing skins at runtime.”
(Alex Harui excels at pointing at intricacies that escape most developers)
Extra. Why is the method that I used to shortcut binding in Flex 3 not adequate in Flex 4?
Each Spark component consists of two classes: a declarative, MXML-based skin class and an ActionScript component class. The skin class contains everything related to the visual appearance of the Spark component, while the ActionScript class contains everything related to the functional logic of the component.
Every Spark component class defines three very important elements: the data the component expects, the constituent parts that make up the component, and the states the component can enter and exit. In the corresponding skin class, the skin defines how that data is visually displayed, how the parts are laid out and visualized, and what the component looks like as it enters and exits different states. These three key elements— data, parts, and states—define the skinning contract upon which Spark is founded.
(source: Spark Intro @Adobe Articles)
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:
The skin class must do the following:
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().
(source: Adobe docs)
Spark built atop of Halo, Skinnable Component extends UIComponent.
Flex 4 component lifecycle, presentation by Mrinal Wadhwa
Numeric Stepper
[SkinPart(required="true")]
/**
* A skin part that defines a TextInput control
* which allows a user to edit the value of
* the NumericStepper component.
* ...
*/
public var textDisplay:TextInput;
/**
* @private
*/
override protected function setValue(newValue:Number):void
{
super.setValue(newValue);
applyDisplayFormatFunction();
}
....
/**
* @private
* Helper method that applies the valueFormatFunction
*/
private function applyDisplayFormatFunction():void
{
if (valueFormatFunction != null)
textDisplay.text = valueFormatFunction(value);
else
textDisplay.text = value.toString();
}
/**
* @private
*/
override protected function partAdded(partName:String, instance:Object):void
{
super.partAdded(partName, instance);
if (instance == textDisplay)
{
textDisplay.addEventListener(FlexEvent.ENTER,
textDisplay_enterHandler);
textDisplay.addEventListener(FocusEvent.FOCUS_OUT,
textDisplay_focusOutHandler);
textDisplay.focusEnabled = false;
textDisplay.maxChars = _maxChars;
// Restrict to digits, minus sign, decimal point, and comma
textDisplay.restrict = "0-9\\-\\.\\,";
textDisplay.text = value.toString();
// Set the the textDisplay to be wide enough to display
// widest possible value.
textDisplay.widthInChars = calculateWidestValue();
}
}
/**
* @private
*/
override protected function partRemoved(partName:String, instance:Object):void
{
super.partRemoved(partName, instance);
if (instance == textDisplay)
{
textDisplay.removeEventListener(FlexEvent.ENTER,
textDisplay_enterHandler);
}
}
Numeric Stepper Skin
<?xml version="1.0" encoding="utf-8"?>
<!--
ADOBE SYSTEMS INCORPORATED
Copyright 2008 Adobe Systems Incorporated
All Rights Reserved.
NOTICE: Adobe permits you to use, modify, and distribute this file
in accordance with the terms of the license agreement accompanying it.
-->
<!--- The default skin class for a Spark NumericStepper component. The skin for the text input field on a NumericStepper
component is defined by the NumericStepperTextInputSkin class.
@see spark.components.NumericStepper
@see spark.skins.spark.NumericStepperTextInputSkin
@langversion 3.0
@playerversion Flash 10
@playerversion AIR 1.5
@productversion Flex 4
-->
<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" minHeight="23" minWidth="40"
alpha.disabled="0.5">
<fx:Metadata>
<![CDATA[
/**
* @copy spark.skins.spark.ApplicationSkin#hostComponent
*/
[HostComponent("spark.components.NumericStepper")]
]]>
</fx:Metadata>
<fx:Script fb:purpose="styling">
/* Define the skin elements that should not be colorized.
For numeric stepper, the skin itself is colorized but the individual parts are not. */
static private const exclusions:Array = ["textDisplay", "decrementButton", "incrementButton"];
/**
* @private
*/
override public function get colorizeExclusions():Array {return exclusions;}
/**
* @private
*/
override protected function initializationComplete():void
{
useChromeColor = true;
super.initializationComplete();
}
private var cornerRadiusChanged:Boolean;
private var borderStylesChanged:Boolean;
/**
* @private
*/
override protected function commitProperties():void
{
super.commitProperties();
if (cornerRadiusChanged)
{
var cr:Number = getStyle("cornerRadius");
if (incrementButton)
incrementButton.setStyle("cornerRadius", cr);
if (decrementButton)
decrementButton.setStyle("cornerRadius", cr);
cornerRadiusChanged = false;
}
if (borderStylesChanged)
{
textDisplay.setStyle("borderAlpha", getStyle("borderAlpha"));
textDisplay.setStyle("borderColor", getStyle("borderColor"));
textDisplay.setStyle("borderVisible", getStyle("borderVisible"));
borderStylesChanged = false;
}
}
/**
* @private
*/
override public function styleChanged(styleProp:String):void
{
var allStyles:Boolean = !styleProp || styleProp == "styleName";
super.styleChanged(styleProp);
if (allStyles || styleProp == "cornerRadius")
{
cornerRadiusChanged = true;
invalidateProperties();
}
if (allStyles || styleProp.indexOf("border") == 0)
{
borderStylesChanged = true;
invalidateProperties();
}
}
</fx:Script>
<s:states>
<s:State name="normal" />
<s:State name="disabled" />
</s:states>
<!--- The default class is NumericStepperIncrementButtonSkin.
@copy spark.components.Spinner#incrementButton
@see spark.skins.spark.NumericStepperIncrementButtonSkin -->
<s:Button id="incrementButton" right="0" top="0" height="50%" tabEnabled="false"
skinClass="spark.skins.spark.NumericStepperIncrementButtonSkin" />
<!--- The default class is NumericStepperDecrementButtonSkin.
@copy spark.components.Spinner#decrementButton
@see spark.skins.spark.NumericStepperDecrementButtonSkin -->
<s:Button id="decrementButton" right="0" bottom="0" height="50%" tabEnabled="false"
skinClass="spark.skins.spark.NumericStepperDecrementButtonSkin" />
<!--- The default class is NumericStepperTextInputSkin.
@copy spark.components.NumericStepper#textDisplay
@see spark.skins.spark.NumericStepperTextInputSkin -->
<s:TextInput id="textDisplay" left="0" top="0" right="18" bottom="0"
skinClass="spark.skins.spark.NumericStepperTextInputSkin" />
</s:SparkSkin>