Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Flex 4 Custom Component - How to notify skin of property changes?

I have a custom Flex 4+ component that I am trying to make and have the skin be aware of changes to a custom property. This property will determine the graphic on the button (and some other visual changes) but the data will change constantly as it will be updated by a timer.

I've looked at untold examples and still seem unable to get the syntax correct or discover how things should be separated. I've looked at overriding commitProperties and the PropertyChangeEvent without success. So I have two questions.

1) How can I get a skin to be notified of a bound property when it changes?

2) If the data for a bound property of the component is an object, will binding work properly if a property of the object changes (or would it be better to pass each property separately)?

Here is a stripped down example of what I'm trying to achieve.

The component looks like this:

<s:ButtonBase 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[
        private var _iconData:String;

        [Bindable]
        public function get iconData():String
        {
            return _iconData;
        }
        public function set iconData(value:String):void
        {
            _iconData = value;
        }
    ]]>
</fx:Script>

I'm calling it like this:

<components:MyButton id="myButton" iconData="{myData.curIconTag}" skinClass="skins.MyButtonSkin" />

I have a lot of different images I could be loading and so I'm afraid the number of states (with the combinations of up/down/over/disabled, etc. may get out of hand so the SetIconDisplay is setting the icon, but the real key is that I have other code in that function that needs to execute when the iconData property changes every X minutes or so. So the skin is something like this:

<s:Skin xmlns:fx="http://ns.adobe.com/mxml/2009" 
    xmlns:s="library://ns.adobe.com/flex/spark" 
    xmlns:fb="http://ns.adobe.com/flashbuilder/2009"
    creationComplete="init()">

<fx:Metadata>
    [HostComponent("components.MyButton")]
</fx:Metadata>

<s:states>
    <s:State name="default" />
    <s:State name="down"/>
    <s:State name="up"/>
    <s:State name="over"/>
    <s:State name="disabled" />
</s:states>

<fx:Script>
    <![CDATA[
        import components.MyButton;

        [Embed(source="images/image1.png")]
        private var icon1:Class;

        [Embed(source="images/image2.png")]
        private var icon2:Class;

        [Embed(source="images/image3.png")]
        private var icon3:Class;

        [Bindable] 
        public var hostComponent:MyButton;

        [Bindable] 
        private var iconClass:Class;

        private function init():void
        {
            iconClass = new Class();
        }

        // how do I get this called when the iconData property on my custom component is changed?
        private function SetIconDisplay():void
        {
            switch (hostComponent.iconData)
            {
                case "apple":
                    iconClass=icon1;
                    break;
                case "orange":
                    iconClass=icon2;
                    break;
                case "grape":
                    iconClass=icon3;
                    break;
            }
        }
    ]]>        
</fx:Script>

<s:BitmapImage source="{iconClass}" x="0" y="0" width="180" height="108"/>

Again, don't worry as much about how the skin is actually doing what it is doing as that will probably change (not using states). I'm just trying to figure out how to call a specific function when the bound property is changed.

Thank You!

like image 263
ImAStreamer Avatar asked Jan 30 '12 15:01

ImAStreamer


3 Answers

I ended up dispatching a custom event when the data is updated and listen for it in the skin.

The component:

<s:ButtonBase 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[
          import classes.CustomEvent;

          private var _iconData:String;

          [Bindable]
          public function get iconData():String
          {
               return _iconData;
          }
          public function set iconData(value:String):void 
          {
               _iconData = value;
               dispatchEvent(new CustomEvent("iconDataUpdated"));
          }
      ]]>
 </fx:Script>

The skin adds this:

    protected function skin_preinitializeHandler(event:FlexEvent):void
{
        hostComponent.addEventListener(CustomEvent.ICON_DATA_UPDATED,SetIconDisplay);
}
like image 104
ImAStreamer Avatar answered Nov 16 '22 12:11

ImAStreamer


Having the base class call a function on one particular skin can get awkward, as it means that the base class is dependent on the skin class, which makes it difficult to swap out skins. There are two good ways to get around this:

Option 1: Move the iconClass up into the component. The skin class can bind directly to the property, and the logic for deciding which icon to use can be handled by the component instead of the skin. This keeps logic out of the skin, and keeps the amount of skinning code you have to work with down.

Option 2: Add an iconData property to the skin, and bind it to the iconData property of the host component. In the setter function, call SetIconDisplay when you have a valid value. This keeps the icons encapsulated in the skin, which may help if you want to use a very different skin for the same component.

Edit: If you're planning on creating several other skins that don't use the icons, #2 is the way to go. Create a property on the skin like so:

private var _iconData:String;

public function get iconData():String
{
    return _iconData;
}

public function set iconData(value:String):void
{
    _iconData = value;
    SetIconDisplay()
}

Then use a binding to connect it to the hostComponent:

<fx:Binding source="hostComponent.iconData" destination="iconData" />
like image 29
Dan Monego Avatar answered Nov 16 '22 12:11

Dan Monego


Another solution to the general question, though maybe not ideal in this situation, is to call skin.invalidateDisplayList() whenever a property changes. Then, in the skin, override the updateDisplayList function and from there call a function that reacts to the changed properties, as well as calling the function on the parent class obviously.

See here: https://forums.adobe.com/thread/797247

like image 1
Stassa Patsantzis Avatar answered Nov 16 '22 12:11

Stassa Patsantzis