Search code examples
apache-flexbuttonextendflex-spark

Flex 4: Is there a simple way to extend Spark button?


I created a somewhat custom Spark button by doing the File > New > MXML skin and basing it on spark.components.button. The problem is that I need to add an extra text field to the button component and dynamically change that text...but of course, the property isn't recognized on a Spark Button.

Is there a simple way to add this field to my custom button skin & its property so it can be addressed? If not, is there a simple way to take what I've done and just extend the Spark Button? I can't seem to find any examples that show how to do it without writing it all up in ActionScript.


Solution

  • I'm glad you asked! This is much easier than you'd think so don't be discouraged! ActionScript is pretty easy once you get the hang of it.

    First of all, let's define what we want. After reading your question I believe you would like to use your button something like this:

    <local:MyCustomButton label="Hello" label2="World!"/>
    

    So let's go over how to make that a reality.

    Now, I would highly suggest extending Button with ActionScript, but it is also possible to do in mxml:

    //MyCustomButton.mxml
    <?xml version="1.0" encoding="utf-8"?>
    <s:Button 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:Button>
    

    Then you can add the SkinParts you need in a <fx:Script>:

    <?xml version="1.0" encoding="utf-8"?>
    <s:Button xmlns:fx="http://ns.adobe.com/mxml/2009" 
              xmlns:s="library://ns.adobe.com/flex/spark" 
              xmlns:mx="library://ns.adobe.com/flex/mx">
    
        <fx:Script>
            <![CDATA[
    
                [SkinPart]
                public var secondLabelDisplay:spark.components.Label;
    
    
            ]]>
        </fx:Script>
    
    
    </s:Button>
    

    So now when you make a skin you should include something like the original label, just with a different ID to reflect your new SkinPart:

    But wait! What text should our second label show?? Well, we will need to add another property that you can set for each individual instance of the button:

    <?xml version="1.0" encoding="utf-8"?>
    <s:Button xmlns:fx="http://ns.adobe.com/mxml/2009" 
              xmlns:s="library://ns.adobe.com/flex/spark" 
              xmlns:mx="library://ns.adobe.com/flex/mx">
    
        <fx:Script>
            <![CDATA[
    
                [SkinPart]
                public var secondLabelDisplay:spark.components.Label;
    
    
                private var _label2:String;
    
                public function get label2():String
                {
                    return _label2;
                }
    
                public function set label2(value:String):void
                {
                    _label2 = value;
                }
    
    
            ]]>
        </fx:Script>
    
    
    </s:Button>
    

    Cool, so now we can set label2 when we use our button, but at this point it won't change the label's actual text property. We need to hook up our label2 to our secondLabelDisplay. We do this by calling invalidateProperties when the label2 changes and then change the label in commitProperties (which will be called because of the invalidateProperties() call):

      <?xml version="1.0" encoding="utf-8"?>
        <s:Button xmlns:fx="http://ns.adobe.com/mxml/2009" 
                  xmlns:s="library://ns.adobe.com/flex/spark" 
                  xmlns:mx="library://ns.adobe.com/flex/mx">
    
            <fx:Script>
                <![CDATA[
    
                    [SkinPart]
                    public var secondLabelDisplay:spark.components.Label;
    
    
                    private var _label2:String;
                    private var label2Changed:Boolean;
    
                    public function get label2():String
                    {
                        return _label2;
                    }
    
                    public function set label2(value:String):void
                    {
                        _label2 = value;
                        label2Changed = true;
                        invalidateProperties();
                    }
    
                    override protected function commitProperties():void
                    {
                        super.commitProperties();
    
                        if(label2Changed)
                        {
                            label2Changed = false;
                                                secondLabelDisplay.text = label2;
                        }
    
                    }
    
    
                ]]>
            </fx:Script>
    
    
        </s:Button>
    

    Lastly, you'll notice that if you change label2 again afte runtime, the label will show the change. But it won't show the change if you set an initial label2 like in our target usage. The Flex team made a special method just for this case, partAdded(). I won't go over too many details about it because there is already a good amount of literature on the subject.

    Finally, here's our finished, custom button awaiting a skin to put it to use:

    <fx:Script>
        <![CDATA[
    
            [SkinPart]
            public var secondLabelDisplay:spark.components.Label;
    
    
            private var _label2:String;
            private var label2Changed:Boolean;
    
            public function get label2():String
            {
                return _label2;
            }
    
            public function set label2(value:String):void
            {
                _label2 = value;
                label2Changed = true;
                invalidateProperties();
            }
    
            override protected function commitProperties():void
            {
                super.commitProperties();
    
                if(label2Changed)
                {
                    label2Changed = false;
                                        secondLabelDisplay.text = label2;
                }
    
            }
    
            override protected function partAdded(partName:String, instance:Object):void
            {
                if(instance == secondLabelDisplay)
                {
                    secondLabelDisplay.text = _label2;
                }
    
            }
    
    
        ]]>
    </fx:Script>
    

    Best of luck!