The following example shows how you can set the close button skin on the Spark TitleWindow container in Flex 4 by setting the skinClass style on the Spark TitleWindowSkin and TitleWindowCloseButtonSkin skins.

The following example(s) require Flash Player 10 and the Adobe Flex 4 SDK. To download the Adobe Flash Builder 4 trial, see http://www.adobe.com/products/flex/. To download the latest nightly build of the Flex 4 SDK, see http://opensource.adobe.com/wiki/display/flexsdk/Download+Flex+4.
For more information on getting started with Flex 4 and Flash Builder 4, see the official Adobe Flex Team blog.

<?xml version="1.0" encoding="utf-8"?>
<!-- http://blog.flexexamples.com/2010/04/04/changing-the-close-button-skin-on-the-spark-titlewindow-container-in-flex-4/ -->
<s:Application name="Spark_TitleWindow_skin_closeButton_test"
        xmlns:fx="http://ns.adobe.com/mxml/2009"
        xmlns:s="library://ns.adobe.com/flex/spark"
        xmlns:mx="library://ns.adobe.com/flex/mx">
    <s:layout>
        <s:VerticalLayout horizontalAlign="center" verticalAlign="middle" />
    </s:layout>
    <s:controlBarContent>
        <s:CheckBox id="chckBx" label="enabled" selected="true" />
    </s:controlBarContent>
 
    <s:TitleWindow id="ttlWndw"
            title="Spark TitleWindow title"
            skinClass="skins.CustomTitleWindowSkin"
            enabled="{chckBx.selected}"
            width="320" height="240">
        <s:Label text="Spark Panel contents"
                left="10" right="10" top="10" />
    </s:TitleWindow>
 
</s:Application>

And the custom Spark TitleWindow skin class, skins/CustomTitleWindowSkin.mxml, is as follows:

<?xml version="1.0" encoding="utf-8"?>
<!-- http://blog.flexexamples.com/2010/04/04/changing-the-close-button-skin-on-the-spark-titlewindow-container-in-flex-4/ -->
<s:SparkSkin name="CustomTitleWindowSkin"
        xmlns:fx="http://ns.adobe.com/mxml/2009"
        xmlns:s="library://ns.adobe.com/flex/spark" 
        xmlns:fb="http://ns.adobe.com/flashbuilder/2009"
        blendMode="normal" mouseEnabled="false"
        minWidth="76" minHeight="76"
        alpha.disabled="0.5" alpha.disabledWithControlBar="0.5">
    <!-- states -->
    <s:states>
        <s:State name="normal" />
        <s:State name="inactive" stateGroups="inactiveGroup" />
        <s:State name="disabled" />
        <s:State name="normalWithControlBar" stateGroups="withControls" />
        <s:State name="inactiveWithControlBar" stateGroups="withControls, inactiveGroup" />
        <s:State name="disabledWithControlBar" stateGroups="withControls" />
    </s:states>
 
    <fx:Metadata>
        [HostComponent("spark.components.TitleWindow")]
    </fx:Metadata> 
 
    <fx:Script fb:purpose="styling">
        <![CDATA[
            /* Define the skin elements that should not be colorized. 
            For panel, border and title background are skinned, but the content area and title text are not. */
            static private const exclusions:Array = ["background", "titleDisplay", "contentGroup"];
 
            override public function get colorizeExclusions():Array {
                return exclusions;
            }
 
            override protected function initializationComplete():void {
                useChromeColor = true;
                super.initializationComplete();
            }
 
            override protected function updateDisplayList(unscaledWidth:Number, unscaledHeight:Number):void {
                if (getStyle("borderVisible") == true) {
                    border.visible = true;
                    background.left = background.top = background.right = background.bottom = 1;
                    contents.left = contents.top = contents.right = contents.bottom = 1;
                } else {
                    border.visible = false;
                    background.left = background.top = background.right = background.bottom = 0;
                    contents.left = contents.top = contents.right = contents.bottom = 0;
                }
 
                dropShadow.visible = getStyle("dropShadowVisible");
 
                var cr:Number = getStyle("cornerRadius");
                var withControls:Boolean = 
                    (currentState == "disabledWithControlBar" || 
                        currentState == "normalWithControlBar" ||
                        currentState == "inactiveWithControlBar");
 
                if (cornerRadius != cr) {
                    cornerRadius = cr;
 
                    dropShadow.tlRadius = cornerRadius;
                    dropShadow.trRadius = cornerRadius;
                    dropShadow.blRadius = withControls ? cornerRadius : 0;
                    dropShadow.brRadius = withControls ? cornerRadius : 0;
 
                    setPartCornerRadii(topMaskRect, withControls); 
                    setPartCornerRadii(border, withControls); 
                    setPartCornerRadii(background, withControls);
                }
 
                if (bottomMaskRect) {
                    setPartCornerRadii(bottomMaskRect, withControls); 
                }
 
                borderStroke.color = getStyle("borderColor");
                borderStroke.alpha = getStyle("borderAlpha");
                backgroundFill.color = getStyle("backgroundColor");
                backgroundFill.alpha = getStyle("backgroundAlpha");
 
                super.updateDisplayList(unscaledWidth, unscaledHeight);
            }
 
            private function setPartCornerRadii(target:Rect, includeBottom:Boolean):void {
                target.topLeftRadiusX = cornerRadius;
                target.topRightRadiusX = cornerRadius;
                target.bottomLeftRadiusX = includeBottom ? cornerRadius : 0;
                target.bottomRightRadiusX = includeBottom ? cornerRadius : 0;
            }
 
            private var cornerRadius:Number;
        ]]>
    </fx:Script>
 
    <!--- drop shadow can't be hittable so it stays sibling of other graphics @private-->
    <s:RectangularDropShadow id="dropShadow" blurX="20" blurY="20" alpha="0.32" 
                             alpha.inactiveGroup="0.22" distance="11"  distance.inactiveGroup="7"
                             angle="90" color="0x000000" left="0" top="0" right="0" bottom="0"/>
 
    <!--- drop shadow can't be hittable so all other graphics go in this group -->
    <s:Group left="0" right="0" top="0" bottom="0">
 
        <!--- top group mask @private-->
        <s:Group left="1" top="1" right="1" bottom="1" id="topGroupMask">
            <!--- @private-->
            <s:Rect id="topMaskRect" left="0" top="0" right="0" bottom="0">
                <s:fill>
                    <s:SolidColor alpha="0"/>
                </s:fill>
            </s:Rect>
        </s:Group>
 
        <s:Group left="1" top="1" right="1" bottom="1" id="bottomGroupMask" 
                 includeIn="withControls">
            <!--- @private-->
            <s:Rect id="bottomMaskRect" left="0" top="0" right="0" bottom="0">
                <s:fill>
                    <s:SolidColor alpha="0"/>
                </s:fill>
            </s:Rect>
        </s:Group>
 
        <s:Rect id="border" left="0" right="0" top="0" bottom="0" >
            <s:stroke>
                <!--- Defines the TitleWindowSkin class's border stroke. The default value is 1. -->
                <s:SolidColorStroke id="borderStroke" weight="1" />
            </s:stroke>
        </s:Rect>
 
        <!-- layer 2: background fill -->
        <!--- Defines the appearance of the TitleWindowSkin class's background. -->
        <s:Rect id="background" left="1" top="1" right="1" bottom="1">
            <s:fill>
                <!--- Defines the TitleWindowSkin class's background fill. The default color is 0xFFFFFF. -->
                <s:SolidColor id="backgroundFill" color="#FFFFFF"/>
            </s:fill>
        </s:Rect>
 
        <!-- layer 3: contents -->
        <!--- Contains the vertical stack of title bar content and control bar. -->
        <s:Group left="1" right="1" top="1" bottom="1" id="contents">
            <s:layout>
                <s:VerticalLayout gap="0" horizontalAlign="justify" />
            </s:layout>
 
            <s:Group id="topGroup" mask="{topGroupMask}">
 
                <!--- layer 0: title bar fill @private -->
                <s:Rect id="tbFill" left="0" right="0" top="0" bottom="1">
                    <s:fill>
                        <s:LinearGradient rotation="90">
                            <s:GradientEntry color="0xD2D2D2"
                                             color.inactiveGroup="0xEAEAEA"/>
                            <s:GradientEntry color="0x9A9A9A"
                                             color.inactiveGroup="0xCECECE"/>
                        </s:LinearGradient>
                    </s:fill>
                </s:Rect>
 
                <!--- layer 1: title bar highlight @private -->
                <s:Rect id="tbHilite" left="0" right="0" top="0" bottom="0">
                    <s:stroke>
                        <s:LinearGradientStroke rotation="90" weight="1">
                            <s:GradientEntry color="0xE6E6E6" />
                            <s:GradientEntry color="0xFFFFFF" alpha="0.22"/>
                        </s:LinearGradientStroke>
                    </s:stroke>
                    <s:fill>
                        <s:LinearGradient rotation="90">
                            <s:GradientEntry color="0xFFFFFF" alpha="0.15" />
                            <s:GradientEntry color="0xFFFFFF" alpha="0.15" ratio="0.44"/>
                            <s:GradientEntry color="0xFFFFFF" alpha="0" ratio="0.4401"/>
                        </s:LinearGradient>
                    </s:fill>
                </s:Rect>
 
                <!--- layer 2: title bar divider @private -->
                <s:Rect id="tbDiv" left="0" right="0" height="1" bottom="0">
                    <s:fill>
                        <s:SolidColor color="0x000000" alpha="0.75" />
                    </s:fill>
                </s:Rect>
 
                <!-- layer 3: text -->
                <s:Label id="titleDisplay" maxDisplayedLines="1"
                         left="9" right="36" top="1" bottom="0" minHeight="30"
                         verticalAlign="middle" fontWeight="bold" />
 
                <!-- layer 4: moveArea -->
                <s:Group id="moveArea" left="0" right="0" top="0" bottom="0" />
 
                <s:Button id="closeButton" skinClass="skins.CustomTitleWindowCloseButtonSkin"
                          width="15" height="15" right="7" top="7"
                          enabled="{hostComponent.enabled}"/>
            </s:Group>
 
            <!--
            Note: setting the minimum size to 0 here so that changes to the host component's
            size will not be thwarted by this skin part's minimum size.   This is a compromise,
            more about it here: http://bugs.adobe.com/jira/browse/SDK-21143
            -->
            <s:Group id="contentGroup" width="100%" height="100%" minWidth="0" minHeight="0">
            </s:Group>
 
            <s:Group id="bottomGroup" minWidth="0" minHeight="0" 
                     includeIn="withControls">  
 
                <s:Group left="0" right="0" top="0" bottom="0" mask="{bottomGroupMask}">
 
                    <!-- layer 0: control bar divider line -->
                    <s:Rect left="0" right="0" top="0" height="1" alpha="0.22">
                        <s:fill>
                            <s:SolidColor color="0x000000" />
                        </s:fill>
                    </s:Rect>
 
                    <!-- layer 1: control bar highlight -->
                    <s:Rect left="0" right="0" top="1" bottom="0">
                        <s:stroke>
                            <s:LinearGradientStroke rotation="90" weight="1">
                                <s:GradientEntry color="0xFFFFFF" />
                                <s:GradientEntry color="0xD8D8D8" />
                            </s:LinearGradientStroke>
                        </s:stroke>
                    </s:Rect>
 
                    <!-- layer 2: control bar fill -->
                    <s:Rect left="1" right="1" top="2" bottom="1">
                        <s:fill>
                            <s:LinearGradient rotation="90">
                                <s:GradientEntry color="0xEDEDED"/>
                                <s:GradientEntry color="0xCDCDCD"/>
                            </s:LinearGradient>
                        </s:fill>
                    </s:Rect>
                </s:Group>
 
                <s:Group id="controlBarGroup" left="0" right="0" top="1" bottom="1" minWidth="0" minHeight="0">
                    <s:layout>
                        <s:HorizontalLayout paddingLeft="10" paddingRight="10" paddingTop="7" paddingBottom="7" gap="10" />
                    </s:layout>
                </s:Group>
            </s:Group>
        </s:Group>
    </s:Group>
 
</s:SparkSkin>

And the custom Spark TitleWindowCloseButton skin, skins/CustomTitleWindowCloseButtonSkin.mxml, is as follows:

<?xml version="1.0" encoding="utf-8"?>
<!-- http://blog.flexexamples.com/2010/04/04/changing-the-close-button-skin-on-the-spark-titlewindow-container-in-flex-4/ -->
<s:SparkSkin name="CustomTitleWindowCloseButtonSkin"
        xmlns:fx="http://ns.adobe.com/mxml/2009"
        xmlns:s="library://ns.adobe.com/flex/spark"
        xmlns:fb="http://ns.adobe.com/flashbuilder/2009"
        alpha.disabled="0.5">
    <!-- states -->
    <s:states>
        <s:State name="up" />
        <s:State name="over"/>
        <s:State name="down" />
        <s:State name="disabled" />
    </s:states>
 
    <!-- host component -->
    <fx:Metadata>
        [HostComponent("spark.components.Button")]
    </fx:Metadata> 
 
    <fx:Script fb:purpose="styling">
        <![CDATA[
            /* Define the skin elements that should not be colorized. 
            For closeButton, the graphics are colorized but the x is not. */
            static private const exclusions:Array = ["xSymbol"];
 
            override public function get colorizeExclusions():Array {
                return exclusions;
            }
 
            /* Define the symbol fill items that should be colored by the "symbolColor" style. */
            static private const symbols:Array = [];
 
            override public function get symbolItems():Array {
                return symbols;
            }
        ]]>
    </fx:Script>
 
    <!--- Defines the appearance of the x in the close button. -->
    <s:Group top="1" left="1" id="xSymbol">
        <s:BitmapImage source="@Embed('assets/bullet_green.png')"
                source.over="@Embed('assets/bullet_yellow.png')"
                source.down="@Embed('assets/bullet_red.png')"
                source.disabled="@Embed('assets/bullet_black.png')" />
    </s:Group>
 
</s:SparkSkin>

This entry is based on a beta version of the Flex 4 SDK and therefore is very likely to change as development of the Flex SDK continues. The API can (and will) change causing examples to possibly not compile in newer versions of the Flex 4 SDK.

 
Tagged with:
 
About The Author

Peter deHaan

Peter deHaan currently works for Adobe on the Flex SDK QA team. While not working on Flex, Flash, and ColdFusion applications, Peter enjoys making up bios and writing in 3rd person. Peter's rarely updated blog can be found at blogs.adobe.com/pdehaan/, actionscriptexamples.com, airexamples.com, and coldfusionexamples.com.

3 Responses to Changing the close button skin on the Spark TitleWindow container in Flex 4

  1. sephiroth says:

    Good Post. I’m migrating some of my application from flex 3.5 and the revolution in the skin system is approching me.

    Am i the only to not get comfortable with the new skinning and with the verbosity of code with flex 4? Code reuse for flex 4 skins is near zero…

    Sephiroth

  2. Robert Trimmons says:

    Where are the png assets?

  3. Peter deHaan says:

    The icons are from the excellent famfamfam.com “Silk” icon set: http://www.famfamfam.com/lab/icons/silk/

    Peter

Leave a Reply

Your email address will not be published.

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> <pre lang="" line="" escaped="">

Anti-Spam Protection by WP-SpamFree