How to inherit states with mxml?

梦想与她 提交于 2019-12-04 02:41:11

I suggest you to use Spark's skinning architecture to obtain your goals. Because skin states are inherited in host component you can place all the logic in OOP way. But skins will still contain duplicate code :( Anyway it is better than duplicate code of all the component.

So our AdvancedPanel will look like the following:

package
{
    import flash.events.MouseEvent;

    import spark.components.supportClasses.ButtonBase;
    import spark.components.supportClasses.SkinnableComponent;

    [SkinState("edit")]
    [SkinState("normal")]
    public class AdvancedPanel extends SkinnableComponent
    {
        [SkinPart(required="true")]
        public var goToEditButton:ButtonBase;
        [SkinPart(required="true")]
        public var showInEditButton:ButtonBase;

        private var editMode:Boolean;

        override protected function getCurrentSkinState():String
        {
            return editMode ? "edit" : "normal";
        }

        override protected function partAdded(partName:String, instance:Object):void
        {
            super.partAdded(partName, instance);
            if (instance == goToEditButton)
                goToEditButton.addEventListener(MouseEvent.CLICK, onGoToEditButtonClick);
        }

        override protected function partRemoved(partName:String, instance:Object):void
        {
            super.partRemoved(partName, instance);
            if (instance == goToEditButton)
                goToEditButton.removeEventListener(MouseEvent.CLICK, onGoToEditButtonClick);
        }

        private function onGoToEditButtonClick(event:MouseEvent):void
        {
            editMode = true;
            invalidateSkinState();
        }
    }
}

And for CustomAdvancedPanel:

package
{
    import spark.components.supportClasses.ButtonBase;

    public class CustomAdvancedPanel extends AdvancedPanel
    {
        [SkinPart(required="true")]
        public var extraEditButton:ButtonBase;
    }
}

Of course you can inherit from Panel class but I made sample code more simple.

And the skins:

<?xml version="1.0" encoding="utf-8"?>
<!-- AdvancedPanelSkin.mxml -->
<s:Skin 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:Metadata>
        [HostComponent("AdvancedPanel")]
    </fx:Metadata>
    <s:states>
        <s:State name="normal" />
        <s:State name="edit" />
    </s:states>
    <s:Panel left="0" right="0" top="0" bottom="0">
        <s:controlBarContent>
            <s:Button id="showInEditButton" label="Show in edit" includeIn="edit" />
            <s:Button id="goToEditButton" label="Go to edit" />
        </s:controlBarContent>
    </s:Panel>
</s:Skin>

And:

<?xml version="1.0" encoding="utf-8"?>
<!-- CustomAdvancedPanelSkin.mxml -->
<s:Skin 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:Metadata>[HostComponent("CustomAdvancedPanel")]</fx:Metadata>
    <s:states>
        <s:State name="normal" />
        <s:State name="edit" />
    </s:states>
    <s:Panel left="0" right="0" top="0" bottom="0">
        <s:Button includeIn="edit" label="Extra edit button" id="extraEditButton" />
        <s:controlBarContent>
            <s:Button id="showInEditButton" label="Show in edit" includeIn="edit" />
            <s:Button id="goToEditButton" label="Go to edit" />
        </s:controlBarContent>
    </s:Panel>
</s:Skin>

AFAIK the component's state does not cross over to inherited components. Think about it - if that were the case (if you could inherit states) then it would make life really complicated whenever you want to extend a component; you would have to be aware of all inherited states and not step on their toes.

I reckon it's a limitation of OO programming, but not sure what exactly. I'm no Flex expert but I thought about it from an object-oriented programming point of view and here's what I think happens:

First consider that when you create an object, Flex (or any OO language) automatically creates a copy of that object AND a private copy of its parent object, which in turn creates a private copy of its parent object and so on up the entire object tree. That might sound weird but as an example of this, when you write super() in a constructor you are calling the constructor of the parent class.

Flex has what it calls "properties". This is the equivalent of what in Java would be a private member field (variable) with a public getter and setter method. When you declare

<local:states>xyz</local:states>

you are effectively saying

states = xyz

which in turn is the AS equivalent of saying

setStates(xyz)

The important part, and this is a general rule about properties, is that setStates is a public method, anyone can call this. However the states array itself is private. If you don't declare one, CustomAdvancedPanel has no states property. Neither does it have a setStates or getStates method. However as setStates/getStates are public, it inherits them from AdvancedPanel so it funcions as if it has these methods. When you call one of these methods (get or set the states array), it actually calls the method where it exists, which is in its parent object, AdvancedPanel. When AdvancedPanel executes the method the value of the states array in AdvancedPanel itself is read or set. This is why when you don't redeclare any states in CustomAdvancedPanel everything works perfectly - you think you are setting and getting the states array in CustomAdvancedPanel but in fact behind the scenes you are operating on the states array in the AdvancedPanel parent object, which is perfectly fine and good.

Now you redefine the states array in CustomAdvancedPanel - what is happening? Remember that declaring a property in Flex is like declaring a private class-level variable and public getters and setters. So you are giving CustomAdvancedPanel a private array called states and public getters and setters to get/set that array. These getters and setters will override the ones from AdvancedPanel. So now your application will interact with CustomAdvancedPanel the same way but behind the scenes you are no longer operating on the methods/variables of AdvancedPanel but rather on the ones you have declared in CustomAdvancedPanel itself. This explains why when you change the state of CustomAdvancedPanel, the part that is inherited from AdvancedPanel does not react, since its display is linked to the states array in AdvancedPanel, which still exists independently.

So why isn't the includeIn allowed in the basic example where you don't redeclare the states? I don't know. Either it's a bug, or perhaps more likely, there's a legitimate language/OO reason why it could never work.

It's possible that my explanation is not totally accurate. That's as far as I understand things. I myself don't know why that would really happen considering the Button in question is part of the superclass. A couple of interesting tests would be:

  1. move the click handler into an actual public method instead of inline.
  2. add super.currentState='edit' to the click handler.

If you want to learn more about all this inheritance stuff, write some simple classes in ActionScript or Flex with one class inheriting another, and run various function calls to see what happens.

"Or is there a better way to obtain the same result?"

Since you asked, and because you didn't make a clear case as to the need for the extra CustomAdvancedPanel component, putting the "Extra edit button" in the AdvancedPanel component is the simplest solution.

<!-- AdvancedPanel.mxml -->
<s:Panel>
  <s:states>
    <s:State name="normal"/>
    <s:State name="edit"/>
  </s:states>
  <s:Button includeIn="edit" label="Extra edit button"/>
  <s:controlBarContent>
    <s:Button 
      includeIn="edit"
      label="Show in edit"/>
    <s:Button 
      label="Go to edit"
      click="{currentState='edit'}"/>
  </s:controlBarContent>
</s:Panel>

Assaf Lavie is right, it would be very confusing, if a custom component had its parent's states. i'd say consider using skins:

Of course the politically correct way is to use skins. However, for those who really just want to brute force state inheritance for MXML classes here is a work around that I have found.

For this method to work, the extending MXML class should declare exactly the same states of the base MXML class, no more and no fewer, all with identical names.

Then in the extending class insert the following method:

        override public function set states(value:Array):void
        {
            if(super.states == null || super.states.length == 0)
            {
                super.states  = value;

                for each (var state:State in value)
                {
                    state.name = "_"+state.name;
                }
            }
            else
            {
                for each (var state:State in value)
                {
                    state.basedOn = "_"+state.name;
                    super.states.push(state);
                }
            }
        }

This works because as the component is created the states variable is set twice, once by the base class, and once by the extending class. This workaround just combines them together.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!